| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2013-04-22 14:58:53 +00:00
										 |  |  |  * Asterisk -- An open source telephony toolkit. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2012 - 2013, Digium, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * David M. Lee, II <dlee@digium.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * See http://www.asterisk.org for more information about
 | 
					
						
							|  |  |  |  * the Asterisk project. Please do not directly contact | 
					
						
							|  |  |  |  * any of the maintainers of this project for assistance; | 
					
						
							|  |  |  |  * the project provides a web site, mailing lists and IRC | 
					
						
							|  |  |  |  * channels for your use. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software, distributed under the terms of | 
					
						
							|  |  |  |  * the GNU General Public License Version 2. See the LICENSE file | 
					
						
							|  |  |  |  * at the top of the source tree. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \file
 | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  |  * \brief /api-docs/events.{format} implementation- WebSocket resource | 
					
						
							| 
									
										
										
										
											2013-04-22 14:58:53 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * \author David M. Lee, II <dlee@digium.com> | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "asterisk.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ASTERISK_FILE_VERSION(__FILE__, "$Revision$") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | #include "asterisk/astobj2.h"
 | 
					
						
							|  |  |  | #include "asterisk/stasis_app.h"
 | 
					
						
							| 
									
										
										
										
											2013-04-22 14:58:53 +00:00
										 |  |  | #include "resource_events.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | /*! Number of buckets for the Stasis application hash table. Remember to keep it
 | 
					
						
							|  |  |  |  *  a prime number! | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #define APPS_NUM_BUCKETS 7
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*! \brief A connection to the event WebSocket */ | 
					
						
							|  |  |  | struct event_session { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 	struct ast_ari_websocket_session *ws_session; | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 	struct ao2_container *websocket_apps; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Explicitly shutdown a session. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * An explicit shutdown is necessary, since stasis-app has a reference to this | 
					
						
							|  |  |  |  * session. We also need to be sure to null out the \c ws_session field, since | 
					
						
							|  |  |  |  * the websocket is about to go away. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * \param session Session info struct. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void session_shutdown(struct event_session *session) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |         struct ao2_iterator i; | 
					
						
							|  |  |  | 	char *app; | 
					
						
							|  |  |  | 	SCOPED_AO2LOCK(lock, session); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	i = ao2_iterator_init(session->websocket_apps, 0); | 
					
						
							|  |  |  | 	while ((app = ao2_iterator_next(&i))) { | 
					
						
							|  |  |  | 		stasis_app_unregister(app); | 
					
						
							|  |  |  | 		ao2_cleanup(app); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ao2_iterator_destroy(&i); | 
					
						
							|  |  |  | 	ao2_cleanup(session->websocket_apps); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	session->websocket_apps = NULL; | 
					
						
							|  |  |  | 	session->ws_session = NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void session_dtor(void *obj) | 
					
						
							| 
									
										
										
										
											2013-04-22 14:58:53 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | #ifdef AST_DEVMODE /* Avoid unused variable warning */
 | 
					
						
							|  |  |  | 	struct event_session *session = obj; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* session_shutdown should have been called before */ | 
					
						
							|  |  |  | 	ast_assert(session->ws_session == NULL); | 
					
						
							|  |  |  | 	ast_assert(session->websocket_apps == NULL); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void session_cleanup(struct event_session *session) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	session_shutdown(session); | 
					
						
							|  |  |  | 	ao2_cleanup(session); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct event_session *session_create( | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 	struct ast_ari_websocket_session *ws_session) | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(struct event_session *, session, NULL, ao2_cleanup); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	session = ao2_alloc(sizeof(*session), session_dtor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	session->ws_session = ws_session; | 
					
						
							|  |  |  | 	session->websocket_apps = | 
					
						
							|  |  |  | 		ast_str_container_alloc(APPS_NUM_BUCKETS); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!session->websocket_apps) { | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_ref(session, +1); | 
					
						
							|  |  |  | 	return session; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Callback handler for Stasis application messages. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void app_handler(void *data, const char *app_name, | 
					
						
							|  |  |  | 			struct ast_json *message) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct event_session *session = data; | 
					
						
							|  |  |  | 	int res; | 
					
						
							| 
									
										
										
										
											2013-07-26 17:42:08 +00:00
										 |  |  | 	const char *msg_type = S_OR( | 
					
						
							|  |  |  | 		ast_json_string_get(ast_json_object_get(message, "type")), | 
					
						
							|  |  |  | 		""); | 
					
						
							|  |  |  | 	const char *msg_application = S_OR( | 
					
						
							|  |  |  | 		ast_json_string_get(ast_json_object_get(message, "application")), | 
					
						
							|  |  |  | 		""); | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  |   | 
					
						
							| 
									
										
										
										
											2013-07-26 17:42:08 +00:00
										 |  |  | 	/* Determine if we've been replaced */ | 
					
						
							|  |  |  | 	if (strcmp(msg_type, "ApplicationReplaced") == 0 && | 
					
						
							|  |  |  | 		strcmp(msg_application, app_name) == 0) { | 
					
						
							|  |  |  | 		ao2_find(session->websocket_apps, msg_application, | 
					
						
							|  |  |  | 			OBJ_UNLINK | OBJ_NODATA); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	res = ast_json_object_set(message, "application", | 
					
						
							|  |  |  | 				  ast_json_string_create(app_name)); | 
					
						
							|  |  |  | 	if(res != 0) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ao2_lock(session); | 
					
						
							|  |  |  | 	if (session->ws_session) { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 		ast_ari_websocket_session_write(session->ws_session, message); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	ao2_unlock(session); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*!
 | 
					
						
							|  |  |  |  * \brief Register for all of the apps given. | 
					
						
							|  |  |  |  * \param session Session info struct. | 
					
						
							|  |  |  |  * \param app_list Comma seperated list of app names to register. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int session_register_apps(struct event_session *session, | 
					
						
							|  |  |  | 				 const char *app_list) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(char *, to_free, NULL, ast_free); | 
					
						
							|  |  |  | 	char *apps, *app_name; | 
					
						
							|  |  |  | 	SCOPED_AO2LOCK(lock, session); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_assert(session->ws_session != NULL); | 
					
						
							|  |  |  | 	ast_assert(session->websocket_apps != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!app_list) { | 
					
						
							|  |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	to_free = apps = ast_strdup(app_list); | 
					
						
							|  |  |  | 	if (!apps) { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 		ast_ari_websocket_session_write(session->ws_session, ast_ari_oom_json()); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		return -1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	while ((app_name = strsep(&apps, ","))) { | 
					
						
							|  |  |  | 		if (ast_str_container_add(session->websocket_apps, app_name)) { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 			ast_ari_websocket_session_write(session->ws_session, ast_ari_oom_json()); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 			return -1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		stasis_app_register(app_name, app_handler, session); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | void ast_ari_websocket_event_websocket(struct ast_ari_websocket_session *ws_session, | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 	struct ast_variable *headers, | 
					
						
							|  |  |  | 	struct ast_event_websocket_args *args) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	RAII_VAR(struct event_session *, session, NULL, session_cleanup); | 
					
						
							|  |  |  | 	struct ast_json *msg; | 
					
						
							|  |  |  | 	int res; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ast_debug(3, "/events WebSocket connection\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	session = session_create(ws_session); | 
					
						
							|  |  |  | 	if (!session) { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 		ast_ari_websocket_session_write(ws_session, ast_ari_oom_json()); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!args->app) { | 
					
						
							|  |  |  | 		RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		msg = ast_json_pack("{s: s, s: [s]}", | 
					
						
							| 
									
										
										
										
											2013-07-05 19:15:27 +00:00
										 |  |  | 			"type", "MissingParams", | 
					
						
							|  |  |  | 			"params", "app"); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		if (!msg) { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 			msg = ast_json_ref(ast_ari_oom_json()); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 		ast_ari_websocket_session_write(session->ws_session, msg); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res = session_register_apps(session, args->app); | 
					
						
							|  |  |  | 	if (res != 0) { | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 		ast_ari_websocket_session_write(ws_session, ast_ari_oom_json()); | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* We don't process any input, but we'll consume it waiting for EOF */ | 
					
						
							| 
									
										
										
										
											2013-07-27 23:11:02 +00:00
										 |  |  | 	while ((msg = ast_ari_websocket_session_read(ws_session))) { | 
					
						
							| 
									
										
										
										
											2013-07-03 16:32:00 +00:00
										 |  |  | 		ast_json_unref(msg); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2013-04-22 14:58:53 +00:00
										 |  |  | } |