1 /*
2  * mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2014-2015, Grasshopper
4  *
5  * Version: MPL 1.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18  *
19  * The Initial Developer of the Original Code is Grasshopper
20  * Portions created by the Initial Developer are Copyright (C)
21  * the Initial Developer. All Rights Reserved.
22  *
23  * Contributor(s):
24  * Chris Rienzo <chris.rienzo@grasshopper.com>
25  *
26  * rayo_cpa_detector.c -- Glue to normalize events from and to allow multiple instantiation of various detectors in FreeSWITCH
27  */
28 
29 #include "rayo_cpa_detector.h"
30 
31 static struct {
32 	/** detectors supported by this module mapped by signal-type */
33 	switch_hash_t *detectors;
34 	/** synchronizes access to detectors */
35 	switch_mutex_t *detectors_mutex;
36 } globals;
37 
38 struct rayo_cpa_detector;
39 
40 /**
41  * Detector definition
42  */
43 struct rayo_cpa_detector {
44 	/** unique internal name of this detector */
45 	const char *name;
46 	/** detector ID */
47 	const char *uuid;
48 	/** start detection APP */
49 	const char *start_app;
50 	/** args to pass to start detection app */
51 	const char *start_app_args;
52 	/** stop detection APP */
53 	const char *stop_app;
54 	/** args to pass to stop detection app */
55 	const char *stop_app_args;
56 	/** (optional) name of header to get the signal type from */
57 	const char *signal_type_header;
58 	/** (optional) where to get the signal value from the event */
59 	const char *signal_value_header;
60 	/** (optional) where to get the signal duration from the event */
61 	const char *signal_duration_header;
62 	/** detector event to signal type mapping */
63 	switch_hash_t *signal_type_map;
64 };
65 
66 /**
67  * Detection state
68  */
69 struct rayo_cpa_detector_state {
70 	/** reference count */
71 	int refs;
72 };
73 
74 /**
75  * Start detecting
76  * @param call_uuid call to detect signal on
77  * @param signal_ns namespace of signal to detect
78  * @param error_detail on failure, describes the error
79  * @return 1 if successful, 0 if failed
80  */
rayo_cpa_detector_start(const char * call_uuid,const char * signal_ns,const char ** error_detail)81 int rayo_cpa_detector_start(const char *call_uuid, const char *signal_ns, const char **error_detail)
82 {
83 	struct rayo_cpa_detector *detector = switch_core_hash_find(globals.detectors, signal_ns);
84 	switch_core_session_t *session;
85 	if (detector) {
86 		if (zstr(detector->start_app)) {
87 			/* nothing to do */
88 			return 1;
89 		}
90 		session = switch_core_session_locate(call_uuid);
91 		if (session) {
92 			struct rayo_cpa_detector_state *detector_state = switch_channel_get_private(switch_core_session_get_channel(session), detector->uuid);
93 			if (detector_state) {
94 				/* detector is already running */
95 				detector_state->refs++;
96 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Start detector %s, refs = %d\n", detector->name, detector_state->refs);
97 				switch_core_session_rwunlock(session);
98 				return 1;
99 			}
100 			detector_state = switch_core_session_alloc(session, sizeof(*detector_state));
101 			detector_state->refs = 1;
102 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Starting detector %s, refs = 1\n", detector->name);
103 			switch_channel_set_private(switch_core_session_get_channel(session), detector->uuid, detector_state);
104 			switch_core_session_execute_application_async(session, detector->start_app, zstr(detector->start_app_args) ? NULL : detector->start_app_args);
105 			switch_core_session_rwunlock(session);
106 			return 1;
107 		} else {
108 			*error_detail = "session gone";
109 			return 0;
110 		}
111 	}
112 	*error_detail = "detector not supported";
113 	return 0;
114 }
115 
116 /**
117  * Stop detecting
118  * @param call_uuid call to stop detecting signal on
119  * @param signal_ns name of signal to stop detecting
120  */
rayo_cpa_detector_stop(const char * call_uuid,const char * signal_ns)121 void rayo_cpa_detector_stop(const char *call_uuid, const char *signal_ns)
122 {
123 	struct rayo_cpa_detector *detector = switch_core_hash_find(globals.detectors, signal_ns);
124 	switch_core_session_t *session;
125 	if (detector) {
126 		if (zstr(detector->stop_app)) {
127 			/* nothing to do */
128 			return;
129 		}
130 		session = switch_core_session_locate(call_uuid);
131 		if (session) {
132 			struct rayo_cpa_detector_state *detector_state = switch_channel_get_private(switch_core_session_get_channel(session), detector->uuid);
133 			if (detector_state) {
134 				detector_state->refs--;
135 				if (detector_state->refs < 0) {
136 					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Stop detector %s refs = %d\n", detector->name, detector_state->refs);
137 				} else {
138 					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Stop detector %s refs = %d\n", detector->name, detector_state->refs);
139 				}
140 				if (detector_state->refs == 0) {
141 					/* nobody interested in detector events- shut it down */
142 					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Stopping detector %s\n", detector->name);
143 					switch_core_session_execute_application_async(session, detector->stop_app, zstr(detector->stop_app_args) ? NULL : detector->stop_app_args);
144 					switch_channel_set_private(switch_core_session_get_channel(session), detector->uuid, NULL);
145 				}
146 			} else {
147 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Detector %s is already stopped\n", detector->name);
148 			}
149 			switch_core_session_rwunlock(session);
150 		}
151 	}
152 }
153 
154 /**
155  * Handle event from detector
156  */
rayo_cpa_detector_event(switch_event_t * event)157 static void rayo_cpa_detector_event(switch_event_t *event)
158 {
159 	struct rayo_cpa_detector *detector = (struct rayo_cpa_detector *)event->bind_user_data;
160 	if (detector) {
161 		const char *signal_type = "rayo_default";
162 		if (!zstr(detector->signal_type_header)) {
163 			signal_type = switch_event_get_header(event, detector->signal_type_header);
164 		}
165 		if (!zstr(signal_type)) {
166 			signal_type = switch_core_hash_find(detector->signal_type_map, signal_type);
167 		}
168 		if (!zstr(signal_type)) {
169 			switch_event_t *cpa_event;
170 			const char *uuid = switch_event_get_header(event, "Unique-ID");
171 			if (zstr(uuid)) {
172 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s %s event is missing call UUID!\n", detector->name, signal_type);
173 				return;
174 			}
175 			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Got Rayo CPA event %s\n", signal_type);
176 			if (switch_event_create_subclass(&cpa_event, SWITCH_EVENT_CUSTOM, "rayo::cpa") == SWITCH_STATUS_SUCCESS) {
177 				switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "Unique-ID", uuid);
178 				switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "detector-name", detector->name);
179 				switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "detector-uuid", detector->uuid);
180 				switch_event_add_header(cpa_event, SWITCH_STACK_BOTTOM, "signal-type", "%s%s:%s", RAYO_CPA_BASE, signal_type, RAYO_VERSION);
181 				if (!zstr(detector->signal_value_header)) {
182 					const char *value = switch_event_get_header(event, detector->signal_value_header);
183 					if (!zstr(value)) {
184 						switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "value", value);
185 					}
186 				}
187 				if (!zstr(detector->signal_duration_header)) {
188 					const char *duration = switch_event_get_header(event, detector->signal_duration_header);
189 					if (!zstr(duration)) {
190 						switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "duration", duration);
191 					}
192 				}
193 				switch_event_fire(&cpa_event);
194 			}
195 		} else {
196 			/* couldn't map event to Rayo signal-type */
197 			const char *event_name = switch_event_get_header(event, "Event-Name");
198 			const char *event_subclass = switch_event_get_header(event, "Event-Subclass");
199 			if (zstr(event_subclass)) {
200 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to find Rayo signal-type for event %s\n", event_name);
201 			} else {
202 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to find Rayo signal-type for event %s %s\n", event_name, event_subclass);
203 			}
204 		}
205 	}
206 }
207 
208 #define RAYO_CPA_DETECTOR_SYNTAX  "rayo_cpa <uuid> <signal-type> <start|stop>"
SWITCH_STANDARD_API(rayo_cpa_detector_api)209 SWITCH_STANDARD_API(rayo_cpa_detector_api)
210 {
211 	char *cmd_dup = NULL;
212 	char *argv[4] = { 0 };
213 	int argc = 0;
214 
215 	if (zstr(cmd)) {
216 		stream->write_function(stream, "-ERR: USAGE %s\n", RAYO_CPA_DETECTOR_SYNTAX);
217 		goto done;
218 	}
219 
220 	cmd_dup = strdup(cmd);
221 	argc = switch_separate_string(cmd_dup, ' ', argv, sizeof(argv) / sizeof(argv[0]));
222 
223 	if (argc != 3) {
224 		stream->write_function(stream, "-ERR: USAGE %s\n", RAYO_CPA_DETECTOR_SYNTAX);
225 	} else {
226 		const char *err_reason = NULL;
227 		if (!strcmp(argv[2], "stop")) {
228 			rayo_cpa_detector_stop(argv[0], argv[1]);
229 			stream->write_function(stream, "+OK\n");
230 		} else if (!strcmp(argv[2], "start")) {
231 			if (!rayo_cpa_detector_start(argv[0], argv[1], &err_reason)) {
232 				if (err_reason) {
233 					stream->write_function(stream, "-ERR: %s\n", err_reason);
234 				} else {
235 					stream->write_function(stream, "-ERR\n");
236 				}
237 			} else {
238 				stream->write_function(stream, "+OK\n");
239 			}
240 		} else {
241 			stream->write_function(stream, "-ERR: USAGE %s\n", RAYO_CPA_DETECTOR_SYNTAX);
242 		}
243 	}
244 
245 done:
246 	switch_safe_free(cmd_dup);
247 
248 	return SWITCH_STATUS_SUCCESS;
249 }
250 
251 /**
252  * Detector definition destructor
253  */
destroy_detector(void * ptr)254 static void destroy_detector(void *ptr)
255 {
256     struct rayo_cpa_detector *detector = (struct rayo_cpa_detector *) ptr;
257 	if (detector->signal_type_map) {
258 		switch_core_hash_destroy(&detector->signal_type_map);
259 	}
260 }
261 
262 /**
263  * Configure CPA
264  */
do_config(switch_memory_pool_t * pool,const char * config_file)265 static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
266 {
267 	switch_xml_t cfg, xml, cpa_xml;
268 	switch_status_t status = SWITCH_STATUS_SUCCESS;
269 	switch_hash_t *bound_events;
270 
271 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Configuring CPA\n");
272 	if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
273 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
274 		return SWITCH_STATUS_TERM;
275 	}
276 
277 	switch_core_hash_init(&bound_events);
278 
279 	cpa_xml = switch_xml_child(cfg, "cpa");
280 	if (cpa_xml) {
281 		switch_xml_t detector_xml;
282 
283 		for (detector_xml = switch_xml_child(cpa_xml, "detector"); detector_xml; detector_xml = detector_xml->next) {
284 			switch_xml_t start_xml, stop_xml, event_xml;
285 			struct rayo_cpa_detector *detector;
286 			char id[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 };
287 			const char *name = switch_xml_attr_soft(detector_xml, "name");
288 			if (zstr(name)) {
289 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing name of CPA detector!\n");
290 				status = SWITCH_STATUS_TERM;
291 				goto done;
292 			}
293 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "CPA detector: %s\n", name);
294 			detector = switch_core_alloc(pool, sizeof(*detector));
295 			switch_core_hash_init(&detector->signal_type_map);
296 			detector->name = switch_core_strdup(pool, name);
297 			switch_uuid_str(id, sizeof(id));
298 			detector->uuid = switch_core_strdup(pool, id);
299 
300 			start_xml = switch_xml_child(detector_xml, "start");
301 			if (start_xml) {
302 				detector->start_app = switch_core_strdup(pool, switch_xml_attr_soft(start_xml, "application"));
303 				detector->start_app_args = switch_core_strdup(pool, switch_xml_attr_soft(start_xml, "data"));
304 			}
305 
306 			stop_xml = switch_xml_child(detector_xml, "stop");
307 			if (stop_xml) {
308 				detector->stop_app = switch_core_strdup(pool, switch_xml_attr_soft(stop_xml, "application"));
309 				detector->stop_app_args = switch_core_strdup(pool, switch_xml_attr_soft(stop_xml, "data"));
310 			}
311 
312 			event_xml = switch_xml_child(detector_xml, "event");
313 			if (event_xml) {
314 				int event_ok = 0;
315 				switch_xml_t signal_type_xml;
316 				const char *event_class = switch_xml_attr_soft(event_xml, "class");
317 				const char *event_subclass = switch_xml_attr_soft(event_xml, "subclass");
318 				switch_event_types_t event_type;
319 				if (zstr(event_class)) {
320 					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing event class for CPA detector: %s\n", detector->name);
321 					status = SWITCH_STATUS_TERM;
322 					goto done;
323 				}
324 
325 				if (switch_name_event(event_class, &event_type) != SWITCH_STATUS_SUCCESS) {
326 					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid event class %s for CPA detector: %s\n", event_class, detector->name);
327 					status = SWITCH_STATUS_TERM;
328 					goto done;
329 				}
330 
331 				/* bind detector to event if not already done... */
332 				{
333 					struct rayo_cpa_detector *bound_detector;
334 					const char *event_name = switch_core_sprintf(pool, "%s %s", event_class, event_subclass);
335 					if (!(bound_detector = switch_core_hash_find(bound_events, event_name))) {
336 						/* not yet bound */
337 						if (zstr(event_subclass)) {
338 							event_subclass = NULL;
339 						}
340 						switch_event_bind("rayo_cpa_detector", event_type, event_subclass, rayo_cpa_detector_event, detector);
341 						switch_core_hash_insert(bound_events, event_name, detector); /* mark as bound */
342 					} else if (bound_detector != detector) {
343 						/* can't have multiple detectors generating the same event! */
344 						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s attempted to bind to event %s that is already bound by %s\n", detector->name, event_name, bound_detector->name);
345 						status = SWITCH_STATUS_TERM;
346 						goto done;
347 					}
348 				}
349 
350 				/* configure native event -> rayo CPA event mapping */
351 				detector->signal_type_header = switch_core_strdup(pool, switch_xml_attr_soft(event_xml, "type-header"));
352 				detector->signal_value_header = switch_core_strdup(pool, switch_xml_attr_soft(event_xml, "value-header"));
353 				detector->signal_duration_header = switch_core_strdup(pool, switch_xml_attr_soft(event_xml, "duration-header"));
354 
355 				/* configure native event type -> rayo CPA signal type mapping */
356 				for (signal_type_xml = switch_xml_child(event_xml, "signal-type"); signal_type_xml; signal_type_xml = signal_type_xml->next) {
357 					const char *header_value = switch_core_strdup(pool, switch_xml_attr_soft(signal_type_xml, "header-value"));
358 					const char *signal_type = switch_core_strdup(pool, switch_xml_attr_soft(signal_type_xml, "value"));
359 					if (zstr(signal_type)) {
360 						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s missing signal-type value!\n", detector->name);
361 						status = SWITCH_STATUS_TERM;
362 						goto done;
363 					} else {
364 						/* add signal-type to detector mapping if not already done for this detector */
365 						const char *signal_type_ns = switch_core_sprintf(pool, "%s%s:%s", RAYO_CPA_BASE, signal_type, RAYO_VERSION);
366 						struct rayo_cpa_detector *bound_detector = switch_core_hash_find(globals.detectors, signal_type_ns);
367 						if (!bound_detector) {
368 							switch_core_hash_insert_destructor(globals.detectors, signal_type_ns, detector, destroy_detector);
369 							switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding CPA %s => %s\n", signal_type_ns, detector->name);
370 							event_ok = 1;
371 						} else if (bound_detector == detector) {
372 							/* detector has multiple signal-type configs w/ same value */
373 							event_ok = 1;
374 						} else {
375 							/* multiple detectors with same signal-type value */
376 							switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s configured to handle signal-type %s that is already handled by %s\n", detector->name, signal_type, bound_detector->name);
377 							status = SWITCH_STATUS_TERM;
378 							goto done;
379 						}
380 					}
381 
382 					/* map event value to signal-type */
383 					if (zstr(header_value)) {
384 						switch_core_hash_insert(detector->signal_type_map, "rayo_default", signal_type);
385 					} else {
386 						switch_core_hash_insert(detector->signal_type_map, header_value, signal_type);
387 					}
388 				}
389 
390 				if (!event_ok) {
391 					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s is missing Rayo signal-type for event\n", detector->name);
392 					status = SWITCH_STATUS_TERM;
393 					goto done;
394 				}
395 			}
396 		}
397 	}
398 
399 done:
400 	switch_core_hash_destroy(&bound_events);
401 	switch_xml_free(xml);
402 
403 	return status;
404 }
405 
406 /**
407  * Console auto-completion for signal types
408  */
rayo_cpa_detector_signal_types(const char * line,const char * cursor,switch_console_callback_match_t ** matches)409 static switch_status_t rayo_cpa_detector_signal_types(const char *line, const char *cursor, switch_console_callback_match_t **matches)
410 {
411 	switch_status_t status = SWITCH_STATUS_FALSE;
412 	switch_hash_index_t *hi;
413 	void *val;
414 	const void *vvar;
415 	switch_console_callback_match_t *my_matches = NULL;
416 
417 	switch_mutex_lock(globals.detectors_mutex);
418 	for (hi = switch_core_hash_first(globals.detectors); hi; hi = switch_core_hash_next(&hi)) {
419 		switch_core_hash_this(hi, &vvar, NULL, &val);
420 		switch_console_push_match(&my_matches, (const char *) vvar);
421 	}
422 	switch_mutex_unlock(globals.detectors_mutex);
423 
424 	if (my_matches) {
425 		*matches = my_matches;
426 		status = SWITCH_STATUS_SUCCESS;
427 	}
428 
429 	return status;
430 }
431 
432 /**
433  * Load CPA signal detection features
434  * @param module_interface
435  * @param pool memory pool
436  * @param config_file
437  * @return SWITCH_STATUS_SUCCESS if successfully loaded
438  */
rayo_cpa_detector_load(switch_loadable_module_interface_t ** module_interface,switch_memory_pool_t * pool,const char * config_file)439 switch_status_t rayo_cpa_detector_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
440 {
441 	switch_api_interface_t *api_interface;
442 
443 	switch_core_hash_init(&globals.detectors);
444 	switch_mutex_init(&globals.detectors_mutex, SWITCH_MUTEX_NESTED, pool);
445 
446 	if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
447 		return SWITCH_STATUS_TERM;
448 	}
449 
450 	SWITCH_ADD_API(api_interface, "rayo_cpa", "Query rayo status", rayo_cpa_detector_api, RAYO_CPA_DETECTOR_SYNTAX);
451 
452 	switch_console_set_complete("add rayo_cpa ::console::list_uuid ::rayo_cpa::list_signal_types start");
453 	switch_console_set_complete("add rayo_cpa ::console::list_uuid ::rayo_cpa::list_signal_types stop");
454 	switch_console_add_complete_func("::rayo_cpa::list_signal_types", rayo_cpa_detector_signal_types);
455 
456 	return SWITCH_STATUS_SUCCESS;
457 }
458 
459 /**
460  * Disable CPA signal detection features
461  */
rayo_cpa_detector_shutdown(void)462 void rayo_cpa_detector_shutdown(void)
463 {
464 	switch_console_set_complete("del rayo_cpa");
465 	switch_console_del_complete_func("::rayo_cpa::list_signal_types");
466 	if (globals.detectors) {
467 		switch_core_hash_destroy(&globals.detectors);
468 	}
469 	switch_event_unbind_callback(rayo_cpa_detector_event);
470 }
471 
472 
473 /* For Emacs:
474  * Local Variables:
475  * mode:c
476  * indent-tabs-mode:t
477  * tab-width:4
478  * c-basic-offset:4
479  * End:
480  * For VIM:
481  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
482  */
483