2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2012 - 2013, Digium, Inc.
6 * David M. Lee, II <dlee@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Stasis application support.
23 * \author David M. Lee, II <dlee@digium.com>
25 * <code>res_stasis.so</code> brings together the various components of the
26 * Stasis application infrastructure.
28 * First, there's the Stasis application handler, stasis_app_exec(). This is
29 * called by <code>app_stasis.so</code> to give control of a channel to the
30 * Stasis application code from the dialplan.
32 * While a channel is in stasis_app_exec(), it has a \ref stasis_app_control
33 * object, which may be used to control the channel.
35 * To control the channel, commands may be sent to channel using
36 * stasis_app_send_command() and stasis_app_send_async_command().
38 * Alongside this, applications may be registered/unregistered using
39 * stasis_app_register()/stasis_app_unregister(). While a channel is in Stasis,
40 * events received on the channel's topic are converted to JSON and forwarded to
41 * the \ref stasis_app_cb. The application may also subscribe to the channel to
42 * continue to receive messages even after the channel has left Stasis, but it
43 * will not be able to control it.
45 * Given all the stuff that comes together in this module, it's been broken up
46 * into several pieces that are in <code>res/stasis/</code> and compiled into
47 * <code>res_stasis.so</code>.
51 <support_level>core</support_level>
56 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
58 #include "asterisk/astobj2.h"
59 #include "asterisk/callerid.h"
60 #include "asterisk/module.h"
61 #include "asterisk/stasis_app_impl.h"
62 #include "asterisk/stasis_channels.h"
63 #include "asterisk/stasis_bridges.h"
64 #include "asterisk/stasis_message_router.h"
65 #include "asterisk/strings.h"
66 #include "stasis/app.h"
67 #include "stasis/control.h"
68 #include "asterisk/core_unreal.h"
69 #include "asterisk/musiconhold.h"
70 #include "asterisk/causes.h"
71 #include "asterisk/stringfields.h"
72 #include "asterisk/bridge_after.h"
74 /*! Time to wait for a frame in the application */
75 #define MAX_WAIT_MS 200
78 * \brief Number of buckets for the Stasis application hash table. Remember to
79 * keep it a prime number!
81 #define APPS_NUM_BUCKETS 127
84 * \brief Number of buckets for the Stasis application hash table. Remember to
85 * keep it a prime number!
87 #define CONTROLS_NUM_BUCKETS 127
90 * \brief Number of buckets for the Stasis bridges hash table. Remember to
91 * keep it a prime number!
93 #define BRIDGES_NUM_BUCKETS 127
96 * \brief Stasis application container.
98 struct ao2_container *apps_registry;
100 struct ao2_container *app_controls;
102 struct ao2_container *app_bridges;
104 struct ao2_container *app_bridges_moh;
106 /*! AO2 hash function for \ref app */
107 static int app_hash(const void *obj, const int flags)
109 const struct app *app;
112 switch (flags & OBJ_SEARCH_MASK) {
116 case OBJ_SEARCH_OBJECT:
121 /* Hash can only work on something with a full key. */
125 return ast_str_hash(key);
128 /*! AO2 comparison function for \ref app */
129 static int app_compare(void *obj, void *arg, int flags)
131 const struct app *object_left = obj;
132 const struct app *object_right = arg;
133 const char *right_key = arg;
136 switch (flags & OBJ_SEARCH_MASK) {
137 case OBJ_SEARCH_OBJECT:
138 right_key = app_name(object_right);
141 cmp = strcmp(app_name(object_left), right_key);
143 case OBJ_SEARCH_PARTIAL_KEY:
145 * We could also use a partial key struct containing a length
146 * so strlen() does not get called for every comparison instead.
148 cmp = strncmp(app_name(object_left), right_key, strlen(right_key));
152 * What arg points to is specific to this traversal callback
153 * and has no special meaning to astobj2.
162 * At this point the traversal callback is identical to a sorted
168 /*! AO2 hash function for \ref stasis_app_control */
169 static int control_hash(const void *obj, const int flags)
171 const struct stasis_app_control *control;
174 switch (flags & OBJ_SEARCH_MASK) {
178 case OBJ_SEARCH_OBJECT:
180 key = stasis_app_control_get_channel_id(control);
183 /* Hash can only work on something with a full key. */
187 return ast_str_hash(key);
190 /*! AO2 comparison function for \ref stasis_app_control */
191 static int control_compare(void *obj, void *arg, int flags)
193 const struct stasis_app_control *object_left = obj;
194 const struct stasis_app_control *object_right = arg;
195 const char *right_key = arg;
198 switch (flags & OBJ_SEARCH_MASK) {
199 case OBJ_SEARCH_OBJECT:
200 right_key = stasis_app_control_get_channel_id(object_right);
203 cmp = strcmp(stasis_app_control_get_channel_id(object_left), right_key);
205 case OBJ_SEARCH_PARTIAL_KEY:
207 * We could also use a partial key struct containing a length
208 * so strlen() does not get called for every comparison instead.
210 cmp = strncmp(stasis_app_control_get_channel_id(object_left), right_key, strlen(right_key));
214 * What arg points to is specific to this traversal callback
215 * and has no special meaning to astobj2.
224 * At this point the traversal callback is identical to a sorted
230 static int cleanup_cb(void *obj, void *arg, int flags)
232 struct app *app = obj;
234 if (!app_is_finished(app)) {
238 ast_verb(1, "Shutting down application '%s'\n", app_name(app));
246 * \brief Clean up any old apps that we don't need any more.
248 static void cleanup(void)
250 ao2_callback(apps_registry, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK,
254 struct stasis_app_control *stasis_app_control_create(struct ast_channel *chan)
256 return control_create(chan);
259 struct stasis_app_control *stasis_app_control_find_by_channel(
260 const struct ast_channel *chan)
266 return stasis_app_control_find_by_channel_id(
267 ast_channel_uniqueid(chan));
270 struct stasis_app_control *stasis_app_control_find_by_channel_id(
271 const char *channel_id)
273 return ao2_find(app_controls, channel_id, OBJ_SEARCH_KEY);
276 /*! AO2 hash function for bridges container */
277 static int bridges_hash(const void *obj, const int flags)
279 const struct ast_bridge *bridge;
282 switch (flags & OBJ_SEARCH_MASK) {
286 case OBJ_SEARCH_OBJECT:
288 key = bridge->uniqueid;
291 /* Hash can only work on something with a full key. */
295 return ast_str_hash(key);
298 /*! AO2 comparison function for bridges container */
299 static int bridges_compare(void *obj, void *arg, int flags)
301 const struct ast_bridge *object_left = obj;
302 const struct ast_bridge *object_right = arg;
303 const char *right_key = arg;
306 switch (flags & OBJ_SEARCH_MASK) {
307 case OBJ_SEARCH_OBJECT:
308 right_key = object_right->uniqueid;
311 cmp = strcmp(object_left->uniqueid, right_key);
313 case OBJ_SEARCH_PARTIAL_KEY:
315 * We could also use a partial key struct containing a length
316 * so strlen() does not get called for every comparison instead.
318 cmp = strncmp(object_left->uniqueid, right_key, strlen(right_key));
322 * What arg points to is specific to this traversal callback
323 * and has no special meaning to astobj2.
332 * At this point the traversal callback is identical to a sorted
339 * Used with app_bridges_moh, provides links between bridges and existing music
340 * on hold channels that are being used with them.
342 struct stasis_app_bridge_moh_wrapper {
343 AST_DECLARE_STRING_FIELDS(
344 AST_STRING_FIELD(channel_id);
345 AST_STRING_FIELD(bridge_id);
349 static void stasis_app_bridge_moh_wrapper_destructor(void *obj)
351 struct stasis_app_bridge_moh_wrapper *wrapper = obj;
352 ast_string_field_free_memory(wrapper);
355 /*! AO2 hash function for the bridges moh container */
356 static int bridges_moh_hash_fn(const void *obj, const int flags)
358 const struct stasis_app_bridge_moh_wrapper *wrapper;
361 switch (flags & OBJ_SEARCH_MASK) {
365 case OBJ_SEARCH_OBJECT:
367 key = wrapper->bridge_id;
370 /* Hash can only work on something with a full key. */
374 return ast_str_hash(key);
377 static int bridges_moh_sort_fn(const void *obj_left, const void *obj_right, const int flags)
379 const struct stasis_app_bridge_moh_wrapper *left = obj_left;
380 const struct stasis_app_bridge_moh_wrapper *right = obj_right;
381 const char *right_key = obj_right;
384 switch (flags & OBJ_SEARCH_MASK) {
385 case OBJ_SEARCH_OBJECT:
386 right_key = right->bridge_id;
389 cmp = strcmp(left->bridge_id, right_key);
391 case OBJ_SEARCH_PARTIAL_KEY:
392 cmp = strncmp(left->bridge_id, right_key, strlen(right_key));
395 /* Sort can only work on something with a full or partial key. */
403 /*! Removes the bridge to music on hold channel link */
404 static void remove_bridge_moh(char *bridge_id)
406 ao2_find(app_bridges_moh, bridge_id, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);
410 /*! After bridge failure callback for moh channels */
411 static void moh_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
413 char *bridge_id = data;
415 remove_bridge_moh(bridge_id);
418 /*! After bridge callback for moh channels */
419 static void moh_after_bridge_cb(struct ast_channel *chan, void *data)
421 char *bridge_id = data;
423 remove_bridge_moh(bridge_id);
426 /*! Request a bridge MOH channel */
427 static struct ast_channel *prepare_bridge_moh_channel(void)
429 RAII_VAR(struct ast_format_cap *, cap, NULL, ast_format_cap_destroy);
430 struct ast_format format;
432 cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_NOLOCK);
437 ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0));
439 return ast_request("Announcer", cap, NULL, "ARI_MOH", NULL);
442 /*! Provides the moh channel with a thread so it can actually play its music */
443 static void *moh_channel_thread(void *data)
445 struct ast_channel *moh_channel = data;
447 while (!ast_safe_sleep(moh_channel, 1000)) {
450 ast_moh_stop(moh_channel);
451 ast_hangup(moh_channel);
458 * \brief Creates, pushes, and links a channel for playing music on hold to bridge
460 * \param bridge Which bridge this moh channel exists for
462 * \retval NULL if the channel could not be created, pushed, or linked
463 * \retval Reference to the channel on success
465 static struct ast_channel *bridge_moh_create(struct ast_bridge *bridge)
467 RAII_VAR(struct stasis_app_bridge_moh_wrapper *, new_wrapper, NULL, ao2_cleanup);
468 RAII_VAR(char *, bridge_id, ast_strdup(bridge->uniqueid), ast_free);
469 struct ast_channel *chan;
476 chan = prepare_bridge_moh_channel();
481 /* The after bridge callback assumes responsibility of the bridge_id. */
482 if (ast_bridge_set_after_callback(chan,
483 moh_after_bridge_cb, moh_after_bridge_cb_failed, bridge_id)) {
489 if (ast_unreal_channel_push_to_bridge(chan, bridge,
490 AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
495 new_wrapper = ao2_alloc_options(sizeof(*new_wrapper),
496 stasis_app_bridge_moh_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
502 if (ast_string_field_init(new_wrapper, 32)) {
506 ast_string_field_set(new_wrapper, bridge_id, bridge->uniqueid);
507 ast_string_field_set(new_wrapper, channel_id, ast_channel_uniqueid(chan));
509 if (!ao2_link_flags(app_bridges_moh, new_wrapper, OBJ_NOLOCK)) {
514 if (ast_pthread_create_detached(&threadid, NULL, moh_channel_thread, chan)) {
515 ast_log(LOG_ERROR, "Failed to create channel thread. Abandoning MOH channel creation.\n");
516 ao2_unlink_flags(app_bridges_moh, new_wrapper, OBJ_NOLOCK);
524 struct ast_channel *stasis_app_bridge_moh_channel(struct ast_bridge *bridge)
526 RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
529 SCOPED_AO2LOCK(lock, app_bridges_moh);
531 moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_SEARCH_KEY | OBJ_NOLOCK);
533 return bridge_moh_create(bridge);
537 return ast_channel_get_by_name(moh_wrapper->channel_id);
540 int stasis_app_bridge_moh_stop(struct ast_bridge *bridge)
542 RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
543 struct ast_channel *chan;
545 moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_SEARCH_KEY | OBJ_UNLINK);
550 chan = ast_channel_get_by_name(moh_wrapper->channel_id);
556 ast_softhangup(chan, AST_CAUSE_NORMAL_CLEARING);
562 struct ast_bridge *stasis_app_bridge_find_by_id(
563 const char *bridge_id)
565 return ao2_find(app_bridges, bridge_id, OBJ_SEARCH_KEY);
570 * \brief In addition to running ao2_cleanup(), this function also removes the
571 * object from the app_controls container.
573 static void control_unlink(struct stasis_app_control *control)
579 ao2_unlink(app_controls, control);
580 ao2_cleanup(control);
583 struct ast_bridge *stasis_app_bridge_create(const char *type)
585 struct ast_bridge *bridge;
587 int flags = AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_MERGE_INHIBIT_TO
588 | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_SWAP_INHIBIT_TO
589 | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED;
591 if (ast_strlen_zero(type) || !strcmp(type, "mixing")) {
592 capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX |
593 AST_BRIDGE_CAPABILITY_MULTIMIX |
594 AST_BRIDGE_CAPABILITY_NATIVE;
595 flags |= AST_BRIDGE_FLAG_SMART;
596 } else if (!strcmp(type, "holding")) {
597 capabilities = AST_BRIDGE_CAPABILITY_HOLDING;
602 bridge = ast_bridge_base_new(capabilities, flags);
604 if (!ao2_link(app_bridges, bridge)) {
605 ast_bridge_destroy(bridge, 0);
612 void stasis_app_bridge_destroy(const char *bridge_id)
614 struct ast_bridge *bridge = stasis_app_bridge_find_by_id(bridge_id);
618 ao2_unlink(app_bridges, bridge);
619 ast_bridge_destroy(bridge, 0);
622 static int send_start_msg(struct app *app, struct ast_channel *chan,
623 int argc, char *argv[])
625 RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
626 RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
628 struct ast_json *json_args;
631 ast_assert(chan != NULL);
633 /* Set channel info */
634 snapshot = ast_channel_snapshot_create(chan);
639 msg = ast_json_pack("{s: s, s: o, s: [], s: o}",
640 "type", "StasisStart",
641 "timestamp", ast_json_timeval(ast_tvnow(), NULL),
643 "channel", ast_channel_snapshot_to_json(snapshot));
648 /* Append arguments to args array */
649 json_args = ast_json_object_get(msg, "args");
650 ast_assert(json_args != NULL);
651 for (i = 0; i < argc; ++i) {
652 int r = ast_json_array_append(json_args,
653 ast_json_string_create(argv[i]));
655 ast_log(LOG_ERROR, "Error appending start message\n");
664 static int send_end_msg(struct app *app, struct ast_channel *chan)
666 RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
667 RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
669 ast_assert(chan != NULL);
671 /* Set channel info */
672 snapshot = ast_channel_snapshot_create(chan);
673 if (snapshot == NULL) {
677 msg = ast_json_pack("{s: s, s: o, s: o}",
679 "timestamp", ast_json_timeval(ast_tvnow(), NULL),
680 "channel", ast_channel_snapshot_to_json(snapshot));
689 void stasis_app_control_execute_until_exhausted(struct ast_channel *chan, struct stasis_app_control *control)
691 while (!control_is_done(control)) {
692 int command_count = control_dispatch_all(control, chan);
693 if (command_count == 0 || ast_channel_fdno(chan) == -1) {
699 /*! /brief Stasis dialplan application callback */
700 int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
703 SCOPED_MODULE_USE(ast_module_info->self);
705 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
706 RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
709 ast_assert(chan != NULL);
711 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
714 "Stasis app '%s' not registered\n", app_name);
717 if (!app_is_active(app)) {
719 "Stasis app '%s' not active\n", app_name);
723 control = control_create(chan);
725 ast_log(LOG_ERROR, "Allocated failed\n");
728 ao2_link(app_controls, control);
730 res = send_start_msg(app, chan, argc, argv);
733 "Error sending start message to '%s'\n", app_name);
737 res = app_subscribe_channel(app, chan);
739 ast_log(LOG_ERROR, "Error subscribing app '%s' to channel '%s'\n",
740 app_name, ast_channel_name(chan));
744 while (!control_is_done(control)) {
745 RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor);
748 struct ast_bridge *last_bridge = NULL;
749 struct ast_bridge *bridge = NULL;
751 /* Check to see if a bridge absorbed our hangup frame */
752 if (ast_check_hangup_locked(chan)) {
756 last_bridge = bridge;
757 bridge = stasis_app_get_bridge(control);
759 if (bridge != last_bridge) {
760 app_unsubscribe_bridge(app, last_bridge);
761 app_subscribe_bridge(app, bridge);
765 /* Bridge is handling channel frames */
766 control_wait(control);
767 control_dispatch_all(control, chan);
771 r = ast_waitfor(chan, MAX_WAIT_MS);
774 ast_debug(3, "%s: Poll error\n",
775 ast_channel_uniqueid(chan));
779 command_count = control_dispatch_all(control, chan);
781 if (command_count > 0 && ast_channel_fdno(chan) == -1) {
782 /* Command drained the channel; wait for next frame */
793 /* Continue on in the dialplan */
794 ast_debug(3, "%s: Hangup (no more frames)\n",
795 ast_channel_uniqueid(chan));
799 if (f->frametype == AST_FRAME_CONTROL) {
800 if (f->subclass.integer == AST_CONTROL_HANGUP) {
801 /* Continue on in the dialplan */
802 ast_debug(3, "%s: Hangup\n",
803 ast_channel_uniqueid(chan));
809 app_unsubscribe_bridge(app, stasis_app_get_bridge(control));
810 app_unsubscribe_channel(app, chan);
812 res = send_end_msg(app, chan);
815 "Error sending end message to %s\n", app_name);
819 /* There's an off chance that app is ready for cleanup. Go ahead
820 * and clean up, just in case
827 int stasis_app_send(const char *app_name, struct ast_json *message)
829 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
831 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
833 /* XXX We can do a better job handling late binding, queueing up
834 * the call for a few seconds to wait for the app to register.
837 "Stasis app '%s' not registered\n", app_name);
841 app_send(app, message);
845 static int append_name(void *obj, void *arg, int flags)
847 struct app *app = obj;
848 struct ao2_container *apps = arg;
850 ast_str_container_add(apps, app_name(app));
854 struct ao2_container *stasis_app_get_all(void)
856 RAII_VAR(struct ao2_container *, apps, NULL, ao2_cleanup);
858 apps = ast_str_container_alloc(1);
863 ao2_callback(apps_registry, OBJ_NODATA, append_name, apps);
865 return ao2_bump(apps);
868 int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
870 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
872 SCOPED_LOCK(apps_lock, apps_registry, ao2_lock, ao2_unlock);
874 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
876 app_update(app, handler, data);
878 app = app_create(app_name, handler, data);
880 ao2_link_flags(apps_registry, app, OBJ_NOLOCK);
886 /* We lazily clean up the apps_registry, because it's good enough to
887 * prevent memory leaks, and we're lazy.
893 void stasis_app_unregister(const char *app_name)
895 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
901 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
904 "Stasis app '%s' not registered\n", app_name);
910 /* There's a decent chance that app is ready for cleanup. Go ahead
911 * and clean up, just in case
916 struct ast_json *stasis_app_to_json(const char *app_name)
918 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
921 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
928 return app_to_json(app);
931 #define CHANNEL_SCHEME "channel:"
932 #define BRIDGE_SCHEME "bridge:"
933 #define ENDPOINT_SCHEME "endpoint:"
935 /*! Struct for capturing event source information */
936 struct event_source {
938 EVENT_SOURCE_CHANNEL,
940 EVENT_SOURCE_ENDPOINT,
943 struct ast_channel *channel;
944 struct ast_bridge *bridge;
945 struct ast_endpoint *endpoint;
949 enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name,
950 const char **event_source_uris, int event_sources_count,
951 struct ast_json **json)
953 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
954 RAII_VAR(struct event_source *, event_sources, NULL, ast_free);
955 enum stasis_app_subscribe_res res = STASIS_ASR_OK;
959 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
963 ast_log(LOG_WARNING, "Could not find app '%s'\n",
964 app_name ? : "(null)");
965 return STASIS_ASR_APP_NOT_FOUND;
968 event_sources = ast_calloc(event_sources_count, sizeof(*event_sources));
969 if (!event_sources) {
970 return STASIS_ASR_INTERNAL_ERROR;
973 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
974 const char *uri = event_source_uris[i];
975 ast_debug(3, "%s: Checking %s\n", app_name,
977 if (ast_begins_with(uri, CHANNEL_SCHEME)) {
978 event_sources[i].event_source_type =
979 EVENT_SOURCE_CHANNEL;
980 event_sources[i].channel = ast_channel_get_by_name(
981 uri + strlen(CHANNEL_SCHEME));
982 if (!event_sources[i].channel) {
983 ast_log(LOG_WARNING, "Channel not found: %s\n", uri);
984 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
986 } else if (ast_begins_with(uri, BRIDGE_SCHEME)) {
987 event_sources[i].event_source_type =
989 event_sources[i].bridge = stasis_app_bridge_find_by_id(
990 uri + strlen(BRIDGE_SCHEME));
991 if (!event_sources[i].bridge) {
992 ast_log(LOG_WARNING, "Bridge not found: %s\n", uri);
993 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
995 } else if (ast_begins_with(uri, ENDPOINT_SCHEME)) {
996 event_sources[i].event_source_type =
997 EVENT_SOURCE_ENDPOINT;
998 event_sources[i].endpoint = ast_endpoint_find_by_id(
999 uri + strlen(ENDPOINT_SCHEME));
1000 if (!event_sources[i].endpoint) {
1001 ast_log(LOG_WARNING, "Endpoint not found: %s\n", uri);
1002 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1005 ast_log(LOG_WARNING, "Invalid scheme: %s\n", uri);
1006 res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
1010 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
1012 ast_debug(1, "%s: Subscribing to %s\n", app_name,
1013 event_source_uris[i]);
1015 switch (event_sources[i].event_source_type) {
1016 case EVENT_SOURCE_CHANNEL:
1017 sub_res = app_subscribe_channel(app,
1018 event_sources[i].channel);
1020 case EVENT_SOURCE_BRIDGE:
1021 sub_res = app_subscribe_bridge(app,
1022 event_sources[i].bridge);
1024 case EVENT_SOURCE_ENDPOINT:
1025 sub_res = app_subscribe_endpoint(app,
1026 event_sources[i].endpoint);
1031 ast_log(LOG_WARNING,
1032 "Error subscribing app '%s' to '%s'\n",
1033 app_name, event_source_uris[i]);
1034 res = STASIS_ASR_INTERNAL_ERROR;
1038 if (res == STASIS_ASR_OK && json) {
1039 ast_debug(1, "%s: Successful; setting results\n", app_name);
1040 *json = app_to_json(app);
1043 for (i = 0; i < event_sources_count; ++i) {
1044 switch (event_sources[i].event_source_type) {
1045 case EVENT_SOURCE_CHANNEL:
1046 event_sources[i].channel =
1047 ast_channel_cleanup(event_sources[i].channel);
1049 case EVENT_SOURCE_BRIDGE:
1050 ao2_cleanup(event_sources[i].bridge);
1051 event_sources[i].bridge = NULL;
1053 case EVENT_SOURCE_ENDPOINT:
1054 ao2_cleanup(event_sources[i].endpoint);
1055 event_sources[i].endpoint = NULL;
1063 enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name,
1064 const char **event_source_uris, int event_sources_count,
1065 struct ast_json **json)
1067 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
1068 enum stasis_app_subscribe_res res = STASIS_ASR_OK;
1072 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
1076 ast_log(LOG_WARNING, "Could not find app '%s'\n",
1077 app_name ? : "(null)");
1078 return STASIS_ASR_APP_NOT_FOUND;
1081 /* Validate the input */
1082 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
1083 if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
1084 const char *channel_id = event_source_uris[i] +
1085 strlen(CHANNEL_SCHEME);
1086 if (!app_is_subscribed_channel_id(app, channel_id)) {
1087 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1089 } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
1090 const char *bridge_id = event_source_uris[i] +
1091 strlen(BRIDGE_SCHEME);
1092 if (!app_is_subscribed_bridge_id(app, bridge_id)) {
1093 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1095 } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
1096 const char *endpoint_id = event_source_uris[i] +
1097 strlen(ENDPOINT_SCHEME);
1098 if (!app_is_subscribed_endpoint_id(app, endpoint_id)) {
1099 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1102 res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
1106 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
1107 if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
1108 const char *channel_id = event_source_uris[i] +
1109 strlen(CHANNEL_SCHEME);
1110 app_unsubscribe_channel_id(app, channel_id);
1111 } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
1112 const char *bridge_id = event_source_uris[i] +
1113 strlen(BRIDGE_SCHEME);
1114 app_unsubscribe_bridge_id(app, bridge_id);
1115 } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
1116 const char *endpoint_id = event_source_uris[i] +
1117 strlen(ENDPOINT_SCHEME);
1118 app_unsubscribe_endpoint_id(app, endpoint_id);
1122 if (res == STASIS_ASR_OK && json) {
1123 *json = app_to_json(app);
1129 void stasis_app_ref(void)
1131 ast_module_ref(ast_module_info->self);
1134 void stasis_app_unref(void)
1136 ast_module_unref(ast_module_info->self);
1139 static int unload_module(void)
1141 ao2_cleanup(apps_registry);
1142 apps_registry = NULL;
1144 ao2_cleanup(app_controls);
1145 app_controls = NULL;
1147 ao2_cleanup(app_bridges);
1150 ao2_cleanup(app_bridges_moh);
1151 app_bridges_moh = NULL;
1156 static int load_module(void)
1158 apps_registry = ao2_container_alloc(APPS_NUM_BUCKETS, app_hash, app_compare);
1159 app_controls = ao2_container_alloc(CONTROLS_NUM_BUCKETS, control_hash, control_compare);
1160 app_bridges = ao2_container_alloc(BRIDGES_NUM_BUCKETS, bridges_hash, bridges_compare);
1161 app_bridges_moh = ao2_container_alloc_hash(
1162 AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1163 37, bridges_moh_hash_fn, bridges_moh_sort_fn, NULL);
1164 if (!apps_registry || !app_controls || !app_bridges || !app_bridges_moh) {
1166 return AST_MODULE_LOAD_FAILURE;
1169 return AST_MODULE_LOAD_SUCCESS;
1172 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application support",
1173 .load = load_module,
1174 .unload = unload_module,