summaryrefslogtreecommitdiff
path: root/res/res_stasis.c
diff options
context:
space:
mode:
authorKevin Harwell <kharwell@digium.com>2013-11-23 17:48:28 +0000
committerKevin Harwell <kharwell@digium.com>2013-11-23 17:48:28 +0000
commited483779946553e2ec42472c6b665b86dcb07066 (patch)
tree954ae85065f670f111954e36e5bd77d4f271778a /res/res_stasis.c
parent05cbf8df9b2ea0b41e049698b9f51ee4365ceab0 (diff)
ARI: Implement device state API
Created a data model and implemented functionality for an ARI device state resource. The following operations have been added that allow a user to manipulate an ARI controlled device: Create/Change the state of an ARI controlled device PUT /deviceStates/{deviceName}&{deviceState} Retrieve all ARI controlled devices GET /deviceStates Retrieve the current state of a device GET /deviceStates/{deviceName} Destroy a device-state controlled by ARI DELETE /deviceStates/{deviceName} The ARI controlled device must begin with 'Stasis:'. An example controlled device name would be Stasis:Example. A 'DeviceStateChanged' event has also been added so that an application can subscribe and receive device change events. Any device state, ARI controlled or not, can be subscribed to. While adding the event, the underlying subscription control mechanism was refactored so that all current and future resource subscriptions would be the same. Each event resource must now register itself in order to be able to properly handle [un]subscribes. (issue ASTERISK-22838) Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/3025/ ........ Merged revisions 403134 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@403135 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res/res_stasis.c')
-rw-r--r--res/res_stasis.c429
1 files changed, 242 insertions, 187 deletions
diff --git a/res/res_stasis.c b/res/res_stasis.c
index e21941210..691462722 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -103,10 +103,15 @@ struct ao2_container *app_bridges;
struct ao2_container *app_bridges_moh;
+const char *stasis_app_name(const struct stasis_app *app)
+{
+ return app_name(app);
+}
+
/*! AO2 hash function for \ref app */
static int app_hash(const void *obj, const int flags)
{
- const struct app *app;
+ const struct stasis_app *app;
const char *key;
switch (flags & OBJ_SEARCH_MASK) {
@@ -115,7 +120,7 @@ static int app_hash(const void *obj, const int flags)
break;
case OBJ_SEARCH_OBJECT:
app = obj;
- key = app_name(app);
+ key = stasis_app_name(app);
break;
default:
/* Hash can only work on something with a full key. */
@@ -128,24 +133,24 @@ static int app_hash(const void *obj, const int flags)
/*! AO2 comparison function for \ref app */
static int app_compare(void *obj, void *arg, int flags)
{
- const struct app *object_left = obj;
- const struct app *object_right = arg;
+ const struct stasis_app *object_left = obj;
+ const struct stasis_app *object_right = arg;
const char *right_key = arg;
int cmp;
switch (flags & OBJ_SEARCH_MASK) {
case OBJ_SEARCH_OBJECT:
- right_key = app_name(object_right);
+ right_key = stasis_app_name(object_right);
/* Fall through */
case OBJ_SEARCH_KEY:
- cmp = strcmp(app_name(object_left), right_key);
+ cmp = strcmp(stasis_app_name(object_left), right_key);
break;
case OBJ_SEARCH_PARTIAL_KEY:
/*
* We could also use a partial key struct containing a length
* so strlen() does not get called for every comparison instead.
*/
- cmp = strncmp(app_name(object_left), right_key, strlen(right_key));
+ cmp = strncmp(stasis_app_name(object_left), right_key, strlen(right_key));
break;
default:
/*
@@ -229,13 +234,13 @@ static int control_compare(void *obj, void *arg, int flags)
static int cleanup_cb(void *obj, void *arg, int flags)
{
- struct app *app = obj;
+ struct stasis_app *app = obj;
if (!app_is_finished(app)) {
return 0;
}
- ast_verb(1, "Shutting down application '%s'\n", app_name(app));
+ ast_verb(1, "Shutting down application '%s'\n", stasis_app_name(app));
app_shutdown(app);
return CMP_MATCH;
@@ -619,7 +624,7 @@ void stasis_app_bridge_destroy(const char *bridge_id)
ast_bridge_destroy(bridge, 0);
}
-static int send_start_msg(struct app *app, struct ast_channel *chan,
+static int send_start_msg(struct stasis_app *app, struct ast_channel *chan,
int argc, char *argv[])
{
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
@@ -667,7 +672,7 @@ static int send_start_msg(struct app *app, struct ast_channel *chan,
return 0;
}
-static int send_end_msg(struct app *app, struct ast_channel *chan)
+static int send_end_msg(struct stasis_app *app, struct ast_channel *chan)
{
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -714,7 +719,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
{
SCOPED_MODULE_USE(ast_module_info->self);
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
struct ast_bridge *last_bridge = NULL;
int res = 0;
@@ -838,7 +843,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
int stasis_app_send(const char *app_name, struct ast_json *message)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
if (!app) {
@@ -849,17 +854,31 @@ int stasis_app_send(const char *app_name, struct ast_json *message)
"Stasis app '%s' not registered\n", app_name);
return -1;
}
-
app_send(app, message);
return 0;
}
+static struct stasis_app *find_app_by_name(const char *app_name)
+{
+ struct stasis_app *res = NULL;
+
+ if (!ast_strlen_zero(app_name)) {
+ res = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
+ }
+
+ if (!res) {
+ ast_log(LOG_WARNING, "Could not find app '%s'\n",
+ app_name ? : "(null)");
+ }
+ return res;
+}
+
static int append_name(void *obj, void *arg, int flags)
{
- struct app *app = obj;
+ struct stasis_app *app = obj;
struct ao2_container *apps = arg;
- ast_str_container_add(apps, app_name(app));
+ ast_str_container_add(apps, stasis_app_name(app));
return 0;
}
@@ -879,7 +898,7 @@ struct ao2_container *stasis_app_get_all(void)
int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
SCOPED_LOCK(apps_lock, apps_registry, ao2_lock, ao2_unlock);
@@ -904,7 +923,7 @@ int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
void stasis_app_unregister(const char *app_name)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
if (!app_name) {
return;
@@ -925,217 +944,249 @@ void stasis_app_unregister(const char *app_name)
cleanup();
}
-struct ast_json *stasis_app_to_json(const char *app_name)
+/*!
+ * \internal \brief List of registered event sources.
+ */
+AST_RWLIST_HEAD_STATIC(event_sources, stasis_app_event_source);
+
+void stasis_app_register_event_source(struct stasis_app_event_source *obj)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+ SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_LIST_INSERT_TAIL(&event_sources, obj, next);
+ /* only need to bump the module ref on non-core sources because the
+ core ones are [un]registered by this module. */
+ if (!stasis_app_is_core_event_source(obj)) {
+ ast_module_ref(ast_module_info->self);
+ }
+}
+
+void stasis_app_unregister_event_source(struct stasis_app_event_source *obj)
+{
+ struct stasis_app_event_source *source;
+ SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&event_sources, source, next) {
+ if (source == obj) {
+ AST_RWLIST_REMOVE_CURRENT(next);
+ if (!stasis_app_is_core_event_source(obj)) {
+ ast_module_unref(ast_module_info->self);
+ }
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+}
- if (app_name) {
- app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
+/*!
+ * \internal
+ * \brief Convert event source data to JSON.
+ *
+ * Calls each event source that has a "to_json" handler allowing each
+ * source to add data to the given JSON object.
+ *
+ * \param app application associated with the event source
+ * \param json a json object to "fill"
+ *
+ * \retval The given json object.
+ */
+static struct ast_json *app_event_sources_to_json(
+ const struct stasis_app *app, struct ast_json *json)
+{
+ struct stasis_app_event_source *source;
+ SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+ AST_LIST_TRAVERSE(&event_sources, source, next) {
+ if (source->to_json) {
+ source->to_json(app, json);
+ }
}
+ return json;
+}
+static struct ast_json *stasis_app_object_to_json(struct stasis_app *app)
+{
if (!app) {
return NULL;
}
- return app_to_json(app);
+ return app_event_sources_to_json(app, app_to_json(app));
}
-#define CHANNEL_SCHEME "channel:"
-#define BRIDGE_SCHEME "bridge:"
-#define ENDPOINT_SCHEME "endpoint:"
-
-/*! Struct for capturing event source information */
-struct event_source {
- enum {
- EVENT_SOURCE_CHANNEL,
- EVENT_SOURCE_BRIDGE,
- EVENT_SOURCE_ENDPOINT,
- } event_source_type;
- union {
- struct ast_channel *channel;
- struct ast_bridge *bridge;
- struct ast_endpoint *endpoint;
- };
-};
-
-enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name,
- const char **event_source_uris, int event_sources_count,
- struct ast_json **json)
+struct ast_json *stasis_app_to_json(const char *app_name)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
- RAII_VAR(struct event_source *, event_sources, NULL, ast_free);
- enum stasis_app_subscribe_res res = STASIS_ASR_OK;
- int i;
+ RAII_VAR(struct stasis_app *, app, find_app_by_name(app_name), ao2_cleanup);
- if (app_name) {
- app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
+ return stasis_app_object_to_json(app);
+}
+
+/*!
+ * \internal
+ * \brief Finds an event source that matches a uri scheme.
+ *
+ * Uri(s) should begin with a particular scheme that can be matched
+ * against an event source.
+ *
+ * \param uri uri containing a scheme to match
+ *
+ * \retval an event source if found, NULL otherwise.
+ */
+static struct stasis_app_event_source *app_event_source_find(const char *uri)
+{
+ struct stasis_app_event_source *source;
+ SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+ AST_LIST_TRAVERSE(&event_sources, source, next) {
+ if (ast_begins_with(uri, source->scheme)) {
+ return source;
+ }
}
+ return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Callback for subscription handling
+ *
+ * \param app [un]subscribing application
+ * \param uri scheme:id of an event source
+ * \param event_source being [un]subscribed [from]to
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+typedef enum stasis_app_subscribe_res (*app_subscription_handler)(
+ struct stasis_app *app, const char *uri,
+ struct stasis_app_event_source *event_source);
+
+/*!
+ * \internal
+ * \brief Subscriptions handler for application [un]subscribing.
+ *
+ * \param app_name Name of the application to subscribe.
+ * \param event_source_uris URIs for the event sources to subscribe to.
+ * \param event_sources_count Array size of event_source_uris.
+ * \param json Optional output pointer for JSON representation of the app
+ * after adding the subscription.
+ * \param handler [un]subscribe handler
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+static enum stasis_app_subscribe_res app_handle_subscriptions(
+ const char *app_name, const char **event_source_uris,
+ int event_sources_count, struct ast_json **json,
+ app_subscription_handler handler)
+{
+ RAII_VAR(struct stasis_app *, app, find_app_by_name(app_name), ao2_cleanup);
+ int i;
if (!app) {
- ast_log(LOG_WARNING, "Could not find app '%s'\n",
- app_name ? : "(null)");
return STASIS_ASR_APP_NOT_FOUND;
}
- event_sources = ast_calloc(event_sources_count, sizeof(*event_sources));
- if (!event_sources) {
- return STASIS_ASR_INTERNAL_ERROR;
- }
-
- for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
+ for (i = 0; i < event_sources_count; ++i) {
const char *uri = event_source_uris[i];
- ast_debug(3, "%s: Checking %s\n", app_name,
- uri);
- if (ast_begins_with(uri, CHANNEL_SCHEME)) {
- event_sources[i].event_source_type =
- EVENT_SOURCE_CHANNEL;
- event_sources[i].channel = ast_channel_get_by_name(
- uri + strlen(CHANNEL_SCHEME));
- if (!event_sources[i].channel) {
- ast_log(LOG_WARNING, "Channel not found: %s\n", uri);
- res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
- }
- } else if (ast_begins_with(uri, BRIDGE_SCHEME)) {
- event_sources[i].event_source_type =
- EVENT_SOURCE_BRIDGE;
- event_sources[i].bridge = stasis_app_bridge_find_by_id(
- uri + strlen(BRIDGE_SCHEME));
- if (!event_sources[i].bridge) {
- ast_log(LOG_WARNING, "Bridge not found: %s\n", uri);
- res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
- }
- } else if (ast_begins_with(uri, ENDPOINT_SCHEME)) {
- event_sources[i].event_source_type =
- EVENT_SOURCE_ENDPOINT;
- event_sources[i].endpoint = ast_endpoint_find_by_id(
- uri + strlen(ENDPOINT_SCHEME));
- if (!event_sources[i].endpoint) {
- ast_log(LOG_WARNING, "Endpoint not found: %s\n", uri);
- res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
- }
- } else {
+ enum stasis_app_subscribe_res res = STASIS_ASR_INTERNAL_ERROR;
+ struct stasis_app_event_source *event_source;
+
+ if (!(event_source = app_event_source_find(uri))) {
ast_log(LOG_WARNING, "Invalid scheme: %s\n", uri);
- res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
+ return STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
}
- }
- for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
- int sub_res = -1;
- ast_debug(1, "%s: Subscribing to %s\n", app_name,
- event_source_uris[i]);
-
- switch (event_sources[i].event_source_type) {
- case EVENT_SOURCE_CHANNEL:
- sub_res = app_subscribe_channel(app,
- event_sources[i].channel);
- break;
- case EVENT_SOURCE_BRIDGE:
- sub_res = app_subscribe_bridge(app,
- event_sources[i].bridge);
- break;
- case EVENT_SOURCE_ENDPOINT:
- sub_res = app_subscribe_endpoint(app,
- event_sources[i].endpoint);
- break;
+ if (handler &&
+ ((res = handler(app, uri, event_source)))) {
+ return res;
}
+ }
- if (sub_res != 0) {
- ast_log(LOG_WARNING,
- "Error subscribing app '%s' to '%s'\n",
- app_name, event_source_uris[i]);
- res = STASIS_ASR_INTERNAL_ERROR;
- }
+ if (json) {
+ ast_debug(3, "%s: Successful; setting results\n", app_name);
+ *json = stasis_app_object_to_json(app);
}
- if (res == STASIS_ASR_OK && json) {
- ast_debug(1, "%s: Successful; setting results\n", app_name);
- *json = app_to_json(app);
+ return STASIS_ASR_OK;
+}
+
+/*!
+ * \internal
+ * \brief Subscribe an app to an event source.
+ *
+ * \param app subscribing application
+ * \param uri scheme:id of an event source
+ * \param event_source being subscribed to
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+static enum stasis_app_subscribe_res app_subscribe(
+ struct stasis_app *app, const char *uri,
+ struct stasis_app_event_source *event_source)
+{
+ const char *app_name = stasis_app_name(app);
+ RAII_VAR(void *, obj, NULL, ao2_cleanup);
+
+ ast_debug(3, "%s: Checking %s\n", app_name, uri);
+
+ if (!event_source->find ||
+ (!(obj = event_source->find(app, uri + strlen(event_source->scheme))))) {
+ ast_log(LOG_WARNING, "Event source not found: %s\n", uri);
+ return STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
}
- for (i = 0; i < event_sources_count; ++i) {
- switch (event_sources[i].event_source_type) {
- case EVENT_SOURCE_CHANNEL:
- event_sources[i].channel =
- ast_channel_cleanup(event_sources[i].channel);
- break;
- case EVENT_SOURCE_BRIDGE:
- ao2_cleanup(event_sources[i].bridge);
- event_sources[i].bridge = NULL;
- break;
- case EVENT_SOURCE_ENDPOINT:
- ao2_cleanup(event_sources[i].endpoint);
- event_sources[i].endpoint = NULL;
- break;
- }
+ ast_debug(3, "%s: Subscribing to %s\n", app_name, uri);
+
+ if (!event_source->subscribe || (event_source->subscribe(app, obj))) {
+ ast_log(LOG_WARNING, "Error subscribing app '%s' to '%s'\n",
+ app_name, uri);
+ return STASIS_ASR_INTERNAL_ERROR;
}
- return res;
+ return STASIS_ASR_OK;
}
-enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name,
+enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name,
const char **event_source_uris, int event_sources_count,
struct ast_json **json)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
- enum stasis_app_subscribe_res res = STASIS_ASR_OK;
- int i;
+ return app_handle_subscriptions(
+ app_name, event_source_uris, event_sources_count,
+ json, app_subscribe);
+}
- if (app_name) {
- app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
- }
+/*!
+ * \internal
+ * \brief Unsubscribe an app from an event source.
+ *
+ * \param app application to unsubscribe
+ * \param uri scheme:id of an event source
+ * \param event_source being unsubscribed from
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+static enum stasis_app_subscribe_res app_unsubscribe(
+ struct stasis_app *app, const char *uri,
+ struct stasis_app_event_source *event_source)
+{
+ const char *app_name = stasis_app_name(app);
+ const char *id = uri + strlen(event_source->scheme);
- if (!app) {
- ast_log(LOG_WARNING, "Could not find app '%s'\n",
- app_name ? : "(null)");
- return STASIS_ASR_APP_NOT_FOUND;
+ if (!event_source->is_subscribed ||
+ (!event_source->is_subscribed(app, id))) {
+ return STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
}
- /* Validate the input */
- for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
- if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
- const char *channel_id = event_source_uris[i] +
- strlen(CHANNEL_SCHEME);
- if (!app_is_subscribed_channel_id(app, channel_id)) {
- res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
- }
- } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
- const char *bridge_id = event_source_uris[i] +
- strlen(BRIDGE_SCHEME);
- if (!app_is_subscribed_bridge_id(app, bridge_id)) {
- res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
- }
- } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
- const char *endpoint_id = event_source_uris[i] +
- strlen(ENDPOINT_SCHEME);
- if (!app_is_subscribed_endpoint_id(app, endpoint_id)) {
- res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
- }
- } else {
- res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
- }
- }
+ ast_debug(3, "%s: Unsubscribing from %s\n", app_name, uri);
- for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
- if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
- const char *channel_id = event_source_uris[i] +
- strlen(CHANNEL_SCHEME);
- app_unsubscribe_channel_id(app, channel_id);
- } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
- const char *bridge_id = event_source_uris[i] +
- strlen(BRIDGE_SCHEME);
- app_unsubscribe_bridge_id(app, bridge_id);
- } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
- const char *endpoint_id = event_source_uris[i] +
- strlen(ENDPOINT_SCHEME);
- app_unsubscribe_endpoint_id(app, endpoint_id);
- }
- }
-
- if (res == STASIS_ASR_OK && json) {
- *json = app_to_json(app);
+ if (!event_source->unsubscribe || (event_source->unsubscribe(app, id))) {
+ ast_log(LOG_WARNING, "Error unsubscribing app '%s' to '%s'\n",
+ app_name, uri);
+ return -1;
}
+ return 0;
+}
- return res;
+enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name,
+ const char **event_source_uris, int event_sources_count,
+ struct ast_json **json)
+{
+ return app_handle_subscriptions(
+ app_name, event_source_uris, event_sources_count,
+ json, app_unsubscribe);
}
void stasis_app_ref(void)
@@ -1150,6 +1201,8 @@ void stasis_app_unref(void)
static int unload_module(void)
{
+ stasis_app_unregister_event_sources();
+
ao2_cleanup(apps_registry);
apps_registry = NULL;
@@ -1206,6 +1259,8 @@ static int load_module(void)
return AST_MODULE_LOAD_FAILURE;
}
+ stasis_app_register_event_sources();
+
return AST_MODULE_LOAD_SUCCESS;
}