1 /*
2  * mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3  * Copyright (C) 2013-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_input_component.c -- Rayo input component implementation
27  *
28  */
29 #include "rayo_components.h"
30 #include "rayo_cpa_component.h"
31 #include "rayo_elements.h"
32 #include "srgs.h"
33 #include "nlsml.h"
34 
35 #define MAX_DTMF 256
36 
37 #define INPUT_MATCH_TAG "match"
38 #define INPUT_MATCH INPUT_MATCH_TAG, RAYO_INPUT_COMPLETE_NS
39 #define INPUT_NOINPUT "noinput", RAYO_INPUT_COMPLETE_NS
40 #define INPUT_NOMATCH "nomatch", RAYO_INPUT_COMPLETE_NS
41 
42 #define RAYO_INPUT_COMPONENT_PRIVATE_VAR "__rayo_input_component"
43 
44 struct input_handler;
45 
46 static struct {
47 	/** grammar parser */
48 	struct srgs_parser *parser;
49 	/** default recognizer to use if none specified */
50 	const char *default_recognizer;
51 } globals;
52 
53 /**
54  * Input component state
55  */
56 struct input_component {
57 	/** component base class */
58 	struct rayo_component base;
59 	/** true if speech detection */
60 	int speech_mode;
61 	/** Number of collected digits */
62 	int num_digits;
63 	/** Terminating digit */
64 	char term_digit;
65 	/** The collected digits */
66 	char digits[MAX_DTMF + 1];
67 	/** grammar to match */
68 	struct srgs_grammar *grammar;
69 	/** time when last digit was received */
70 	switch_time_t last_digit_time;
71 	/** timeout before first digit is received */
72 	int initial_timeout;
73 	/** maximum silence allowed */
74 	int max_silence;
75 	/** minimum speech detection confidence */
76 	double min_confidence;
77 	/** sensitivity to background noise */
78 	double sensitivity;
79 	/** timeout after first digit is received */
80 	int inter_digit_timeout;
81 	/** stop flag */
82 	int stop;
83 	/** true if input timers started */
84 	int start_timers;
85 	/** true if event fired for first digit / start of speech */
86 	int barge_event;
87 	/** optional language to use */
88 	const char *language;
89 	/** optional recognizer to use */
90 	const char *recognizer;
91 	/** global data */
92 	struct input_handler *handler;
93 };
94 
95 #define INPUT_COMPONENT(x) ((struct input_component *)x)
96 
97 /**
98  * Call input state
99  */
100 struct input_handler {
101 	/** media bug to monitor frames / control input lifecycle */
102 	switch_media_bug_t *bug;
103 	/** active voice input component */
104 	struct input_component *voice_component;
105 	/** active dtmf input components */
106 	switch_hash_t *dtmf_components;
107 	/** synchronizes media bug and dtmf callbacks */
108 	switch_mutex_t *mutex;
109 	/** last recognizer used */
110 	const char *last_recognizer;
111 };
112 
113 /**
114  * @param digit1 to match
115  * @param digit2 to match
116  * @return true if matching
117  */
digit_test(char digit1,char digit2)118 static int digit_test(char digit1, char digit2)
119 {
120 	return digit1 && digit2 && tolower(digit1) == tolower(digit2);
121 }
122 
123 /**
124  * Send match event to client
125  */
send_match_event(struct rayo_component * component,iks * result)126 static void send_match_event(struct rayo_component *component, iks *result)
127 {
128 	iks *event = rayo_component_create_complete_event(RAYO_COMPONENT(component), INPUT_MATCH);
129 	iks *match = iks_find(iks_find(event, "complete"), INPUT_MATCH_TAG);
130 	iks_insert_attrib(match, "content-type", "application/nlsml+xml");
131 	iks_insert_cdata(match, iks_string(iks_stack(result), result), 0);
132 	rayo_component_send_complete_event(component, event);
133 }
134 
135 /**
136  * Send barge-in event to client
137  */
send_barge_event(struct rayo_component * component)138 static void send_barge_event(struct rayo_component *component)
139 {
140 	iks *event = iks_new("presence");
141 	iks *x;
142 	iks_insert_attrib(event, "from", RAYO_JID(component));
143 	iks_insert_attrib(event, "to", component->client_jid);
144 	x = iks_insert(event, "start-of-input");
145 	iks_insert_attrib(x, "xmlns", RAYO_INPUT_NS);
146 	RAYO_SEND_REPLY(component, component->client_jid, event);
147 }
148 
149 /**
150  * Check if dtmf component has timed out
151  */
dtmf_component_check_timeout(struct input_component * component,switch_core_session_t * session)152 static switch_status_t dtmf_component_check_timeout(struct input_component *component, switch_core_session_t *session)
153 {
154 	/* check for stopped component */
155 	if (component->stop) {
156 		rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
157 
158 		/* let handler know component is done */
159 		return SWITCH_STATUS_FALSE;
160 	}
161 
162 	/* check for timeout */
163 	if (component->start_timers) {
164 		int elapsed_ms = (switch_micro_time_now() - component->last_digit_time) / 1000;
165 		if (component->num_digits && component->inter_digit_timeout > 0 && elapsed_ms > component->inter_digit_timeout) {
166 			enum srgs_match_type match;
167 			const char *interpretation = NULL;
168 
169 			/* we got some input, check for match */
170 			match = srgs_grammar_match(component->grammar, component->digits, &interpretation);
171 			if (match == SMT_MATCH || match == SMT_MATCH_END) {
172 				iks *result = nlsml_create_dtmf_match(component->digits, interpretation);
173 				/* notify of match */
174 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "MATCH = %s\n", component->digits);
175 				send_match_event(RAYO_COMPONENT(component), result);
176 				iks_delete(result);
177 			} else {
178 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "inter-digit-timeout\n");
179 				rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOMATCH);
180 			}
181 
182 			/* let handler know component is done */
183 			return SWITCH_STATUS_FALSE;
184 		} else if (!component->num_digits && component->initial_timeout > 0 && elapsed_ms > component->initial_timeout) {
185 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "initial-timeout\n");
186 			rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOINPUT);
187 
188 			/* let handler know component is done */
189 			return SWITCH_STATUS_FALSE;
190 		}
191 	}
192 	return SWITCH_STATUS_SUCCESS;
193 }
194 
195 /**
196  * Process DTMF press for a specific component
197  * @param component to receive DTMF
198  * @param session
199  * @param dtmf
200  * @param direction
201  * @return SWITCH_STATUS_FALSE if component is done
202  */
dtmf_component_on_dtmf(struct input_component * component,switch_core_session_t * session,const switch_dtmf_t * dtmf,switch_dtmf_direction_t direction)203 static switch_status_t dtmf_component_on_dtmf(struct input_component *component, switch_core_session_t *session, const switch_dtmf_t *dtmf, switch_dtmf_direction_t direction)
204 {
205 	int is_term_digit = 0;
206 	enum srgs_match_type match;
207 	const char *interpretation = NULL;
208 
209 	is_term_digit = digit_test(component->term_digit, dtmf->digit);
210 
211 	if (!is_term_digit) {
212 		component->digits[component->num_digits] = dtmf->digit;
213 		component->num_digits++;
214 		component->digits[component->num_digits] = '\0';
215 		component->last_digit_time = switch_micro_time_now();
216 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Collected digits = \"%s\"\n", component->digits);
217 	} else {
218 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Collected term digit = \"%c\"\n", dtmf->digit);
219 	}
220 
221 	match = srgs_grammar_match(component->grammar, component->digits, &interpretation);
222 
223 	if (is_term_digit) {
224 		/* finalize result if terminating digit was pressed */
225 		if (match == SMT_MATCH_PARTIAL) {
226 			match = SMT_NO_MATCH;
227 		} else if (match == SMT_MATCH) {
228 			match = SMT_MATCH_END;
229 		}
230 	} else if (component->num_digits >= MAX_DTMF) {
231 		/* maximum digits collected and still not a definitive match */
232 		if (match != SMT_MATCH_END) {
233 			match = SMT_NO_MATCH;
234 		}
235 	}
236 
237 	switch (match) {
238 		case SMT_MATCH:
239 		case SMT_MATCH_PARTIAL: {
240 			/* need more digits */
241 			if (component->num_digits == 1) {
242 				send_barge_event(RAYO_COMPONENT(component));
243 			}
244 			break;
245 		}
246 		case SMT_NO_MATCH: {
247 			/* notify of no-match and remove input component */
248 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "NO MATCH = %s\n", component->digits);
249 			rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOMATCH);
250 
251 			/* let handler know component is done */
252 			return SWITCH_STATUS_FALSE;
253 		}
254 		case SMT_MATCH_END: {
255 			iks *result = nlsml_create_dtmf_match(component->digits, interpretation);
256 			/* notify of match and remove input component */
257 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "MATCH = %s\n", component->digits);
258 			send_match_event(RAYO_COMPONENT(component), result);
259 			iks_delete(result);
260 
261 			/* let handler know component is done */
262 			return SWITCH_STATUS_FALSE;
263 		}
264 	}
265 
266 	/* still need more input */
267 	return SWITCH_STATUS_SUCCESS;
268 }
269 
270 /**
271  * Process DTMF press on call
272  */
input_handler_on_dtmf(switch_core_session_t * session,const switch_dtmf_t * dtmf,switch_dtmf_direction_t direction)273 static switch_status_t input_handler_on_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf, switch_dtmf_direction_t direction)
274 {
275 	switch_channel_t *channel = switch_core_session_get_channel(session);
276 	struct input_handler *handler = (struct input_handler *)switch_channel_get_private(channel, RAYO_INPUT_COMPONENT_PRIVATE_VAR);
277 
278 	if (handler) {
279 		switch_event_t *components_to_remove = NULL;
280 		switch_hash_index_t *hi;
281 
282 		switch_mutex_lock(handler->mutex);
283 
284 		/* check input on each component */
285 		for (hi = switch_core_hash_first(handler->dtmf_components); hi; hi = switch_core_hash_next(&hi)) {
286 			const void *jid;
287 			void *component;
288 			switch_core_hash_this(hi, &jid, NULL, &component);
289 			if (dtmf_component_on_dtmf(INPUT_COMPONENT(component), session, dtmf, direction) != SWITCH_STATUS_SUCCESS) {
290 				if (!components_to_remove) {
291 					switch_event_create_subclass(&components_to_remove, SWITCH_EVENT_CLONE, NULL);
292 				}
293 				switch_event_add_header_string(components_to_remove, SWITCH_STACK_BOTTOM, "done", RAYO_JID(component));
294 			}
295 		}
296 
297 		/* remove any finished components */
298 		if (components_to_remove) {
299 			switch_event_header_t *component_to_remove = NULL;
300 			for (component_to_remove = components_to_remove->headers; component_to_remove; component_to_remove = component_to_remove->next) {
301 				switch_core_hash_delete(handler->dtmf_components, component_to_remove->value);
302 			}
303 			switch_event_destroy(&components_to_remove);
304 		}
305 
306 		switch_mutex_unlock(handler->mutex);
307 	}
308     return SWITCH_STATUS_SUCCESS;
309 }
310 
311 /**
312  * Monitor for input
313  */
input_handler_bug_callback(switch_media_bug_t * bug,void * user_data,switch_abc_type_t type)314 static switch_bool_t input_handler_bug_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
315 {
316 	switch_core_session_t *session = switch_core_media_bug_get_session(bug);
317 	struct input_handler *handler = (struct input_handler *)user_data;
318 	switch_hash_index_t *hi;
319 
320 	switch_mutex_lock(handler->mutex);
321 
322 	switch(type) {
323 		case SWITCH_ABC_TYPE_INIT: {
324 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Adding DTMF callback\n");
325 			switch_core_event_hook_add_recv_dtmf(session, input_handler_on_dtmf);
326 			break;
327 		}
328 		case SWITCH_ABC_TYPE_READ_REPLACE: {
329 			switch_frame_t *rframe = switch_core_media_bug_get_read_replace_frame(bug);
330 			switch_event_t *components_to_remove = NULL;
331 
332 			/* check timeout/stop on each component */
333 			for (hi = switch_core_hash_first(handler->dtmf_components); hi; hi = switch_core_hash_next(&hi)) {
334 				const void *jid;
335 				void *component;
336 				switch_core_hash_this(hi, &jid, NULL, &component);
337 				if (dtmf_component_check_timeout(INPUT_COMPONENT(component), session) != SWITCH_STATUS_SUCCESS) {
338 					if (!components_to_remove) {
339 						switch_event_create_subclass(&components_to_remove, SWITCH_EVENT_CLONE, NULL);
340 					}
341 					switch_event_add_header_string(components_to_remove, SWITCH_STACK_BOTTOM, "done", RAYO_JID(component));
342 				}
343 			}
344 
345 			/* remove any finished components */
346 			if (components_to_remove) {
347 				switch_event_header_t *component_to_remove = NULL;
348 				for (component_to_remove = components_to_remove->headers; component_to_remove; component_to_remove = component_to_remove->next) {
349 					switch_core_hash_delete(handler->dtmf_components, component_to_remove->value);
350 				}
351 				switch_event_destroy(&components_to_remove);
352 			}
353 
354 			switch_core_media_bug_set_read_replace_frame(bug, rframe);
355 			break;
356 		}
357 		case SWITCH_ABC_TYPE_CLOSE:
358 			/* complete all components */
359 			for (hi = switch_core_hash_first(handler->dtmf_components); hi; hi = switch_core_hash_next(&hi)) {
360 				const void *jid;
361 				void *component;
362 				switch_core_hash_this(hi, &jid, NULL, &component);
363 				rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_HANGUP);
364 			}
365 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Removing DTMF callback\n");
366 			switch_core_event_hook_remove_recv_dtmf(session, input_handler_on_dtmf);
367 			switch_core_hash_destroy(&handler->dtmf_components);
368 			break;
369 		default:
370 			break;
371 	}
372 	switch_mutex_unlock(handler->mutex);
373 	return SWITCH_TRUE;
374 }
375 
376 /**
377  * Validate input request
378  * @param input request to validate
379  * @param error message
380  * @return 0 if error, 1 if valid
381  */
validate_call_input(iks * input,const char ** error)382 static int validate_call_input(iks *input, const char **error)
383 {
384 	iks *grammar;
385 	const char *content_type;
386 	int has_grammar = 0;
387 	int use_mrcp = 0;
388 
389 	/* validate input attributes */
390 	if (!VALIDATE_RAYO_INPUT(input)) {
391 		*error = "Bad <input> attrib value";
392 		return 0;
393 	}
394 
395 	use_mrcp = !strncmp("unimrcp", iks_find_attrib(input, "recognizer") ? iks_find_attrib(input, "recognizer") : globals.default_recognizer, 7);
396 
397 	/* validate grammar elements */
398 	for (grammar = iks_find(input, "grammar"); grammar; grammar = iks_next_tag(grammar)) {
399 		/* is this a grammar? */
400 		if (strcmp("grammar", iks_name(grammar))) {
401 			continue;
402 		}
403 		content_type = iks_find_attrib(grammar, "content-type");
404 		if (zstr(content_type)) {
405 			/* grammar URL */
406 			if (zstr(iks_find_attrib(grammar, "url"))) {
407 				*error = "url or content-type must be set";
408 				return 0;
409 			} else if (!use_mrcp) {
410 				*error = "url only supported with unimrcp recognizer";
411 				return 0;
412 			}
413 		} else {
414 			/* inline grammar / only support srgs */
415 			if (!zstr(iks_find_attrib(grammar, "url"))) {
416 				*error = "url not allowed with content-type";
417 				return 0;
418 			} else if (strcmp("application/srgs+xml", content_type) && strcmp("text/plain", content_type)) {
419 				*error = "Unsupported content type";
420 				return 0;
421 			}
422 
423 			/* missing inline grammar body */
424 			if (zstr(iks_find_cdata(input, "grammar"))) {
425 				*error = "Grammar content is missing";
426 				return 0;
427 			}
428 		}
429 		has_grammar = 1;
430 	}
431 
432 	if (!has_grammar) {
433 		*error = "Missing <grammar>";
434 		return 0;
435 	}
436 
437 	return 1;
438 }
439 
setup_grammars_pocketsphinx(struct input_component * component,switch_core_session_t * session,iks * input,const struct xmpp_error ** stanza_error,const char ** error_detail)440 static char *setup_grammars_pocketsphinx(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail)
441 {
442 	const char *jsgf_path;
443 	switch_stream_handle_t grammar = { 0 };
444 	SWITCH_STANDARD_STREAM(grammar);
445 
446 	/* transform SRGS grammar to JSGF */
447 	if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
448 		*stanza_error = STANZA_ERROR_BAD_REQUEST;
449 		*error_detail = "Failed to parse grammar body";
450 		switch_safe_free(grammar.data);
451 		return NULL;
452 	}
453 
454 	jsgf_path = srgs_grammar_to_jsgf_file(component->grammar, SWITCH_GLOBAL_dirs.grammar_dir, "gram");
455 	if (!jsgf_path) {
456 		*stanza_error = STANZA_ERROR_BAD_REQUEST;
457 		*error_detail = "Grammar conversion to JSGF error";
458 		switch_safe_free(grammar.data);
459 		return NULL;
460 	}
461 
462 	/* build pocketsphinx grammar string */
463 	grammar.write_function(&grammar,
464 		"{start-input-timers=%s,no-input-timeout=%d,speech-timeout=%d,confidence-threshold=%d}%s",
465 		component->start_timers ? "true" : "false",
466 		component->initial_timeout,
467 		component->max_silence,
468 		(int)ceil(component->min_confidence * 100.0),
469 		jsgf_path);
470 
471 	return (char *)grammar.data;
472 }
473 
setup_grammars_unimrcp(struct input_component * component,switch_core_session_t * session,iks * input,const struct xmpp_error ** stanza_error,const char ** error_detail)474 static char *setup_grammars_unimrcp(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail)
475 {
476 	iks *grammar_tag;
477 	switch_asr_handle_t *ah;
478 	switch_stream_handle_t grammar_uri_list = { 0 };
479 	SWITCH_STANDARD_STREAM(grammar_uri_list);
480 
481 	/* unlock handler mutex, otherwise deadlock will happen when switch_ivr_detect_speech_init adds a new media bug */
482 	switch_mutex_unlock(component->handler->mutex);
483 	ah = switch_core_session_alloc(session, sizeof(*ah));
484 	if (switch_ivr_detect_speech_init(session, component->recognizer, "", ah) != SWITCH_STATUS_SUCCESS) {
485 		switch_mutex_lock(component->handler->mutex);
486 		*stanza_error = STANZA_ERROR_INTERNAL_SERVER_ERROR;
487 		*error_detail = "Failed to initialize recognizer";
488 		switch_safe_free(grammar_uri_list.data);
489 		return NULL;
490 	}
491 	switch_mutex_lock(component->handler->mutex);
492 
493 	/* handle input config */
494 	switch_core_asr_text_param(ah, "start-input-timers", component->start_timers ? "true" : "false");
495 	switch_core_asr_text_param(ah, "confidence-threshold", switch_core_sprintf(RAYO_POOL(component), "%f", component->min_confidence));
496 	switch_core_asr_text_param(ah, "sensitivity-level", switch_core_sprintf(RAYO_POOL(component), "%f", component->sensitivity));
497 	if (component->initial_timeout > 0) {
498 		switch_core_asr_text_param(ah, "no-input-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->initial_timeout));
499 	}
500 	if (component->max_silence > 0) {
501 		switch_core_asr_text_param(ah, "speech-complete-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->max_silence));
502 		switch_core_asr_text_param(ah, "speech-incomplete-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->max_silence));
503 	}
504 	if (!zstr(component->language)) {
505 		switch_core_asr_text_param(ah, "speech-language", component->language);
506 	}
507 	if (!strcmp(iks_find_attrib_soft(input, "mode"), "any") || !strcmp(iks_find_attrib_soft(input, "mode"), "dtmf")) {
508 		/* set dtmf params */
509 		if (component->inter_digit_timeout > 0) {
510 			switch_core_asr_text_param(ah, "dtmf-interdigit-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->inter_digit_timeout));
511 		}
512 		if (component->term_digit) {
513 			switch_core_asr_text_param(ah, "dtmf-term-char", switch_core_sprintf(RAYO_POOL(component), "%c", component->term_digit));
514 		}
515 	}
516 
517 	/* override input configs w/ custom headers */
518 	{
519 		iks *header = NULL;
520 		for (header = iks_find(input, "header"); header; header = iks_next_tag(header)) {
521 			if (!strcmp("header", iks_name(header))) {
522 				const char *name = iks_find_attrib_soft(header, "name");
523 				const char *value = iks_find_attrib_soft(header, "value");
524 				if (!zstr(name) && !zstr(value)) {
525 					switch_core_asr_text_param(ah, (char *)name, value);
526 				}
527 			}
528 		}
529 	}
530 
531 	switch_core_asr_text_param(ah, "start-recognize", "false");
532 	switch_core_asr_text_param(ah, "define-grammar", "true");
533 	for (grammar_tag = iks_find(input, "grammar"); grammar_tag; grammar_tag = iks_next_tag(grammar_tag)) {
534 		const char *grammar_name;
535 		iks *grammar_cdata;
536 		const char *grammar;
537 
538 		/* is this a grammar? */
539 		if (strcmp("grammar", iks_name(grammar_tag))) {
540 			continue;
541 		}
542 
543 		if (!zstr(iks_find_attrib_soft(grammar_tag, "content-type"))) {
544 			/* get the srgs contained in this grammar */
545 			if (!(grammar_cdata = iks_child(grammar_tag)) || iks_type(grammar_cdata) != IKS_CDATA) {
546 				*stanza_error = STANZA_ERROR_BAD_REQUEST;
547 				*error_detail = "Missing grammar";
548 				switch_safe_free(grammar_uri_list.data);
549 				return NULL;
550 			}
551 			grammar = switch_core_sprintf(RAYO_POOL(component), "inline:%s", iks_cdata(grammar_cdata));
552 		} else {
553 			/* Grammar is at a URL */
554 			grammar = iks_find_attrib_soft(grammar_tag, "url");
555 			if (zstr(grammar)) {
556 				*stanza_error = STANZA_ERROR_BAD_REQUEST;
557 				*error_detail = "Missing grammar";
558 				switch_safe_free(grammar_uri_list.data);
559 				return NULL;
560 			}
561 			if (strncasecmp(grammar, "http", 4) && strncasecmp(grammar, "file", 4)) {
562 				*stanza_error = STANZA_ERROR_BAD_REQUEST;
563 				*error_detail = "Bad URL";
564 				switch_safe_free(grammar_uri_list.data);
565 				return NULL;
566 			}
567 		}
568 		grammar_name = switch_core_sprintf(RAYO_POOL(component), "grammar-%d", rayo_actor_seq_next(RAYO_ACTOR(component)));
569 
570 		/* DEFINE-GRAMMAR */
571 		/* unlock handler mutex, otherwise deadlock will happen if switch_ivr_detect_speech_load_grammar removes the media bug */
572 		switch_mutex_unlock(component->handler->mutex);
573 		if (switch_ivr_detect_speech_load_grammar(session, grammar, grammar_name) != SWITCH_STATUS_SUCCESS) {
574 			switch_mutex_lock(component->handler->mutex);
575 			*stanza_error = STANZA_ERROR_INTERNAL_SERVER_ERROR;
576 			*error_detail = "Failed to load grammar";
577 			switch_safe_free(grammar_uri_list.data);
578 			return NULL;
579 		}
580 		switch_mutex_lock(component->handler->mutex);
581 
582 		/* add grammar to uri-list */
583 		grammar_uri_list.write_function(&grammar_uri_list, "session:%s\r\n", grammar_name);
584 	}
585 	switch_core_asr_text_param(ah, "start-recognize", "true");
586 	switch_core_asr_text_param(ah, "define-grammar", "false");
587 
588 	return (char *)grammar_uri_list.data;
589 }
590 
setup_grammars_unknown(struct input_component * component,switch_core_session_t * session,iks * input,const struct xmpp_error ** stanza_error,const char ** error_detail)591 static char *setup_grammars_unknown(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail)
592 {
593 	switch_stream_handle_t grammar = { 0 };
594 	SWITCH_STANDARD_STREAM(grammar);
595 	grammar.write_function(&grammar, "%s", iks_find_cdata(input, "grammar"));
596 	return (char *)grammar.data;
597 }
598 
599 /**
600  * Start call input on voice resource
601  */
start_call_voice_input(struct input_component * component,switch_core_session_t * session,iks * input,iks * iq,int barge_in)602 static iks *start_call_voice_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in)
603 {
604 	struct input_handler *handler = component->handler;
605 	char *grammar = NULL;
606 	const struct xmpp_error *stanza_error = NULL;
607 	const char *error_detail = NULL;
608 
609 	if (component->speech_mode && handler->voice_component) {
610 		/* don't allow multi voice input */
611 		RAYO_RELEASE(component);
612 		RAYO_DESTROY(component);
613 		return iks_new_error_detailed(iq, STANZA_ERROR_CONFLICT, "Multiple voice input is not allowed");
614 	}
615 
616 	handler->voice_component = component;
617 
618 	if (zstr(component->recognizer)) {
619 		component->recognizer = globals.default_recognizer;
620 	}
621 
622 	/* if recognition engine is different, we can't handle this request */
623 	if (!zstr(handler->last_recognizer) && strcmp(component->recognizer, handler->last_recognizer)) {
624 		handler->voice_component = NULL;
625 		RAYO_RELEASE(component);
626 		RAYO_DESTROY(component);
627 		return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Must use the same recognizer for the entire call");
628 	} else if (zstr(handler->last_recognizer)) {
629 		handler->last_recognizer = switch_core_session_strdup(session, component->recognizer);
630 	}
631 
632 	if (!strcmp(component->recognizer, "pocketsphinx")) {
633 		grammar = setup_grammars_pocketsphinx(component, session, input, &stanza_error, &error_detail);
634 	} else if (!strncmp(component->recognizer, "unimrcp", strlen("unimrcp"))) {
635 		grammar = setup_grammars_unimrcp(component, session, input, &stanza_error, &error_detail);
636 	} else {
637 		grammar = setup_grammars_unknown(component, session, input, &stanza_error, &error_detail);
638 	}
639 
640 	if (!grammar) {
641 		handler->voice_component = NULL;
642 		RAYO_RELEASE(component);
643 		RAYO_DESTROY(component);
644 		return iks_new_error_detailed(iq, stanza_error, error_detail);
645 	}
646 
647 	/* acknowledge command */
648 	rayo_component_send_start(RAYO_COMPONENT(component), iq);
649 
650 	/* start speech detection */
651 	switch_channel_set_variable(switch_core_session_get_channel(session), "fire_asr_events", "true");
652 	/* unlock handler mutex, otherwise deadlock will happen if switch_ivr_detect_speech adds a media bug */
653 	switch_mutex_unlock(handler->mutex);
654 	if (switch_ivr_detect_speech(session, component->recognizer, grammar, "mod_rayo_grammar", "", NULL) != SWITCH_STATUS_SUCCESS) {
655 		switch_mutex_lock(handler->mutex);
656 		handler->voice_component = NULL;
657 		rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_ERROR);
658 	} else {
659 		switch_mutex_lock(handler->mutex);
660 	}
661 	switch_safe_free(grammar);
662 
663 	return NULL;
664 }
665 
666 /**
667  * Start call input on DTMF resource
668  */
start_call_dtmf_input(struct input_component * component,switch_core_session_t * session,iks * input,iks * iq,int barge_in)669 static iks *start_call_dtmf_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in)
670 {
671 	/* parse the grammar */
672 	if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
673 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to parse grammar body\n");
674 		RAYO_RELEASE(component);
675 		RAYO_DESTROY(component);
676 		return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Failed to parse grammar body");
677 	}
678 
679 	component->last_digit_time = switch_micro_time_now();
680 
681 	/* acknowledge command */
682 	rayo_component_send_start(RAYO_COMPONENT(component), iq);
683 
684 	/* start dtmf input detection */
685 	switch_core_hash_insert(component->handler->dtmf_components, RAYO_JID(component), component);
686 
687 	return NULL;
688 }
689 
690 /**
691  * Start call input for the given component
692  * @param component the input or prompt component
693  * @param session the session
694  * @param input the input request
695  * @param iq the original input/prompt request
696  */
start_call_input(struct input_component * component,switch_core_session_t * session,iks * input,iks * iq,int barge_in)697 static iks *start_call_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in)
698 {
699 	iks *result = NULL;
700 
701 	/* set up input component for new detection */
702 	struct input_handler *handler = (struct input_handler *)switch_channel_get_private(switch_core_session_get_channel(session), RAYO_INPUT_COMPONENT_PRIVATE_VAR);
703 	if (!handler) {
704 		/* create input component */
705 		handler = switch_core_session_alloc(session, sizeof(*handler));
706 		switch_mutex_init(&handler->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
707 		switch_core_hash_init(&handler->dtmf_components);
708 		switch_channel_set_private(switch_core_session_get_channel(session), RAYO_INPUT_COMPONENT_PRIVATE_VAR, handler);
709 		handler->last_recognizer = "";
710 
711 		/* fire up media bug to monitor lifecycle */
712 		if (switch_core_media_bug_add(session, "rayo_input_component", NULL, input_handler_bug_callback, handler, 0, SMBF_READ_REPLACE, &handler->bug) != SWITCH_STATUS_SUCCESS) {
713 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to create input handler media bug\n");
714 			RAYO_RELEASE(component);
715 			RAYO_DESTROY(component);
716 			return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create input handler media bug");
717 		}
718 	}
719 
720 	switch_mutex_lock(handler->mutex);
721 
722 	if (!handler->dtmf_components) {
723 		/* handler bug was destroyed */
724 		switch_mutex_unlock(handler->mutex);
725 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Input handler media bug is closed\n");
726 		RAYO_RELEASE(component);
727 		RAYO_DESTROY(component);
728 		return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Input handler media bug is closed\n");
729 	}
730 
731 	component->grammar = NULL;
732 	component->num_digits = 0;
733 	component->digits[0] = '\0';
734 	component->stop = 0;
735 	component->initial_timeout = iks_find_int_attrib(input, "initial-timeout");
736 	component->inter_digit_timeout = iks_find_int_attrib(input, "inter-digit-timeout");
737 	component->max_silence = iks_find_int_attrib(input, "max-silence");
738 	component->min_confidence = iks_find_decimal_attrib(input, "min-confidence");
739 	component->sensitivity = iks_find_decimal_attrib(input, "sensitivity");
740 	component->barge_event = iks_find_bool_attrib(input, "barge-event");
741 	component->start_timers = iks_find_bool_attrib(input, "start-timers");
742 	component->term_digit = iks_find_char_attrib(input, "terminator");
743 	component->recognizer = switch_core_strdup(RAYO_POOL(component), iks_find_attrib_soft(input, "recognizer"));
744 	component->language = switch_core_strdup(RAYO_POOL(component), iks_find_attrib_soft(input, "language"));
745 	component->handler = handler;
746 	component->speech_mode = strcmp(iks_find_attrib_soft(input, "mode"), "dtmf");
747 
748 	if (component->speech_mode) {
749 		result = start_call_voice_input(component, session, input, iq, barge_in);
750 	} else {
751 		result = start_call_dtmf_input(component, session, input, iq, barge_in);
752 	}
753 
754 	switch_mutex_unlock(handler->mutex);
755 
756 	return result;
757 }
758 
759 /**
760  * Create input component id for session.
761  * @param session requesting component
762  * @param input request
763  * @return the ID
764  */
create_input_component_id(switch_core_session_t * session,iks * input)765 static char *create_input_component_id(switch_core_session_t *session, iks *input)
766 {
767 	const char *mode = "unk";
768 	if (input) {
769 		mode = iks_find_attrib_soft(input, "mode");
770 		if (!strcmp(mode, "dtmf")) {
771 			return NULL;
772 		}
773 		if (!strcmp(mode, "any")) {
774 			mode = "voice";
775 		}
776 	}
777 	return switch_core_session_sprintf(session, "%s-input-%s", switch_core_session_get_uuid(session), mode);
778 }
779 
780 /**
781  * Release any resources consumed by this input component
782  */
input_component_cleanup(struct rayo_actor * component)783 static void input_component_cleanup(struct rayo_actor *component)
784 {
785 	if (INPUT_COMPONENT(component)->speech_mode) {
786 		switch_core_session_t *session = switch_core_session_locate(component->parent->id);
787 		if (session) {
788 			switch_ivr_stop_detect_speech(session);
789 			switch_core_session_rwunlock(session);
790 		}
791 	}
792 }
793 
794 /**
795  * Start execution of input component
796  */
start_call_input_component(struct rayo_actor * call,struct rayo_message * msg,void * session_data)797 static iks *start_call_input_component(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
798 {
799 	iks *iq = msg->payload;
800 	switch_core_session_t *session = (switch_core_session_t *)session_data;
801 	iks *input = iks_find(iq, "input");
802 	char *component_id = create_input_component_id(session, input);
803 	switch_memory_pool_t *pool = NULL;
804 	struct input_component *input_component = NULL;
805 	const char *error = NULL;
806 
807 	/* Start CPA */
808 	if (!strcmp(iks_find_attrib_soft(input, "mode"), "cpa")) {
809 		return rayo_cpa_component_start(call, msg, session_data);
810 	}
811 
812 	/* start input */
813 	if (!validate_call_input(input, &error)) {
814 		return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, error);
815 	}
816 
817 	switch_core_new_memory_pool(&pool);
818 	input_component = switch_core_alloc(pool, sizeof(*input_component));
819 	input_component = INPUT_COMPONENT(rayo_component_init_cleanup(RAYO_COMPONENT(input_component), pool, RAT_CALL_COMPONENT, "input", component_id, call, iks_find_attrib(iq, "from"), input_component_cleanup));
820 	if (!input_component) {
821 		switch_core_destroy_memory_pool(&pool);
822 		return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create input entity");
823 	}
824 	return start_call_input(input_component, session, input, iq, 0);
825 }
826 
827 /**
828  * Stop execution of input component
829  */
stop_call_input_component(struct rayo_actor * component,struct rayo_message * msg,void * data)830 static iks *stop_call_input_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
831 {
832 	iks *iq = msg->payload;
833 	struct input_component *input_component = INPUT_COMPONENT(component);
834 
835 	if (input_component && !input_component->stop) {
836 		switch_core_session_t *session = switch_core_session_locate(component->parent->id);
837 		if (session) {
838 			switch_mutex_lock(input_component->handler->mutex);
839 			input_component->stop = 1;
840 			if (input_component->speech_mode) {
841 				switch_mutex_unlock(input_component->handler->mutex);
842 				switch_ivr_stop_detect_speech(session);
843 				switch_mutex_lock(input_component->handler->mutex);
844 				rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
845 			}
846 			switch_mutex_unlock(input_component->handler->mutex);
847 			switch_core_session_rwunlock(session);
848 		}
849 	}
850 	return iks_new_iq_result(iq);
851 }
852 
853 /**
854  * Start input component timers
855  */
start_timers_call_input_component(struct rayo_actor * component,struct rayo_message * msg,void * data)856 static iks *start_timers_call_input_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
857 {
858 	iks *iq = msg->payload;
859 	struct input_component *input_component = INPUT_COMPONENT(component);
860 	if (input_component) {
861 		switch_core_session_t *session = switch_core_session_locate(component->parent->id);
862 		if (session) {
863 			switch_mutex_lock(input_component->handler->mutex);
864 			if (input_component->speech_mode) {
865 				switch_mutex_unlock(input_component->handler->mutex);
866 				switch_ivr_detect_speech_start_input_timers(session);
867 				switch_mutex_lock(input_component->handler->mutex);
868 			} else {
869 				input_component->last_digit_time = switch_micro_time_now();
870 				input_component->start_timers = 1;
871 			}
872 			switch_mutex_unlock(input_component->handler->mutex);
873 			switch_core_session_rwunlock(session);
874 		}
875 	}
876 	return iks_new_iq_result(iq);
877 }
878 
879 /**
880  * Get text / error from result
881  */
get_detected_speech_result_text(cJSON * result_json,double * confidence,const char ** error_text)882 static const char *get_detected_speech_result_text(cJSON *result_json, double *confidence, const char **error_text)
883 {
884 	const char *result_text = NULL;
885 	const char *text = cJSON_GetObjectCstr(result_json, "text");
886 	if (confidence) {
887 		*confidence = 0.0;
888 	}
889 	if (!zstr(text)) {
890 		cJSON *json_confidence = cJSON_GetObjectItem(result_json, "confidence");
891 		if (json_confidence && json_confidence->valuedouble > 0.0) {
892 			*confidence = json_confidence->valuedouble;
893 		} else {
894 			*confidence = 0.99;
895 		}
896 		result_text = text;
897 	} else if (error_text) {
898 		*error_text = cJSON_GetObjectCstr(result_json, "error");
899 	}
900 	return result_text;
901 }
902 
903 /**
904  * Handle speech detection event
905  */
on_detected_speech_event(switch_event_t * event)906 static void on_detected_speech_event(switch_event_t *event)
907 {
908 	const char *speech_type = switch_event_get_header(event, "Speech-Type");
909 	char *event_str = NULL;
910 	const char *uuid = switch_event_get_header(event, "Unique-ID");
911 	switch_event_serialize(event, &event_str, SWITCH_FALSE);
912 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s\n", event_str);
913 	if (!speech_type || !uuid) {
914 		return;
915 	}
916 
917 	if (!strcasecmp("detected-speech", speech_type)) {
918 		char *component_id = switch_mprintf("%s-input-voice", uuid);
919 		struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
920 
921 		switch_safe_free(component_id);
922 		if (component) {
923 			const char *result = switch_event_get_body(event);
924 
925 			switch_mutex_lock(INPUT_COMPONENT(component)->handler->mutex);
926 			INPUT_COMPONENT(component)->handler->voice_component = NULL;
927 			switch_mutex_unlock(INPUT_COMPONENT(component)->handler->mutex);
928 
929 			if (zstr(result)) {
930 				rayo_component_send_complete(component, INPUT_NOMATCH);
931 			} else {
932 				if (result[0] == '{') {
933 					// internal FS JSON format
934 					cJSON *json_result = cJSON_Parse(result);
935 					if (json_result) {
936 						// examine result to determine what happened
937 						double confidence = 0.0;
938 						const char *error_text = NULL;
939 						const char *result_text = NULL;
940 						result_text = get_detected_speech_result_text(json_result, &confidence, &error_text);
941 						if (!zstr(result_text)) {
942 							// got result... send as NLSML
943 							iks *result = nlsml_create_match(result_text, NULL, "speech", (int)(confidence * 100.0));
944 							/* notify of match */
945 							switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "MATCH = %s\n", result_text);
946 							send_match_event(RAYO_COMPONENT(component), result);
947 							iks_delete(result);
948 						} else if (zstr(error_text)) {
949 							// unknown error
950 							switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "No matching text nor error in result: %s!\n", result);
951 							rayo_component_send_complete(component, INPUT_NOMATCH);
952 						} else if (!strcmp(error_text, "no_input")) {
953 							// no input error
954 							rayo_component_send_complete(component, INPUT_NOINPUT);
955 						} else if (!strcmp(error_text, "no_match")) {
956 							// no match error
957 							rayo_component_send_complete(component, INPUT_NOMATCH);
958 						} else {
959 							// generic error
960 							iks *response = rayo_component_create_complete_event(component, COMPONENT_COMPLETE_ERROR);
961 							iks *error = NULL;
962 							if ((error = iks_find(response, "complete"))) {
963 								if ((error = iks_find(error, "error"))) {
964 									iks_insert_cdata(error, error_text, strlen(error_text));
965 								}
966 							}
967 							rayo_component_send_complete_event(component, response);
968 						}
969 						cJSON_Delete(json_result);
970 					} else {
971 						// failed to parse JSON result
972 						switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "Failed to parse JSON result: %s!\n", result);
973 						rayo_component_send_complete(component, INPUT_NOMATCH);
974 					}
975 				} else if (strchr(result, '<')) {
976 					/* got an XML result */
977 					enum nlsml_match_type match_type = nlsml_parse(result, uuid);
978 					switch (match_type) {
979 					case NMT_NOINPUT:
980 						rayo_component_send_complete(component, INPUT_NOINPUT);
981 						break;
982 					case NMT_MATCH: {
983 						iks *result_xml = nlsml_normalize(result);
984 						send_match_event(RAYO_COMPONENT(component), result_xml);
985 						iks_delete(result_xml);
986 						break;
987 					}
988 					case NMT_BAD_XML:
989 						switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "Failed to parse NLSML result: %s!\n", result);
990 						rayo_component_send_complete(component, INPUT_NOMATCH);
991 						break;
992 					case NMT_NOMATCH:
993 						rayo_component_send_complete(component, INPUT_NOMATCH);
994 						break;
995 					default:
996 						switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_CRIT, "Unknown NLSML match type: %i, %s!\n", match_type, result);
997 						rayo_component_send_complete(component, INPUT_NOMATCH);
998 						break;
999 					}
1000 				} else if (strstr(result, "002")) {
1001 					/* Completion-Cause: 002 no-input-timeout */
1002 					rayo_component_send_complete(component, INPUT_NOINPUT);
1003 				} else if (strstr(result, "004") || strstr(result, "005") || strstr(result, "006") || strstr(result, "009") || strstr(result, "010")) {
1004 					/* Completion-Cause: 004 gram-load-failure */
1005 					/* Completion-Cause: 005 gram-comp-failure */
1006 					/* Completion-Cause: 006 error */
1007 					/* Completion-Cause: 009 uri-failure */
1008 					/* Completion-Cause: 010 language-unsupported */
1009 					iks *response = rayo_component_create_complete_event(component, COMPONENT_COMPLETE_ERROR);
1010 					const char *error_reason = switch_event_get_header(event, "ASR-Completion-Reason");
1011 					if (!zstr(error_reason)) {
1012 						iks *error;
1013 						if ((error = iks_find(response, "complete"))) {
1014 							if ((error = iks_find(error, "error"))) {
1015 								iks_insert_cdata(error, error_reason, strlen(error_reason));
1016 							}
1017 						}
1018 					}
1019 					rayo_component_send_complete_event(component, response);
1020 				} else {
1021 					/* assume no match */
1022 					/* Completion-Cause: 001 no-match */
1023 					/* Completion-Cause: 003 recognition-timeout */
1024 					/* Completion-Cause: 007 speech-too-early */
1025 					/* Completion-Cause: 008 too-much-speech-timeout */
1026 					rayo_component_send_complete(component, INPUT_NOMATCH);
1027 				}
1028 			}
1029 			RAYO_RELEASE(component);
1030 		}
1031 	} else if (!strcasecmp("begin-speaking", speech_type)) {
1032 		char *component_id = switch_mprintf("%s-input-voice", uuid);
1033 		struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
1034 		switch_safe_free(component_id);
1035 		if (component && INPUT_COMPONENT(component)->barge_event) {
1036 			send_barge_event(component);
1037 		}
1038 		RAYO_RELEASE(component);
1039 	} else if (!strcasecmp("closed", speech_type)) {
1040 		char *component_id = switch_mprintf("%s-input-voice", uuid);
1041 		struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
1042 		switch_safe_free(component_id);
1043 		if (component) {
1044 			char *channel_state = switch_event_get_header(event, "Channel-State");
1045 			switch_mutex_lock(INPUT_COMPONENT(component)->handler->mutex);
1046 			INPUT_COMPONENT(component)->handler->voice_component = NULL;
1047 			switch_mutex_unlock(INPUT_COMPONENT(component)->handler->mutex);
1048 			switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Recognizer closed\n");
1049 			if (channel_state && !strcmp("CS_HANGUP", channel_state)) {
1050 				rayo_component_send_complete(component, COMPONENT_COMPLETE_HANGUP);
1051 			} else {
1052 				/* shouldn't get here... */
1053 				rayo_component_send_complete(component, COMPONENT_COMPLETE_ERROR);
1054 			}
1055 			RAYO_RELEASE(component);
1056 		}
1057 	}
1058 	switch_safe_free(event_str);
1059 }
1060 
1061 /**
1062  * Process module XML configuration
1063  * @param pool memory pool to allocate from
1064  * @param config_file to use
1065  * @return SWITCH_STATUS_SUCCESS on successful configuration
1066  */
do_config(switch_memory_pool_t * pool,const char * config_file)1067 static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
1068 {
1069 	switch_xml_t cfg, xml;
1070 
1071 	/* set defaults */
1072 	globals.default_recognizer = "pocketsphinx";
1073 
1074 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Configuring module\n");
1075 	if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
1076 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
1077 		return SWITCH_STATUS_TERM;
1078 	}
1079 
1080 	/* get params */
1081 	{
1082 		switch_xml_t settings = switch_xml_child(cfg, "input");
1083 		if (settings) {
1084 			switch_xml_t param;
1085 			for (param = switch_xml_child(settings, "param"); param; param = param->next) {
1086 				const char *var = switch_xml_attr_soft(param, "name");
1087 				const char *val = switch_xml_attr_soft(param, "value");
1088 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "param: %s = %s\n", var, val);
1089 				if (!strcasecmp(var, "default-recognizer")) {
1090 					if (!zstr(val)) {
1091 						globals.default_recognizer = switch_core_strdup(pool, val);
1092 					}
1093 				} else {
1094 					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported param: %s\n", var);
1095 				}
1096 			}
1097 		}
1098 	}
1099 
1100 	switch_xml_free(xml);
1101 
1102 	return SWITCH_STATUS_SUCCESS;
1103 }
1104 
1105 /**
1106  * Initialize input component
1107  * @param module_interface
1108  * @param pool memory pool to allocate from
1109  * @param config_file to use
1110  * @return SWITCH_STATUS_SUCCESS if successful
1111  */
rayo_input_component_load(switch_loadable_module_interface_t ** module_interface,switch_memory_pool_t * pool,const char * config_file)1112 switch_status_t rayo_input_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
1113 {
1114 	if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
1115 		return SWITCH_STATUS_TERM;
1116 	}
1117 
1118 	srgs_init();
1119 	nlsml_init();
1120 
1121 	globals.parser = srgs_parser_new(NULL);
1122 
1123 	rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_INPUT_NS":input", start_call_input_component);
1124 	rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "input", "set:"RAYO_EXT_NS":stop", stop_call_input_component);
1125 	rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "input", "set:"RAYO_INPUT_NS":start-timers", start_timers_call_input_component);
1126 	switch_event_bind("rayo_input_component", SWITCH_EVENT_DETECTED_SPEECH, SWITCH_EVENT_SUBCLASS_ANY, on_detected_speech_event, NULL);
1127 
1128 	return rayo_cpa_component_load(module_interface, pool, config_file);
1129 }
1130 
1131 /**
1132  * Shutdown input component
1133  * @return SWITCH_STATUS_SUCCESS if successful
1134  */
rayo_input_component_shutdown(void)1135 switch_status_t rayo_input_component_shutdown(void)
1136 {
1137 	switch_event_unbind_callback(on_detected_speech_event);
1138 
1139 	if (globals.parser) {
1140 		srgs_parser_destroy(globals.parser);
1141 	}
1142 	srgs_destroy();
1143 	nlsml_destroy();
1144 
1145 	rayo_cpa_component_shutdown();
1146 
1147 	return SWITCH_STATUS_SUCCESS;
1148 }
1149 
1150 /* For Emacs:
1151  * Local Variables:
1152  * mode:c
1153  * indent-tabs-mode:t
1154  * tab-width:4
1155  * c-basic-offset:4
1156  * End:
1157  * For VIM:
1158  * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
1159  */
1160 
1161