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 RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, ao2_find(app_bridges_moh, bridge_id, OBJ_SEARCH_KEY), ao2_cleanup);
409 ao2_unlink_flags(app_bridges_moh, moh_wrapper, OBJ_NOLOCK);
414 /*! After bridge failure callback for moh channels */
415 static void moh_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
417 char *bridge_id = data;
419 remove_bridge_moh(bridge_id);
422 /*! After bridge callback for moh channels */
423 static void moh_after_bridge_cb(struct ast_channel *chan, void *data)
425 char *bridge_id = data;
427 remove_bridge_moh(bridge_id);
430 /*! Request a bridge MOH channel */
431 static struct ast_channel *prepare_bridge_moh_channel(void)
433 RAII_VAR(struct ast_format_cap *, cap, NULL, ast_format_cap_destroy);
434 struct ast_format format;
436 cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_NOLOCK);
441 ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0));
443 return ast_request("Announcer", cap, NULL, "ARI_MOH", NULL);
446 /*! Provides the moh channel with a thread so it can actually play its music */
447 static void *moh_channel_thread(void *data)
449 struct ast_channel *moh_channel = data;
451 while (!ast_safe_sleep(moh_channel, 1000));
453 ast_moh_stop(moh_channel);
454 ast_hangup(moh_channel);
461 * \brief Creates, pushes, and links a channel for playing music on hold to bridge
463 * \param bridge Which bridge this moh channel exists for
465 * \retval NULL if the channel could not be created, pushed, or linked
466 * \retval Reference to the channel on success
468 static struct ast_channel *bridge_moh_create(struct ast_bridge *bridge)
470 RAII_VAR(struct stasis_app_bridge_moh_wrapper *, new_wrapper, NULL, ao2_cleanup);
471 RAII_VAR(char *, bridge_id, ast_strdup(bridge->uniqueid), ast_free);
472 struct ast_channel *chan;
479 chan = prepare_bridge_moh_channel();
485 /* The after bridge callback assumes responsibility of the bridge_id. */
486 ast_bridge_set_after_callback(chan, moh_after_bridge_cb, moh_after_bridge_cb_failed, bridge_id);
490 if (ast_unreal_channel_push_to_bridge(chan, bridge,
491 AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
496 new_wrapper = ao2_alloc_options(sizeof(*new_wrapper), stasis_app_bridge_moh_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
502 if (ast_string_field_init(new_wrapper, 32)) {
507 ast_string_field_set(new_wrapper, bridge_id, bridge->uniqueid);
508 ast_string_field_set(new_wrapper, channel_id, ast_channel_uniqueid(chan));
510 if (!ao2_link(app_bridges_moh, new_wrapper)) {
515 if (ast_pthread_create_detached(&threadid, NULL, moh_channel_thread, chan)) {
516 ast_log(LOG_ERROR, "Failed to create channel thread. Abandoning MOH channel creation.\n");
517 ao2_unlink_flags(app_bridges_moh, new_wrapper, OBJ_NOLOCK);
525 struct ast_channel *stasis_app_bridge_moh_channel(struct ast_bridge *bridge)
527 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);
534 struct ast_channel *bridge_moh_channel = bridge_moh_create(bridge);
535 return bridge_moh_channel;
538 return ast_channel_get_by_name(moh_wrapper->channel_id);
541 int stasis_app_bridge_moh_stop(struct ast_bridge *bridge)
543 RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup);
544 struct ast_channel *chan;
546 SCOPED_AO2LOCK(lock, app_bridges_moh);
548 moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_SEARCH_KEY | OBJ_NOLOCK);
554 chan = ast_channel_get_by_name(moh_wrapper->channel_id);
560 ast_softhangup(chan, AST_CAUSE_NORMAL_CLEARING);
563 ao2_unlink_flags(app_bridges_moh, moh_wrapper, OBJ_NOLOCK);
568 struct ast_bridge *stasis_app_bridge_find_by_id(
569 const char *bridge_id)
571 return ao2_find(app_bridges, bridge_id, OBJ_SEARCH_KEY);
576 * \brief In addition to running ao2_cleanup(), this function also removes the
577 * object from the app_controls container.
579 static void control_unlink(struct stasis_app_control *control)
585 ao2_unlink_flags(app_controls, control,
586 OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NODATA);
587 ao2_cleanup(control);
590 struct ast_bridge *stasis_app_bridge_create(const char *type)
592 struct ast_bridge *bridge;
593 int capabilities, flags = AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM | AST_BRIDGE_FLAG_MERGE_INHIBIT_TO
594 | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_SWAP_INHIBIT_TO
595 | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED;
597 if (ast_strlen_zero(type) || !strcmp(type, "mixing")) {
598 capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX |
599 AST_BRIDGE_CAPABILITY_MULTIMIX |
600 AST_BRIDGE_CAPABILITY_NATIVE;
601 flags |= AST_BRIDGE_FLAG_SMART;
602 } else if (!strcmp(type, "holding")) {
603 capabilities = AST_BRIDGE_CAPABILITY_HOLDING;
608 bridge = ast_bridge_base_new(capabilities, flags);
610 ao2_link(app_bridges, bridge);
615 void stasis_app_bridge_destroy(const char *bridge_id)
617 struct ast_bridge *bridge = stasis_app_bridge_find_by_id(bridge_id);
621 ao2_unlink(app_bridges, bridge);
622 ast_bridge_destroy(bridge, 0);
625 static int send_start_msg(struct app *app, struct ast_channel *chan,
626 int argc, char *argv[])
628 RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
629 RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
631 struct ast_json *json_args;
634 ast_assert(chan != NULL);
636 /* Set channel info */
637 snapshot = ast_channel_snapshot_create(chan);
642 msg = ast_json_pack("{s: s, s: o, s: [], s: o}",
643 "type", "StasisStart",
644 "timestamp", ast_json_timeval(ast_tvnow(), NULL),
646 "channel", ast_channel_snapshot_to_json(snapshot));
651 /* Append arguments to args array */
652 json_args = ast_json_object_get(msg, "args");
653 ast_assert(json_args != NULL);
654 for (i = 0; i < argc; ++i) {
655 int r = ast_json_array_append(json_args,
656 ast_json_string_create(argv[i]));
658 ast_log(LOG_ERROR, "Error appending start message\n");
667 static int send_end_msg(struct app *app, struct ast_channel *chan)
669 RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
670 RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
672 ast_assert(chan != NULL);
674 /* Set channel info */
675 snapshot = ast_channel_snapshot_create(chan);
676 if (snapshot == NULL) {
680 msg = ast_json_pack("{s: s, s: o, s: o}",
682 "timestamp", ast_json_timeval(ast_tvnow(), NULL),
683 "channel", ast_channel_snapshot_to_json(snapshot));
692 void stasis_app_control_execute_until_exhausted(struct ast_channel *chan, struct stasis_app_control *control)
694 while (!control_is_done(control)) {
695 int command_count = control_dispatch_all(control, chan);
696 if (command_count == 0 || ast_channel_fdno(chan) == -1) {
702 /*! /brief Stasis dialplan application callback */
703 int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
706 SCOPED_MODULE_USE(ast_module_info->self);
708 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
709 RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
712 ast_assert(chan != NULL);
714 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
717 "Stasis app '%s' not registered\n", app_name);
720 if (!app_is_active(app)) {
722 "Stasis app '%s' not active\n", app_name);
726 control = control_create(chan);
728 ast_log(LOG_ERROR, "Allocated failed\n");
731 ao2_link(app_controls, control);
733 res = send_start_msg(app, chan, argc, argv);
736 "Error sending start message to '%s'\n", app_name);
740 res = app_subscribe_channel(app, chan);
742 ast_log(LOG_ERROR, "Error subscribing app '%s' to channel '%s'\n",
743 app_name, ast_channel_name(chan));
747 while (!control_is_done(control)) {
748 RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor);
751 struct ast_bridge *last_bridge = NULL;
752 struct ast_bridge *bridge = NULL;
754 /* Check to see if a bridge absorbed our hangup frame */
755 if (ast_check_hangup_locked(chan)) {
759 last_bridge = bridge;
760 bridge = stasis_app_get_bridge(control);
762 if (bridge != last_bridge) {
763 app_unsubscribe_bridge(app, last_bridge);
764 app_subscribe_bridge(app, bridge);
768 /* Bridge is handling channel frames */
769 control_wait(control);
770 control_dispatch_all(control, chan);
774 r = ast_waitfor(chan, MAX_WAIT_MS);
777 ast_debug(3, "%s: Poll error\n",
778 ast_channel_uniqueid(chan));
782 command_count = control_dispatch_all(control, chan);
784 if (command_count > 0 && ast_channel_fdno(chan) == -1) {
785 /* Command drained the channel; wait for next frame */
796 /* Continue on in the dialplan */
797 ast_debug(3, "%s: Hangup (no more frames)\n",
798 ast_channel_uniqueid(chan));
802 if (f->frametype == AST_FRAME_CONTROL) {
803 if (f->subclass.integer == AST_CONTROL_HANGUP) {
804 /* Continue on in the dialplan */
805 ast_debug(3, "%s: Hangup\n",
806 ast_channel_uniqueid(chan));
812 app_unsubscribe_bridge(app, stasis_app_get_bridge(control));
813 app_unsubscribe_channel(app, chan);
815 res = send_end_msg(app, chan);
818 "Error sending end message to %s\n", app_name);
822 /* There's an off chance that app is ready for cleanup. Go ahead
823 * and clean up, just in case
830 int stasis_app_send(const char *app_name, struct ast_json *message)
832 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
834 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
836 /* XXX We can do a better job handling late binding, queueing up
837 * the call for a few seconds to wait for the app to register.
840 "Stasis app '%s' not registered\n", app_name);
844 app_send(app, message);
848 static int append_name(void *obj, void *arg, int flags)
850 struct app *app = obj;
851 struct ao2_container *apps = arg;
853 ast_str_container_add(apps, app_name(app));
857 struct ao2_container *stasis_app_get_all(void)
859 RAII_VAR(struct ao2_container *, apps, NULL, ao2_cleanup);
861 apps = ast_str_container_alloc(1);
866 ao2_callback(apps_registry, OBJ_NODATA, append_name, apps);
868 return ao2_bump(apps);
871 int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
873 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
875 SCOPED_LOCK(apps_lock, apps_registry, ao2_lock, ao2_unlock);
877 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
879 app_update(app, handler, data);
881 app = app_create(app_name, handler, data);
883 ao2_link_flags(apps_registry, app, OBJ_NOLOCK);
889 /* We lazily clean up the apps_registry, because it's good enough to
890 * prevent memory leaks, and we're lazy.
896 void stasis_app_unregister(const char *app_name)
898 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
904 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
907 "Stasis app '%s' not registered\n", app_name);
913 /* There's a decent chance that app is ready for cleanup. Go ahead
914 * and clean up, just in case
919 struct ast_json *stasis_app_to_json(const char *app_name)
921 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
924 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
931 return app_to_json(app);
934 #define CHANNEL_SCHEME "channel:"
935 #define BRIDGE_SCHEME "bridge:"
936 #define ENDPOINT_SCHEME "endpoint:"
938 /*! Struct for capturing event source information */
939 struct event_source {
941 EVENT_SOURCE_CHANNEL,
943 EVENT_SOURCE_ENDPOINT,
946 struct ast_channel *channel;
947 struct ast_bridge *bridge;
948 struct ast_endpoint *endpoint;
952 enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name,
953 const char **event_source_uris, int event_sources_count,
954 struct ast_json **json)
956 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
957 RAII_VAR(struct event_source *, event_sources, NULL, ast_free);
958 enum stasis_app_subscribe_res res = STASIS_ASR_OK;
962 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
966 ast_log(LOG_WARNING, "Could not find app '%s'\n",
967 app_name ? : "(null)");
968 return STASIS_ASR_APP_NOT_FOUND;
971 event_sources = ast_calloc(event_sources_count, sizeof(*event_sources));
972 if (!event_sources) {
973 return STASIS_ASR_INTERNAL_ERROR;
976 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
977 const char *uri = event_source_uris[i];
978 ast_debug(3, "%s: Checking %s\n", app_name,
980 if (ast_begins_with(uri, CHANNEL_SCHEME)) {
981 event_sources[i].event_source_type =
982 EVENT_SOURCE_CHANNEL;
983 event_sources[i].channel = ast_channel_get_by_name(
984 uri + strlen(CHANNEL_SCHEME));
985 if (!event_sources[i].channel) {
986 ast_log(LOG_WARNING, "Channel not found: %s\n", uri);
987 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
989 } else if (ast_begins_with(uri, BRIDGE_SCHEME)) {
990 event_sources[i].event_source_type =
992 event_sources[i].bridge = stasis_app_bridge_find_by_id(
993 uri + strlen(BRIDGE_SCHEME));
994 if (!event_sources[i].bridge) {
995 ast_log(LOG_WARNING, "Bridge not found: %s\n", uri);
996 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
998 } else if (ast_begins_with(uri, ENDPOINT_SCHEME)) {
999 event_sources[i].event_source_type =
1000 EVENT_SOURCE_ENDPOINT;
1001 event_sources[i].endpoint = ast_endpoint_find_by_id(
1002 uri + strlen(ENDPOINT_SCHEME));
1003 if (!event_sources[i].endpoint) {
1004 ast_log(LOG_WARNING, "Endpoint not found: %s\n", uri);
1005 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1008 ast_log(LOG_WARNING, "Invalid scheme: %s\n", uri);
1009 res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
1013 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
1015 ast_debug(1, "%s: Subscribing to %s\n", app_name,
1016 event_source_uris[i]);
1018 switch (event_sources[i].event_source_type) {
1019 case EVENT_SOURCE_CHANNEL:
1020 sub_res = app_subscribe_channel(app,
1021 event_sources[i].channel);
1023 case EVENT_SOURCE_BRIDGE:
1024 sub_res = app_subscribe_bridge(app,
1025 event_sources[i].bridge);
1027 case EVENT_SOURCE_ENDPOINT:
1028 sub_res = app_subscribe_endpoint(app,
1029 event_sources[i].endpoint);
1034 ast_log(LOG_WARNING,
1035 "Error subscribing app '%s' to '%s'\n",
1036 app_name, event_source_uris[i]);
1037 res = STASIS_ASR_INTERNAL_ERROR;
1041 if (res == STASIS_ASR_OK && json) {
1042 ast_debug(1, "%s: Successful; setting results\n", app_name);
1043 *json = app_to_json(app);
1046 for (i = 0; i < event_sources_count; ++i) {
1047 switch (event_sources[i].event_source_type) {
1048 case EVENT_SOURCE_CHANNEL:
1049 event_sources[i].channel =
1050 ast_channel_cleanup(event_sources[i].channel);
1052 case EVENT_SOURCE_BRIDGE:
1053 ao2_cleanup(event_sources[i].bridge);
1054 event_sources[i].bridge = NULL;
1056 case EVENT_SOURCE_ENDPOINT:
1057 ao2_cleanup(event_sources[i].endpoint);
1058 event_sources[i].endpoint = NULL;
1066 enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name,
1067 const char **event_source_uris, int event_sources_count,
1068 struct ast_json **json)
1070 RAII_VAR(struct app *, app, NULL, ao2_cleanup);
1071 enum stasis_app_subscribe_res res = STASIS_ASR_OK;
1075 ast_log(LOG_WARNING, "Could not find app '%s'\n",
1076 app_name ? : "(null)");
1077 app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
1081 return STASIS_ASR_APP_NOT_FOUND;
1084 /* Validate the input */
1085 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
1086 if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
1087 const char *channel_id = event_source_uris[i] +
1088 strlen(CHANNEL_SCHEME);
1089 if (!app_is_subscribed_channel_id(app, channel_id)) {
1090 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1092 } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
1093 const char *bridge_id = event_source_uris[i] +
1094 strlen(BRIDGE_SCHEME);
1095 if (!app_is_subscribed_bridge_id(app, bridge_id)) {
1096 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1098 } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
1099 const char *endpoint_id = event_source_uris[i] +
1100 strlen(ENDPOINT_SCHEME);
1101 if (!app_is_subscribed_endpoint_id(app, endpoint_id)) {
1102 res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
1105 res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
1109 for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
1110 if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
1111 const char *channel_id = event_source_uris[i] +
1112 strlen(CHANNEL_SCHEME);
1113 app_unsubscribe_channel_id(app, channel_id);
1114 } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
1115 const char *bridge_id = event_source_uris[i] +
1116 strlen(BRIDGE_SCHEME);
1117 app_unsubscribe_bridge_id(app, bridge_id);
1118 } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
1119 const char *endpoint_id = event_source_uris[i] +
1120 strlen(ENDPOINT_SCHEME);
1121 app_unsubscribe_endpoint_id(app, endpoint_id);
1125 if (res == STASIS_ASR_OK && json) {
1126 *json = app_to_json(app);
1132 void stasis_app_ref(void)
1134 ast_module_ref(ast_module_info->self);
1137 void stasis_app_unref(void)
1139 ast_module_unref(ast_module_info->self);
1142 static int load_module(void)
1144 apps_registry = ao2_container_alloc(APPS_NUM_BUCKETS, app_hash,
1146 if (apps_registry == NULL) {
1147 return AST_MODULE_LOAD_FAILURE;
1150 app_controls = ao2_container_alloc(CONTROLS_NUM_BUCKETS, control_hash,
1152 if (app_controls == NULL) {
1153 return AST_MODULE_LOAD_FAILURE;
1156 app_bridges = ao2_container_alloc(BRIDGES_NUM_BUCKETS, bridges_hash,
1159 app_bridges_moh = ao2_container_alloc_hash(
1160 AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
1161 37, bridges_moh_hash_fn, bridges_moh_sort_fn, NULL);
1163 if (!app_bridges_moh) {
1164 return AST_MODULE_LOAD_FAILURE;
1167 return AST_MODULE_LOAD_SUCCESS;
1170 static int unload_module(void)
1172 ao2_cleanup(apps_registry);
1173 apps_registry = NULL;
1175 ao2_cleanup(app_controls);
1176 app_controls = NULL;
1178 ao2_cleanup(app_bridges);
1181 ao2_cleanup(app_bridges_moh);
1182 app_bridges_moh = NULL;
1187 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application support",
1188 .load = load_module,
1189 .unload = unload_module,