diff --git a/include/asterisk/devicestate.h b/include/asterisk/devicestate.h index a3267387f9925f29e2f7e8c653ef5c534fa37f40..565e013410c3737184445eda6d8e13a5f9bb597b 100644 --- a/include/asterisk/devicestate.h +++ b/include/asterisk/devicestate.h @@ -324,6 +324,15 @@ struct stasis_cache *ast_device_state_cache(void); */ struct stasis_message_type *ast_device_state_message_type(void); +/*! + * \brief Clear the device from the stasis cache. + * \param The device to clear + * \retval 0 if successful + * \retval -1 nothing to clear + * \since 12 + */ +int ast_device_state_clear_cache(const char *device); + /*! * \brief Initialize the device state core * \retval 0 Success diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index 4ef55b1937f60c1762d6e4b7dd0fa6fc0e790aea..0c22a6c309764c7adce9b9ab5b6a1191ca1de629 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -111,6 +111,18 @@ void stasis_app_unregister(const char *app_name); */ int stasis_app_send(const char *app_name, struct ast_json *message); +/*! \brief Forward declare app */ +struct stasis_app; + +/*! + * \brief Retrieve an application's name + * + * \param app An application + * + * \return The name of the application. + */ +const char *stasis_app_name(const struct stasis_app *app); + /*! * \brief Return the JSON representation of a Stasis application. * @@ -121,6 +133,102 @@ int stasis_app_send(const char *app_name, struct ast_json *message); */ struct ast_json *stasis_app_to_json(const char *app_name); +/*! + * \brief Event source information and callbacks. + */ +struct stasis_app_event_source { + /*! \brief The scheme to match against on [un]subscribes */ + const char *scheme; + + /*! + * \brief Find an event source data object by the given id/name. + * + * \param app Application + * \param id A unique identifier to search on + * + * \return The data object associated with the id/name. + */ + void *(*find)(const struct stasis_app *app, const char *id); + + /*! + * \brief Subscribe an application to an event source. + * + * \param app Application + * \param obj an event source data object + * + * \return 0 on success, failure code otherwise + */ + int (*subscribe)(struct stasis_app *app, void *obj); + + /*! + * \brief Cancel the subscription an app has to an event source. + * + * \param app Application + * \param id a previously subscribed object id + * + * \return 0 on success, failure code otherwise + */ + int (*unsubscribe)(struct stasis_app *app, const char *id); + + /*! + * \brief Find an event source by the given id/name. + * + * \param app Application + * \param id A unique identifier to check + * + * \return true if id is subscribed, false otherwise. + */ + int (*is_subscribed)(struct stasis_app *app, const char *id); + + /*! + * \brief Convert event source data to json + * + * \param app Application + * \param id json object to fill + */ + void (*to_json)(const struct stasis_app *app, struct ast_json *json); + + /*! Next item in the list */ + AST_LIST_ENTRY(stasis_app_event_source) next; +}; + +/*! + * \brief Register an application event source. + * + * \param obj the event source to register + */ +void stasis_app_register_event_source(struct stasis_app_event_source *obj); + +/*! + * \brief Register core event sources. + */ +void stasis_app_register_event_sources(void); + +/*! + * \brief Checks to see if the given object is a core event source + * + * \note core event sources are currently only endpoint, bridge, and channel. + * + * \param obj event source object to check + * + * \return non-zero if core event source, otherwise 0 (false) + + */ +int stasis_app_is_core_event_source(struct stasis_app_event_source *obj); + +/*! + * \brief Unregister an application event source. + * + * \param obj the event source to unregister + */ +void stasis_app_unregister_event_source(struct stasis_app_event_source *obj); + +/*! + * \brief Unregister core event sources. + */ +void stasis_app_unregister_event_sources(void); + + /*! \brief Return code for stasis_app_[un]subscribe */ enum stasis_app_subscribe_res { STASIS_ASR_OK, diff --git a/include/asterisk/stasis_app_device_state.h b/include/asterisk/stasis_app_device_state.h new file mode 100644 index 0000000000000000000000000000000000000000..2bc521a04d2b5fc45a043e5502b46c7f055e6fb4 --- /dev/null +++ b/include/asterisk/stasis_app_device_state.h @@ -0,0 +1,95 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@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. + */ + +#ifndef _ASTERISK_STASIS_APP_DEVICE_STATE_H +#define _ASTERISK_STASIS_APP_DEVICE_STATE_H + +/*! \file + * + * \brief Stasis Application Device State API. See \ref res_stasis "Stasis + * Application API" for detailed documentation. + * + * \author Kevin Harwell <kharwell@digium.com> + * \since 12 + */ + +#include "asterisk/app.h" +#include "asterisk/stasis_app.h" + +/*! @{ */ + +/*! + * \brief Convert device state to json. + * + * \param name the name of the device + * \param state the device state + * \return JSON representation. + * \return \c NULL on error. + */ +struct ast_json *stasis_app_device_state_to_json( + const char *name, enum ast_device_state state); + +/*! + * \brief Convert device states to json array. + * + * \return JSON representation. + * \return \c NULL on error. + */ +struct ast_json *stasis_app_device_states_to_json(void); + +/*! Stasis device state application result codes */ +enum stasis_device_state_result { + /*! Application controlled device state is okay */ + STASIS_DEVICE_STATE_OK, + /*! The device name is not application controlled */ + STASIS_DEVICE_STATE_NOT_CONTROLLED, + /*! The application controlled device name is missing */ + STASIS_DEVICE_STATE_MISSING, + /*! The application controlled device is unknown */ + STASIS_DEVICE_STATE_UNKNOWN, + /*! The application controlled device has subscribers */ + STASIS_DEVICE_STATE_SUBSCRIBERS +}; + +/*! + * \brief Changes the state of a device controlled by ARI. + * + * \note The controlled device must be prefixed with 'Stasis:'. + * \note Implicitly creates the device state. + * + * \param name the name of the ARI controlled device + * \param value a valid device state value + * + * \return a stasis device state application result. + */ +enum stasis_device_state_result stasis_app_device_state_update( + const char *name, const char *value); + +/*! + * \brief Delete a device controlled by ARI. + * + * \param name the name of the ARI controlled device + * + * \returna stasis device state application result. + */ +enum stasis_device_state_result stasis_app_device_state_delete( + const char *name); + +/*! @} */ + +#endif /* _ASTERISK_STASIS_APP_DEVICE_STATE_H */ diff --git a/main/devicestate.c b/main/devicestate.c index 158d1f817e8302108ddca561efba071a5be9e758..7b52f00af04cfdcd1145d8083ede8426ff43c2ee 100644 --- a/main/devicestate.c +++ b/main/devicestate.c @@ -734,6 +734,22 @@ struct stasis_topic *ast_device_state_topic(const char *device) return stasis_topic_pool_get_topic(device_state_topic_pool, device); } +int ast_device_state_clear_cache(const char *device) +{ + RAII_VAR(struct stasis_message *, cached_msg, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + + if (!(cached_msg = stasis_cache_get(ast_device_state_cache(), + ast_device_state_message_type(), device))) { + /* nothing to clear */ + return -1; + } + + msg = stasis_cache_clear_create(cached_msg); + stasis_publish(ast_device_state_topic(device), msg); + return 0; +} + int ast_publish_device_state_full( const char *device, enum ast_device_state state, diff --git a/res/ari.make b/res/ari.make index 0257f81a9b865a0fa560eb9b96c7ac2ae6d5f7b3..c5dac2d3c070502273ae190f7f9896ca9b7294e4 100644 --- a/res/ari.make +++ b/res/ari.make @@ -45,6 +45,10 @@ res_ari_playbacks.so: ari/resource_playbacks.o ari/resource_playbacks.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_playbacks) +res_ari_device_states.so: ari/resource_device_states.o + +ari/resource_device_states.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_device_states) + res_ari_events.so: ari/resource_events.o ari/resource_events.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_events) diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index 9ea2f94cdf5f93c970a91e00ea4866fa9ce3a402..f84a92ed76c6082e5e5b6bd361b503b7f6eec6f3 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -1333,6 +1333,60 @@ ari_validator ast_ari_validate_playback_fn(void) return ast_ari_validate_playback; } +int ast_ari_validate_device_state(struct ast_json *json) +{ + int res = 1; + struct ast_json_iter *iter; + int has_name = 0; + int has_state = 0; + + for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { + if (strcmp("name", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_name = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI DeviceState field name failed validation\n"); + res = 0; + } + } else + if (strcmp("state", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_state = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI DeviceState field state failed validation\n"); + res = 0; + } + } else + { + ast_log(LOG_ERROR, + "ARI DeviceState has undocumented field %s\n", + ast_json_object_iter_key(iter)); + res = 0; + } + } + + if (!has_name) { + ast_log(LOG_ERROR, "ARI DeviceState missing required field name\n"); + res = 0; + } + + if (!has_state) { + ast_log(LOG_ERROR, "ARI DeviceState missing required field state\n"); + res = 0; + } + + return res; +} + +ari_validator ast_ari_validate_device_state_fn(void) +{ + return ast_ari_validate_device_state; +} + int ast_ari_validate_application_replaced(struct ast_json *json) { int res = 1; @@ -2746,6 +2800,85 @@ ari_validator ast_ari_validate_channel_varset_fn(void) return ast_ari_validate_channel_varset; } +int ast_ari_validate_device_state_changed(struct ast_json *json) +{ + int res = 1; + struct ast_json_iter *iter; + int has_type = 0; + int has_application = 0; + int has_device_state = 0; + + for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) { + if (strcmp("type", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_type = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged field type failed validation\n"); + res = 0; + } + } else + if (strcmp("application", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_application = 1; + prop_is_valid = ast_ari_validate_string( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged field application failed validation\n"); + res = 0; + } + } else + if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_date( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged field timestamp failed validation\n"); + res = 0; + } + } else + if (strcmp("device_state", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_device_state = 1; + prop_is_valid = ast_ari_validate_device_state( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged field device_state failed validation\n"); + res = 0; + } + } else + { + ast_log(LOG_ERROR, + "ARI DeviceStateChanged has undocumented field %s\n", + ast_json_object_iter_key(iter)); + res = 0; + } + } + + if (!has_type) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field type\n"); + res = 0; + } + + if (!has_application) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field application\n"); + res = 0; + } + + if (!has_device_state) { + ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field device_state\n"); + res = 0; + } + + return res; +} + +ari_validator ast_ari_validate_device_state_changed_fn(void) +{ + return ast_ari_validate_device_state_changed; +} + int ast_ari_validate_endpoint_state_change(struct ast_json *json) { int res = 1; @@ -2887,6 +3020,9 @@ int ast_ari_validate_event(struct ast_json *json) if (strcmp("ChannelVarset", discriminator) == 0) { return ast_ari_validate_channel_varset(json); } else + if (strcmp("DeviceStateChanged", discriminator) == 0) { + return ast_ari_validate_device_state_changed(json); + } else if (strcmp("EndpointStateChange", discriminator) == 0) { return ast_ari_validate_endpoint_state_change(json); } else @@ -3025,6 +3161,9 @@ int ast_ari_validate_message(struct ast_json *json) if (strcmp("ChannelVarset", discriminator) == 0) { return ast_ari_validate_channel_varset(json); } else + if (strcmp("DeviceStateChanged", discriminator) == 0) { + return ast_ari_validate_device_state_changed(json); + } else if (strcmp("EndpointStateChange", discriminator) == 0) { return ast_ari_validate_endpoint_state_change(json); } else @@ -3592,6 +3731,7 @@ int ast_ari_validate_application(struct ast_json *json) struct ast_json_iter *iter; int has_bridge_ids = 0; int has_channel_ids = 0; + int has_device_names = 0; int has_endpoint_ids = 0; int has_name = 0; @@ -3618,6 +3758,17 @@ int ast_ari_validate_application(struct ast_json *json) res = 0; } } else + if (strcmp("device_names", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + has_device_names = 1; + prop_is_valid = ast_ari_validate_list( + ast_json_object_iter_value(iter), + ast_ari_validate_string); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI Application field device_names failed validation\n"); + res = 0; + } + } else if (strcmp("endpoint_ids", ast_json_object_iter_key(iter)) == 0) { int prop_is_valid; has_endpoint_ids = 1; @@ -3657,6 +3808,11 @@ int ast_ari_validate_application(struct ast_json *json) res = 0; } + if (!has_device_names) { + ast_log(LOG_ERROR, "ARI Application missing required field device_names\n"); + res = 0; + } + if (!has_endpoint_ids) { ast_log(LOG_ERROR, "ARI Application missing required field endpoint_ids\n"); res = 0; diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index a1e5b099e93a40e1f3493ba1d48892ef00558dcd..979fa4a47688001f403dc4af7b73e5a06e8f9ddc 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -480,6 +480,24 @@ int ast_ari_validate_playback(struct ast_json *json); */ ari_validator ast_ari_validate_playback_fn(void); +/*! + * \brief Validator for DeviceState. + * + * Represents the state of a device. + * + * \param json JSON object to validate. + * \returns True (non-zero) if valid. + * \returns False (zero) if invalid. + */ +int ast_ari_validate_device_state(struct ast_json *json); + +/*! + * \brief Function pointer to ast_ari_validate_device_state(). + * + * See \ref ast_ari_model_validators.h for more details. + */ +ari_validator ast_ari_validate_device_state_fn(void); + /*! * \brief Validator for ApplicationReplaced. * @@ -754,6 +772,24 @@ int ast_ari_validate_channel_varset(struct ast_json *json); */ ari_validator ast_ari_validate_channel_varset_fn(void); +/*! + * \brief Validator for DeviceStateChanged. + * + * Notification that a device state has changed. + * + * \param json JSON object to validate. + * \returns True (non-zero) if valid. + * \returns False (zero) if invalid. + */ +int ast_ari_validate_device_state_changed(struct ast_json *json); + +/*! + * \brief Function pointer to ast_ari_validate_device_state_changed(). + * + * See \ref ast_ari_model_validators.h for more details. + */ +ari_validator ast_ari_validate_device_state_changed_fn(void); + /*! * \brief Validator for EndpointStateChange. * @@ -1052,6 +1088,9 @@ ari_validator ast_ari_validate_application_fn(void); * - media_uri: string (required) * - state: string (required) * - target_uri: string (required) + * DeviceState + * - name: string (required) + * - state: string (required) * ApplicationReplaced * - type: string (required) * - application: string (required) @@ -1143,6 +1182,11 @@ ari_validator ast_ari_validate_application_fn(void); * - channel: Channel * - value: string (required) * - variable: string (required) + * DeviceStateChanged + * - type: string (required) + * - application: string (required) + * - timestamp: Date + * - device_state: DeviceState (required) * EndpointStateChange * - type: string (required) * - application: string (required) @@ -1187,6 +1231,7 @@ ari_validator ast_ari_validate_application_fn(void); * Application * - bridge_ids: List[string] (required) * - channel_ids: List[string] (required) + * - device_names: List[string] (required) * - endpoint_ids: List[string] (required) * - name: string (required) */ diff --git a/res/ari/resource_applications.h b/res/ari/resource_applications.h index 12aacb3306face4fd8c25dfad0ecc35c37bd7928..6aebd07fd530bfb366fc4eaf6c2de3b3c46f5fd9 100644 --- a/res/ari/resource_applications.h +++ b/res/ari/resource_applications.h @@ -67,7 +67,7 @@ void ast_ari_applications_get(struct ast_variable *headers, struct ast_ari_appli struct ast_ari_applications_subscribe_args { /*! \brief Application's name */ const char *application_name; - /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource} */ + /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName} */ const char **event_source; /*! \brief Length of event_source array. */ size_t event_source_count; @@ -88,7 +88,7 @@ void ast_ari_applications_subscribe(struct ast_variable *headers, struct ast_ari struct ast_ari_applications_unsubscribe_args { /*! \brief Application's name */ const char *application_name; - /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource} */ + /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, device_state:{deviceName} */ const char **event_source; /*! \brief Length of event_source array. */ size_t event_source_count; diff --git a/res/ari/resource_device_states.c b/res/ari/resource_device_states.c new file mode 100644 index 0000000000000000000000000000000000000000..50b87674028a2e729bcc190b4ddebe010b684110 --- /dev/null +++ b/res/ari/resource_device_states.c @@ -0,0 +1,111 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@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 + * + * \brief /api-docs/deviceStates.{format} implementation- Device state resources + * + * \author Kevin Harwell <kharwell@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_device_states.h" +#include "asterisk/stasis_app_device_state.h" + +void ast_ari_device_states_list( + struct ast_variable *headers, + struct ast_ari_device_states_list_args *args, + struct ast_ari_response *response) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + + if (!(json = stasis_app_device_states_to_json())) { + ast_ari_response_error(response, 500, + "Internal Server Error", "Error building response"); + return; + } + + ast_ari_response_ok(response, json); +} + +void ast_ari_device_states_get(struct ast_variable *headers, + struct ast_ari_device_states_get_args *args, + struct ast_ari_response *response) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + + if (!(json = stasis_app_device_state_to_json( + args->device_name, ast_device_state(args->device_name)))) { + ast_ari_response_error(response, 500, + "Internal Server Error", "Error building response"); + return; + } + + ast_ari_response_ok(response, json); +} + +void ast_ari_device_states_update(struct ast_variable *headers, + struct ast_ari_device_states_update_args *args, + struct ast_ari_response *response) +{ + switch (stasis_app_device_state_update( + args->device_name, args->device_state)) { + case STASIS_DEVICE_STATE_NOT_CONTROLLED: + ast_ari_response_error(response, 409, + "Conflict", "Uncontrolled device specified"); + return; + case STASIS_DEVICE_STATE_MISSING: + ast_ari_response_error(response, 404, + "Not Found", "Device name is missing"); + return; + case STASIS_DEVICE_STATE_UNKNOWN: + ast_ari_response_error(response, 500, "Internal Server Error", + "Unknown device"); + return; + case STASIS_DEVICE_STATE_OK: + case STASIS_DEVICE_STATE_SUBSCRIBERS: /* shouldn't be returned for update */ + ast_ari_response_no_content(response); + } +} + +void ast_ari_device_states_delete(struct ast_variable *headers, + struct ast_ari_device_states_delete_args *args, + struct ast_ari_response *response) +{ + switch (stasis_app_device_state_delete(args->device_name)) { + case STASIS_DEVICE_STATE_NOT_CONTROLLED: + ast_ari_response_error(response, 409, + "Conflict", "Uncontrolled device specified"); + return; + case STASIS_DEVICE_STATE_MISSING: + ast_ari_response_error(response, 404, + "Not Found", "Device name is missing"); + return; + case STASIS_DEVICE_STATE_SUBSCRIBERS: + ast_ari_response_error(response, 500, + "Internal Server Error", + "Cannot delete device with subscribers"); + return; + case STASIS_DEVICE_STATE_OK: + case STASIS_DEVICE_STATE_UNKNOWN: + ast_ari_response_no_content(response); + } +} diff --git a/res/ari/resource_device_states.h b/res/ari/resource_device_states.h new file mode 100644 index 0000000000000000000000000000000000000000..a3bac999c1b489463c9cade4ecb998f2413dd3dc --- /dev/null +++ b/res/ari/resource_device_states.h @@ -0,0 +1,95 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@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 + * + * \brief Generated file - declares stubs to be implemented in + * res/ari/resource_deviceStates.c + * + * Device state resources + * + * \author Kevin Harwell <kharwell@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/ari_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_DEVICESTATES_H +#define _ASTERISK_RESOURCE_DEVICESTATES_H + +#include "asterisk/ari.h" + +/*! \brief Argument struct for ast_ari_device_states_list() */ +struct ast_ari_device_states_list_args { +}; +/*! + * \brief List all ARI controlled device states. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_device_states_list(struct ast_variable *headers, struct ast_ari_device_states_list_args *args, struct ast_ari_response *response); +/*! \brief Argument struct for ast_ari_device_states_get() */ +struct ast_ari_device_states_get_args { + /*! \brief Name of the device */ + const char *device_name; +}; +/*! + * \brief Retrieve the current state of a device. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_device_states_get(struct ast_variable *headers, struct ast_ari_device_states_get_args *args, struct ast_ari_response *response); +/*! \brief Argument struct for ast_ari_device_states_update() */ +struct ast_ari_device_states_update_args { + /*! \brief Name of the device */ + const char *device_name; + /*! \brief Device state value */ + const char *device_state; +}; +/*! + * \brief Change the state of a device controlled by ARI. (Note - implicitly creates the device state). + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_device_states_update(struct ast_variable *headers, struct ast_ari_device_states_update_args *args, struct ast_ari_response *response); +/*! \brief Argument struct for ast_ari_device_states_delete() */ +struct ast_ari_device_states_delete_args { + /*! \brief Name of the device */ + const char *device_name; +}; +/*! + * \brief Destroy a device-state controlled by ARI. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_device_states_delete(struct ast_variable *headers, struct ast_ari_device_states_delete_args *args, struct ast_ari_response *response); + +#endif /* _ASTERISK_RESOURCE_DEVICESTATES_H */ diff --git a/res/res_ari_device_states.c b/res/res_ari_device_states.c new file mode 100644 index 0000000000000000000000000000000000000000..07f51ba3e96fdbdc1cf8ee7a914e9b8139d7c905 --- /dev/null +++ b/res/res_ari_device_states.c @@ -0,0 +1,323 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_ari_resource.c.mustache + */ + +/*! \file + * + * \brief Device state resources + * + * \author Kevin Harwell <kharwell@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_ari</depend> + <depend type="module">res_stasis</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/app.h" +#include "asterisk/module.h" +#include "asterisk/stasis_app.h" +#include "ari/resource_device_states.h" +#if defined(AST_DEVMODE) +#include "ari/ari_model_validators.h" +#endif + +#define MAX_VALS 128 + +/*! + * \brief Parameter parsing callback for /deviceStates. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_device_states_list_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_ari_device_states_list_args args = {}; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + ast_ari_device_states_list(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_list(response->message, + ast_ari_validate_device_state_fn()); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /deviceStates\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /deviceStates\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +/*! + * \brief Parameter parsing callback for /deviceStates/{deviceName}. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_device_states_get_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_ari_device_states_get_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "deviceName") == 0) { + args.device_name = (i->value); + } else + {} + } + ast_ari_device_states_get(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_device_state( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /deviceStates/{deviceName}\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /deviceStates/{deviceName}\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +/*! + * \brief Parameter parsing callback for /deviceStates/{deviceName}. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_device_states_update_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_ari_device_states_update_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "deviceState") == 0) { + args.device_state = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "deviceName") == 0) { + args.device_name = (i->value); + } else + {} + } + ast_ari_device_states_update(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + case 404: /* Device name is missing */ + case 409: /* Uncontrolled device specified */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_void( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /deviceStates/{deviceName}\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /deviceStates/{deviceName}\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +/*! + * \brief Parameter parsing callback for /deviceStates/{deviceName}. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_device_states_delete_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_ari_device_states_delete_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "deviceName") == 0) { + args.device_name = (i->value); + } else + {} + } + ast_ari_device_states_delete(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + case 404: /* Device name is missing */ + case 409: /* Uncontrolled device specified */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_void( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /deviceStates/{deviceName}\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /deviceStates/{deviceName}\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} + +/*! \brief REST handler for /api-docs/deviceStates.{format} */ +static struct stasis_rest_handlers deviceStates_deviceName = { + .path_segment = "deviceName", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = ast_ari_device_states_get_cb, + [AST_HTTP_PUT] = ast_ari_device_states_update_cb, + [AST_HTTP_DELETE] = ast_ari_device_states_delete_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/deviceStates.{format} */ +static struct stasis_rest_handlers deviceStates = { + .path_segment = "deviceStates", + .callbacks = { + [AST_HTTP_GET] = ast_ari_device_states_list_cb, + }, + .num_children = 1, + .children = { &deviceStates_deviceName, } +}; + +static int load_module(void) +{ + int res = 0; + stasis_app_ref(); + res |= ast_ari_add_handler(&deviceStates); + return res; +} + +static int unload_module(void) +{ + ast_ari_remove_handler(&deviceStates); + stasis_app_unref(); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "RESTful API module - Device state resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_ari,res_stasis", + ); diff --git a/res/res_stasis.c b/res/res_stasis.c index e21941210b764c7cec19e81dbe3d62fdfac22b41..691462722e549eea34944b771ec83a2f41ce0000 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; } diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c new file mode 100644 index 0000000000000000000000000000000000000000..e6d339107ca409081c96cba9a91756ff72ece0df --- /dev/null +++ b/res/res_stasis_device_state.c @@ -0,0 +1,416 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kevin Harwell <kharwell@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. + */ + +/*** MODULEINFO + <depend type="module">res_stasis</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/astdb.h" +#include "asterisk/astobj2.h" +#include "asterisk/module.h" +#include "asterisk/stasis_app_impl.h" +#include "asterisk/stasis_app_device_state.h" + +#define DEVICE_STATE_SIZE 64 +/*! astdb family name */ +#define DEVICE_STATE_FAMILY "StasisDeviceState" +/*! Stasis device state provider */ +#define DEVICE_STATE_PROVIDER_STASIS "Stasis" +/*! Scheme for custom device states */ +#define DEVICE_STATE_SCHEME_STASIS "Stasis:" +/*! Scheme for device state subscriptions */ +#define DEVICE_STATE_SCHEME_SUB "device_state:" + +/*! Number of hash buckets for device state subscriptions */ +#define DEVICE_STATE_BUCKETS 37 + +/*! Container for subscribed device states */ +static struct ao2_container *device_state_subscriptions; + +/*! + * \brief Device state subscription object. + */ +struct device_state_subscription { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(app_name); + AST_STRING_FIELD(device_name); + ); + /*! The subscription object */ + struct stasis_subscription *sub; +}; + +static int device_state_subscriptions_hash(const void *obj, const int flags) +{ + const struct device_state_subscription *object; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + object = obj; + return ast_str_hash(object->device_name); + case OBJ_SEARCH_KEY: + default: + /* Hash can only work on something with a full key. */ + ast_assert(0); + return 0; + } +} + +static int device_state_subscriptions_cmp(void *obj, void *arg, int flags) +{ + const struct device_state_subscription *object_left = obj; + const struct device_state_subscription *object_right = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + /* find objects matching both device and app names */ + if (strcmp(object_left->device_name, + object_right->device_name)) { + return 0; + } + cmp = strcmp(object_left->app_name, object_right->app_name); + break; + case OBJ_SEARCH_KEY: + case OBJ_SEARCH_PARTIAL_KEY: + ast_assert(0); /* not supported by container */ + /* fall through */ + default: + cmp = 0; + break; + } + + return cmp ? 0 : CMP_MATCH | CMP_STOP; +} + +static void device_state_subscription_destroy(void *obj) +{ + struct device_state_subscription *sub = obj; + sub->sub = stasis_unsubscribe(sub->sub); + ast_string_field_free_memory(sub); +} + +static struct device_state_subscription *device_state_subscription_create( + const struct stasis_app *app, const char *device_name) +{ + struct device_state_subscription *sub = ao2_alloc( + sizeof(*sub), device_state_subscription_destroy); + const char *app_name = stasis_app_name(app); + size_t size = strlen(device_name) + strlen(app_name) + 2; + + if (!sub) { + return NULL; + } + + if (ast_string_field_init(sub, size)) { + ao2_ref(sub, -1); + return NULL; + } + + ast_string_field_set(sub, app_name, app_name); + ast_string_field_set(sub, device_name, device_name); + return sub; +} + +static struct device_state_subscription *find_device_state_subscription( + struct stasis_app *app, const char *name) +{ + struct device_state_subscription dummy_sub = { + .app_name = stasis_app_name(app), + .device_name = name + }; + + return ao2_find(device_state_subscriptions, &dummy_sub, OBJ_SEARCH_OBJECT); +} + +static void remove_device_state_subscription( + struct device_state_subscription *sub) +{ + ao2_unlink(device_state_subscriptions, sub); +} + +struct ast_json *stasis_app_device_state_to_json( + const char *name, enum ast_device_state state) +{ + return ast_json_pack("{s: s, s: s}", + "name", name, + "state", ast_devstate_str(state)); +} + +struct ast_json *stasis_app_device_states_to_json(void) +{ + struct ast_json *array = ast_json_array_create(); + RAII_VAR(struct ast_db_entry *, tree, + ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree); + struct ast_db_entry *entry; + + for (entry = tree; entry; entry = entry->next) { + const char *name = strrchr(entry->key, '/'); + if (!ast_strlen_zero(name)) { + struct ast_str *device = ast_str_alloca(DEVICE_STATE_SIZE); + ast_str_set(&device, 0, "%s%s", + DEVICE_STATE_SCHEME_STASIS, ++name); + ast_json_array_append( + array, stasis_app_device_state_to_json( + ast_str_buffer(device), + ast_device_state(ast_str_buffer(device)))); + } + } + + return array; +} + +static void send_device_state(struct device_state_subscription *sub, + const char *name, enum ast_device_state state) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + + json = ast_json_pack("{s:s, s:s, s:o, s:o}", + "type", "DeviceStateChanged", + "application", sub->app_name, + "timestamp", ast_json_timeval(ast_tvnow(), NULL), + "device_state", stasis_app_device_state_to_json( + name, state)); + + if (!json) { + ast_log(LOG_ERROR, "Unable to create device state json object\n"); + return; + } + + stasis_app_send(sub->app_name, json); +} + +enum stasis_device_state_result stasis_app_device_state_update( + const char *name, const char *value) +{ + size_t size = strlen(DEVICE_STATE_SCHEME_STASIS); + enum ast_device_state state; + + ast_debug(3, "Updating device name = %s, value = %s", name, value); + + if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) { + ast_log(LOG_ERROR, "Update can only be used to set " + "'%s' device state!\n", DEVICE_STATE_SCHEME_STASIS); + return STASIS_DEVICE_STATE_NOT_CONTROLLED; + } + + name += size; + if (ast_strlen_zero(name)) { + ast_log(LOG_ERROR, "Update requires custom device name!\n"); + return STASIS_DEVICE_STATE_MISSING; + } + + if (!value || (state = ast_devstate_val(value)) == AST_DEVICE_UNKNOWN) { + ast_log(LOG_ERROR, "Unknown device state " + "value '%s'\n", value); + return STASIS_DEVICE_STATE_UNKNOWN; + } + + ast_db_put(DEVICE_STATE_FAMILY, name, value); + ast_devstate_changed(state, AST_DEVSTATE_CACHABLE, "%s%s", + DEVICE_STATE_SCHEME_STASIS, name); + + return STASIS_DEVICE_STATE_OK; +} + +enum stasis_device_state_result stasis_app_device_state_delete(const char *name) +{ + const char *full_name = name; + size_t size = strlen(DEVICE_STATE_SCHEME_STASIS); + + if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) { + ast_log(LOG_ERROR, "Can only delete '%s' device states!\n", + DEVICE_STATE_SCHEME_STASIS); + return STASIS_DEVICE_STATE_NOT_CONTROLLED; + } + + name += size; + if (ast_strlen_zero(name)) { + ast_log(LOG_ERROR, "Delete requires a device name!\n"); + return STASIS_DEVICE_STATE_MISSING; + } + + if (ast_device_state_clear_cache(full_name)) { + return STASIS_DEVICE_STATE_UNKNOWN; + } + + ast_db_del(DEVICE_STATE_FAMILY, name); + + /* send state change for delete */ + ast_devstate_changed( + AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "%s%s", + DEVICE_STATE_SCHEME_STASIS, name); + + return STASIS_DEVICE_STATE_OK; +} + +static void populate_cache(void) +{ + RAII_VAR(struct ast_db_entry *, tree, + ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree); + struct ast_db_entry *entry; + + for (entry = tree; entry; entry = entry->next) { + const char *name = strrchr(entry->key, '/'); + if (!ast_strlen_zero(name)) { + ast_devstate_changed( + ast_devstate_val(entry->data), + AST_DEVSTATE_CACHABLE, "%s%s\n", + DEVICE_STATE_SCHEME_STASIS, name + 1); + } + } +} + +static enum ast_device_state stasis_device_state_cb(const char *data) +{ + char buf[DEVICE_STATE_SIZE] = ""; + + ast_db_get(DEVICE_STATE_FAMILY, data, buf, sizeof(buf)); + + return ast_devstate_val(buf); +} + +static void device_state_cb(void *data, struct stasis_subscription *sub, + struct stasis_message *msg) +{ + struct ast_device_state_message *device_state; + + if (ast_device_state_message_type() != stasis_message_type(msg)) { + return; + } + + device_state = stasis_message_data(msg); + if (device_state->eid) { + /* ignore non-aggregate states */ + return; + } + + send_device_state(data, device_state->device, device_state->state); +} + +static void *find_device_state(const struct stasis_app *app, const char *name) +{ + return device_state_subscription_create(app, name); +} + +static int is_subscribed_device_state(struct stasis_app *app, const char *name) +{ + RAII_VAR(struct device_state_subscription *, sub, + find_device_state_subscription(app, name), ao2_cleanup); + return sub != NULL; +} + +static int subscribe_device_state(struct stasis_app *app, void *obj) +{ + struct device_state_subscription *sub = obj; + + ast_debug(3, "Subscribing to device %s", sub->device_name); + + if (is_subscribed_device_state(app, sub->device_name)) { + ast_log(LOG_WARNING, "Already subscribed to %s\n", sub->device_name); + return -1; + } + + if (!(sub->sub = stasis_subscribe( + ast_device_state_topic(sub->device_name), + device_state_cb, sub))) { + ast_log(LOG_ERROR, "Unable to subscribe to device %s\n", + sub->device_name); + return -1; + } + + ao2_link(device_state_subscriptions, sub); + return 0; +} + +static int unsubscribe_device_state(struct stasis_app *app, const char *name) +{ + RAII_VAR(struct device_state_subscription *, sub, + find_device_state_subscription(app, name), ao2_cleanup); + remove_device_state_subscription(sub); + return 0; +} + +static int device_to_json_cb(void *obj, void *arg, void *data, int flags) +{ + struct device_state_subscription *sub = obj; + const char *app_name = arg; + struct ast_json *array = data; + + if (strcmp(sub->app_name, app_name)) { + return 0; + } + + ast_json_array_append( + array, ast_json_string_create(sub->device_name)); + return 0; + +} + +static void devices_to_json(const struct stasis_app *app, struct ast_json *json) +{ + struct ast_json *array = ast_json_array_create(); + ao2_callback_data(device_state_subscriptions, OBJ_NODATA, + device_to_json_cb, (void *)stasis_app_name(app), array); + ast_json_object_set(json, "device_names", array); +} + +struct stasis_app_event_source device_state_event_source = { + .scheme = DEVICE_STATE_SCHEME_SUB, + .find = find_device_state, + .subscribe = subscribe_device_state, + .unsubscribe = unsubscribe_device_state, + .is_subscribed = is_subscribed_device_state, + .to_json = devices_to_json +}; + +static int load_module(void) +{ + populate_cache(); + if (ast_devstate_prov_add(DEVICE_STATE_PROVIDER_STASIS, + stasis_device_state_cb)) { + return AST_MODULE_LOAD_FAILURE; + } + + if (!(device_state_subscriptions = ao2_container_alloc( + DEVICE_STATE_BUCKETS, device_state_subscriptions_hash, + device_state_subscriptions_cmp))) { + return AST_MODULE_LOAD_FAILURE; + } + + stasis_app_register_event_source(&device_state_event_source); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_devstate_prov_del(DEVICE_STATE_PROVIDER_STASIS); + stasis_app_unregister_event_source(&device_state_event_source); + ao2_cleanup(device_state_subscriptions); + device_state_subscriptions = NULL; + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application device state support", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis"); diff --git a/res/res_stasis_device_state.exports.in b/res/res_stasis_device_state.exports.in new file mode 100644 index 0000000000000000000000000000000000000000..0ad493c49ec08d12ba952400b7fb25a40a8dcd98 --- /dev/null +++ b/res/res_stasis_device_state.exports.in @@ -0,0 +1,6 @@ +{ + global: + LINKER_SYMBOL_PREFIXstasis_app_*; + local: + *; +}; diff --git a/res/stasis/app.c b/res/stasis/app.c index 433d3adb5324a8dcc6f63d65f593e0f9f32368aa..8ad41e565bae337b109a375b305c7bd201b1c4b0 100644 --- a/res/stasis/app.c +++ b/res/stasis/app.c @@ -36,7 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_endpoints.h" #include "asterisk/stasis_message_router.h" -struct app { +struct stasis_app { /*! Aggregation topic for this application. */ struct stasis_topic *topic; /*! Router for handling messages forwarded to \a topic. */ @@ -93,7 +93,7 @@ static void forwards_unsubscribe(struct app_forwards *forwards) forwards->topic_cached_forward = NULL; } -static struct app_forwards *forwards_create(struct app *app, +static struct app_forwards *forwards_create(struct stasis_app *app, const char *id) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); @@ -114,7 +114,7 @@ static struct app_forwards *forwards_create(struct app *app, } /*! Forward a channel's topics to an app */ -static struct app_forwards *forwards_create_channel(struct app *app, +static struct app_forwards *forwards_create_channel(struct stasis_app *app, struct ast_channel *chan) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); @@ -149,7 +149,7 @@ static struct app_forwards *forwards_create_channel(struct app *app, } /*! Forward a bridge's topics to an app */ -static struct app_forwards *forwards_create_bridge(struct app *app, +static struct app_forwards *forwards_create_bridge(struct stasis_app *app, struct ast_bridge *bridge) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); @@ -184,7 +184,7 @@ static struct app_forwards *forwards_create_bridge(struct app *app, } /*! Forward a endpoint's topics to an app */ -static struct app_forwards *forwards_create_endpoint(struct app *app, +static struct app_forwards *forwards_create_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); @@ -250,7 +250,7 @@ static int forwards_sort(const void *obj_left, const void *obj_right, int flags) static void app_dtor(void *obj) { - struct app *app = obj; + struct stasis_app *app = obj; ast_verb(1, "Destroying Stasis app %s\n", app->name); @@ -268,7 +268,7 @@ static void app_dtor(void *obj) static void sub_default_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { - struct app *app = data; + struct stasis_app *app = data; RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); if (stasis_subscription_final_message(sub, message)) { @@ -435,7 +435,7 @@ static void sub_channel_update_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { - struct app *app = data; + struct stasis_app *app = data; struct stasis_cache_update *update; struct ast_channel_snapshot *new_snapshot; struct ast_channel_snapshot *old_snapshot; @@ -489,7 +489,7 @@ static void sub_endpoint_update_handler(void *data, struct stasis_message *message) { RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); - struct app *app = data; + struct stasis_app *app = data; struct stasis_cache_update *update; struct ast_endpoint_snapshot *new_snapshot; const struct timeval *tv; @@ -535,7 +535,7 @@ static void sub_bridge_update_handler(void *data, struct stasis_message *message) { RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); - struct app *app = data; + struct stasis_app *app = data; struct stasis_cache_update *update; struct ast_bridge_snapshot *new_snapshot; struct ast_bridge_snapshot *old_snapshot; @@ -569,7 +569,7 @@ static void sub_bridge_update_handler(void *data, static void bridge_merge_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { - struct app *app = data; + struct stasis_app *app = data; struct ast_bridge_merge_message *merge; RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); @@ -599,9 +599,9 @@ static void bridge_merge_handler(void *data, struct stasis_subscription *sub, stasis_publish(app->topic, message); } -struct app *app_create(const char *name, stasis_app_cb handler, void *data) +struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); + RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup); size_t size; int res = 0; @@ -674,7 +674,7 @@ struct app *app_create(const char *name, stasis_app_cb handler, void *data) * \param app App to send the message to. * \param message Message to send. */ -void app_send(struct app *app, struct ast_json *message) +void app_send(struct stasis_app *app, struct ast_json *message) { stasis_app_cb handler; RAII_VAR(void *, data, NULL, ao2_cleanup); @@ -699,7 +699,7 @@ void app_send(struct app *app, struct ast_json *message) handler(data, app->name, message); } -void app_deactivate(struct app *app) +void app_deactivate(struct stasis_app *app) { SCOPED_AO2LOCK(lock, app); ast_verb(1, "Deactivating Stasis app '%s'\n", app->name); @@ -708,7 +708,7 @@ void app_deactivate(struct app *app) app->data = NULL; } -void app_shutdown(struct app *app) +void app_shutdown(struct stasis_app *app) { SCOPED_AO2LOCK(lock, app); @@ -720,20 +720,20 @@ void app_shutdown(struct app *app) app->bridge_merge_sub = NULL; } -int app_is_active(struct app *app) +int app_is_active(struct stasis_app *app) { SCOPED_AO2LOCK(lock, app); return app->handler != NULL; } -int app_is_finished(struct app *app) +int app_is_finished(struct stasis_app *app) { SCOPED_AO2LOCK(lock, app); return app->handler == NULL && ao2_container_count(app->forwards) == 0; } -void app_update(struct app *app, stasis_app_cb handler, void *data) +void app_update(struct stasis_app *app, stasis_app_cb handler, void *data) { SCOPED_AO2LOCK(lock, app); @@ -760,12 +760,12 @@ void app_update(struct app *app, stasis_app_cb handler, void *data) app->data = data; } -const char *app_name(const struct app *app) +const char *app_name(const struct stasis_app *app) { return app->name; } -struct ast_json *app_to_json(const struct app *app) +struct ast_json *app_to_json(const struct stasis_app *app) { RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); struct ast_json *channels; @@ -815,7 +815,7 @@ struct ast_json *app_to_json(const struct app *app) return ast_json_ref(json); } -int app_subscribe_channel(struct app *app, struct ast_channel *chan) +int app_subscribe_channel(struct stasis_app *app, struct ast_channel *chan) { int res; @@ -846,7 +846,12 @@ int app_subscribe_channel(struct app *app, struct ast_channel *chan) } } -static int unsubscribe(struct app *app, const char *kind, const char *id) +static int subscribe_channel(struct stasis_app *app, void *obj) +{ + return app_subscribe_channel(app, obj); +} + +static int unsubscribe(struct stasis_app *app, const char *kind, const char *id) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); SCOPED_AO2LOCK(lock, app->forwards); @@ -870,7 +875,7 @@ static int unsubscribe(struct app *app, const char *kind, const char *id) return 0; } -int app_unsubscribe_channel(struct app *app, struct ast_channel *chan) +int app_unsubscribe_channel(struct stasis_app *app, struct ast_channel *chan) { if (!app || !chan) { return -1; @@ -879,7 +884,7 @@ int app_unsubscribe_channel(struct app *app, struct ast_channel *chan) return app_unsubscribe_channel_id(app, ast_channel_uniqueid(chan)); } -int app_unsubscribe_channel_id(struct app *app, const char *channel_id) +int app_unsubscribe_channel_id(struct stasis_app *app, const char *channel_id) { if (!app || !channel_id) { return -1; @@ -888,14 +893,27 @@ int app_unsubscribe_channel_id(struct app *app, const char *channel_id) return unsubscribe(app, "channel", channel_id); } -int app_is_subscribed_channel_id(struct app *app, const char *channel_id) +int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); forwards = ao2_find(app->forwards, channel_id, OBJ_SEARCH_KEY); return forwards != NULL; } -int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge) +static void *channel_find(const struct stasis_app *app, const char *id) +{ + return ast_channel_get_by_name(id); +} + +struct stasis_app_event_source channel_event_source = { + .scheme = "channel:", + .find = channel_find, + .subscribe = subscribe_channel, + .unsubscribe = app_unsubscribe_channel_id, + .is_subscribed = app_is_subscribed_channel_id +}; + +int app_subscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge) { if (!app || !bridge) { return -1; @@ -920,7 +938,12 @@ int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge) } } -int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge) +static int subscribe_bridge(struct stasis_app *app, void *obj) +{ + return app_subscribe_bridge(app, obj); +} + +int app_unsubscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge) { if (!app || !bridge) { return -1; @@ -929,7 +952,7 @@ int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge) return app_unsubscribe_bridge_id(app, bridge->uniqueid); } -int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id) +int app_unsubscribe_bridge_id(struct stasis_app *app, const char *bridge_id) { if (!app || !bridge_id) { return -1; @@ -938,14 +961,27 @@ int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id) return unsubscribe(app, "bridge", bridge_id); } -int app_is_subscribed_bridge_id(struct app *app, const char *bridge_id) +int app_is_subscribed_bridge_id(struct stasis_app *app, const char *bridge_id) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); forwards = ao2_find(app->forwards, bridge_id, OBJ_SEARCH_KEY); return forwards != NULL; } -int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint) +static void *bridge_find(const struct stasis_app *app, const char *id) +{ + return stasis_app_bridge_find_by_id(id); +} + +struct stasis_app_event_source bridge_event_source = { + .scheme = "bridge:", + .find = bridge_find, + .subscribe = subscribe_bridge, + .unsubscribe = app_unsubscribe_bridge_id, + .is_subscribed = app_is_subscribed_bridge_id +}; + +int app_subscribe_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint) { if (!app || !endpoint) { return -1; @@ -970,7 +1006,12 @@ int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint) } } -int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id) +static int subscribe_endpoint(struct stasis_app *app, void *obj) +{ + return app_subscribe_endpoint(app, obj); +} + +int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id) { if (!app || !endpoint_id) { return -1; @@ -979,9 +1020,45 @@ int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id) return unsubscribe(app, "endpoint", endpoint_id); } -int app_is_subscribed_endpoint_id(struct app *app, const char *endpoint_id) +int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id) { RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup); forwards = ao2_find(app->forwards, endpoint_id, OBJ_SEARCH_KEY); return forwards != NULL; } + +static void *endpoint_find(const struct stasis_app *app, const char *id) +{ + return ast_endpoint_find_by_id(id); +} + +struct stasis_app_event_source endpoint_event_source = { + .scheme = "endpoint:", + .find = endpoint_find, + .subscribe = subscribe_endpoint, + .unsubscribe = app_unsubscribe_endpoint_id, + .is_subscribed = app_is_subscribed_endpoint_id +}; + +void stasis_app_register_event_sources(void) +{ + stasis_app_register_event_source(&channel_event_source); + stasis_app_register_event_source(&bridge_event_source); + stasis_app_register_event_source(&endpoint_event_source); +} + +int stasis_app_is_core_event_source(struct stasis_app_event_source *obj) +{ + return obj == &endpoint_event_source || + obj == &bridge_event_source || + obj == &channel_event_source; +} + +void stasis_app_unregister_event_sources(void) +{ + stasis_app_unregister_event_source(&endpoint_event_source); + stasis_app_unregister_event_source(&bridge_event_source); + stasis_app_unregister_event_source(&channel_event_source); +} + + diff --git a/res/stasis/app.h b/res/stasis/app.h index 4db9db97d80d5763b7f4c1cd677227f789d9c50e..419ec54a85832bfb551c31a15fc2c46ca0e15891 100644 --- a/res/stasis/app.h +++ b/res/stasis/app.h @@ -34,7 +34,7 @@ /*! * \brief Opaque pointer to \c res_stasis app structure. */ -struct app; +struct stasis_app; /*! * \brief Create a res_stasis application. @@ -45,7 +45,7 @@ struct app; * \return New \c res_stasis application. * \return \c NULL on error. */ -struct app *app_create(const char *name, stasis_app_cb handler, void *data); +struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data); /*! * \brief Tears down an application. @@ -54,7 +54,7 @@ struct app *app_create(const char *name, stasis_app_cb handler, void *data); * * \param app Application to unsubscribe. */ -void app_shutdown(struct app *app); +void app_shutdown(struct stasis_app *app); /*! * \brief Deactivates an application. @@ -64,7 +64,7 @@ void app_shutdown(struct app *app); * * \param app Application to deactivate. */ -void app_deactivate(struct app *app); +void app_deactivate(struct stasis_app *app); /*! * \brief Checks whether an app is active. @@ -73,7 +73,7 @@ void app_deactivate(struct app *app); * \return True (non-zero) if app is active. * \return False (zero) if app has been deactivated. */ -int app_is_active(struct app *app); +int app_is_active(struct stasis_app *app); /*! * \brief Checks whether a deactivated app has no channels. @@ -82,7 +82,7 @@ int app_is_active(struct app *app); * \param True (non-zero) if app is deactivated, and has no associated channels. * \param False (zero) otherwise. */ -int app_is_finished(struct app *app); +int app_is_finished(struct stasis_app *app); /*! * \brief Update the handler and data for a \c res_stasis application. @@ -93,7 +93,7 @@ int app_is_finished(struct app *app); * \param handler New application callback. * \param data New data pointer for the callback. */ -void app_update(struct app *app, stasis_app_cb handler, void *data); +void app_update(struct stasis_app *app, stasis_app_cb handler, void *data); /*! * \brief Return an application's name. @@ -102,7 +102,7 @@ void app_update(struct app *app, stasis_app_cb handler, void *data); * \return Name of the application. * \return \c NULL is \a app is \c NULL. */ -const char *app_name(const struct app *app); +const char *app_name(const struct stasis_app *app); /*! * \brief Send a message to an application. @@ -110,11 +110,11 @@ const char *app_name(const struct app *app); * \param app Application. * \param message Message to send. */ -void app_send(struct app *app, struct ast_json *message); +void app_send(struct stasis_app *app, struct ast_json *message); struct app_forwards; -struct ast_json *app_to_json(const struct app *app); +struct ast_json *app_to_json(const struct stasis_app *app); /*! * \brief Subscribes an application to a channel. @@ -124,7 +124,7 @@ struct ast_json *app_to_json(const struct app *app); * \return 0 on success. * \return Non-zero on error. */ -int app_subscribe_channel(struct app *app, struct ast_channel *chan); +int app_subscribe_channel(struct stasis_app *app, struct ast_channel *chan); /*! * \brief Cancel the subscription an app has for a channel. @@ -134,7 +134,7 @@ int app_subscribe_channel(struct app *app, struct ast_channel *chan); * \return 0 on success. * \return Non-zero on error. */ -int app_unsubscribe_channel(struct app *app, struct ast_channel *chan); +int app_unsubscribe_channel(struct stasis_app *app, struct ast_channel *chan); /*! * \brief Cancel the subscription an app has for a channel. @@ -144,7 +144,7 @@ int app_unsubscribe_channel(struct app *app, struct ast_channel *chan); * \return 0 on success. * \return Non-zero on error. */ -int app_unsubscribe_channel_id(struct app *app, const char *channel_id); +int app_unsubscribe_channel_id(struct stasis_app *app, const char *channel_id); /*! * \brief Test if an app is subscribed to a channel. @@ -154,7 +154,7 @@ int app_unsubscribe_channel_id(struct app *app, const char *channel_id); * \return True (non-zero) if channel is subscribed to \a app. * \return False (zero) if channel is not subscribed. */ -int app_is_subscribed_channel_id(struct app *app, const char *channel_id); +int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id); /*! * \brief Add a bridge subscription to an existing channel subscription. @@ -164,7 +164,7 @@ int app_is_subscribed_channel_id(struct app *app, const char *channel_id); * \return 0 on success. * \return Non-zero on error. */ -int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge); +int app_subscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge); /*! * \brief Cancel the bridge subscription for an application. @@ -174,7 +174,7 @@ int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge); * \return 0 on success. * \return Non-zero on error. */ -int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge); +int app_unsubscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge); /*! * \brief Cancel the subscription an app has for a bridge. @@ -184,7 +184,7 @@ int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge); * \return 0 on success. * \return Non-zero on error. */ -int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id); +int app_unsubscribe_bridge_id(struct stasis_app *app, const char *bridge_id); /*! * \brief Test if an app is subscribed to a bridge. @@ -194,7 +194,7 @@ int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id); * \return True (non-zero) if bridge is subscribed to \a app. * \return False (zero) if bridge is not subscribed. */ -int app_is_subscribed_bridge_id(struct app *app, const char *bridge_id); +int app_is_subscribed_bridge_id(struct stasis_app *app, const char *bridge_id); /*! * \brief Subscribes an application to a endpoint. @@ -204,7 +204,7 @@ int app_is_subscribed_bridge_id(struct app *app, const char *bridge_id); * \return 0 on success. * \return Non-zero on error. */ -int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint); +int app_subscribe_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint); /*! * \brief Cancel the subscription an app has for a endpoint. @@ -214,7 +214,7 @@ int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint); * \return 0 on success. * \return Non-zero on error. */ -int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id); +int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id); /*! * \brief Test if an app is subscribed to a endpoint. @@ -224,6 +224,6 @@ int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id); * \return True (non-zero) if endpoint is subscribed to \a app. * \return False (zero) if endpoint is not subscribed. */ -int app_is_subscribed_endpoint_id(struct app *app, const char *endpoint_id); +int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id); #endif /* _ASTERISK_RES_STASIS_APP_H */ diff --git a/rest-api-templates/ari.make.mustache b/rest-api-templates/ari.make.mustache index 37f09c17966695059251416d6cd33eed893e24bb..50293cf11a3b21deb60f29091a648f328df92fb8 100644 --- a/rest-api-templates/ari.make.mustache +++ b/rest-api-templates/ari.make.mustache @@ -19,8 +19,8 @@ # {{#apis}} -res_ari_{{name}}.so: ari/resource_{{name}}.o +res_ari_{{c_name}}.so: ari/resource_{{c_name}}.o -ari/resource_{{name}}.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_{{name}}) +ari/resource_{{c_name}}.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_{{c_name}}) {{/apis}} diff --git a/rest-api/api-docs/applications.json b/rest-api/api-docs/applications.json index 494088dc51e43ef5aa6495d29d3e8628b439f9a2..cd27d586ec418f5b1ea2a17962f97397065070e9 100644 --- a/rest-api/api-docs/applications.json +++ b/rest-api/api-docs/applications.json @@ -68,7 +68,7 @@ }, { "name": "eventSource", - "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}", + "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName}", "paramType": "query", "required": true, "allowMultiple": true, @@ -107,7 +107,7 @@ }, { "name": "eventSource", - "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}", + "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, device_state:{deviceName}", "paramType": "query", "required": true, "allowMultiple": true, @@ -160,6 +160,11 @@ "type": "List[string]", "description": "{tech}/{resource} for endpoints subscribed to.", "required": true + }, + "device_names": { + "type": "List[string]", + "description": "Names of the devices subscribed to.", + "required": true } } } diff --git a/rest-api/api-docs/deviceStates.json b/rest-api/api-docs/deviceStates.json new file mode 100644 index 0000000000000000000000000000000000000000..529bc70da8e539877b46dc916a3b3e23b07470a3 --- /dev/null +++ b/rest-api/api-docs/deviceStates.json @@ -0,0 +1,151 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "Kevin Harwell <kharwell@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/deviceStates.{format}", + "apis": [ + { + "path": "/deviceStates", + "description": "Device states", + "operations": [ + { + "httpMethod": "GET", + "summary": "List all ARI controlled device states.", + "nickname": "list", + "responseClass": "List[DeviceState]" + } + ] + }, + { + "path": "/deviceStates/{deviceName}", + "description": "Device state", + "operations": [ + { + "httpMethod": "GET", + "summary": "Retrieve the current state of a device.", + "nickname": "get", + "responseClass": "DeviceState", + "parameters": [ + { + "name": "deviceName", + "description": "Name of the device", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + }, + { + "httpMethod": "PUT", + "summary": "Change the state of a device controlled by ARI. (Note - implicitly creates the device state).", + "nickname": "update", + "responseClass": "void", + "parameters": [ + { + "name": "deviceName", + "description": "Name of the device", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "deviceState", + "description": "Device state value", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string", + "allowableValues": { + "valueType": "LIST", + "values": [ + "NOT_INUSE", + "INUSE", + "BUSY", + "INVALID", + "UNAVAILABLE", + "RINGING", + "RINGINUSE", + "ONHOLD" + ] + } + + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Device name is missing" + }, + { + "code": 409, + "reason": "Uncontrolled device specified" + } + ] + }, + { + "httpMethod": "DELETE", + "summary": "Destroy a device-state controlled by ARI.", + "nickname": "delete", + "responseClass": "void", + "parameters": [ + { + "name": "deviceName", + "description": "Name of the device", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Device name is missing" + }, + { + "code": 409, + "reason": "Uncontrolled device specified" + } + ] + } + ] + } + ], + "models": { + "DeviceState": { + "id": "DeviceState", + "description": "Represents the state of a device.", + "properties": { + "name": { + "type": "string", + "description": "Name of the device.", + "required": true + }, + "state": { + "type": "string", + "description": "Device's state", + "required": true, + "allowableValues": { + "valueType": "LIST", + "values": [ + "UNKNOWN", + "NOT_INUSE", + "INUSE", + "BUSY", + "INVALID", + "UNAVAILABLE", + "RINGING", + "RINGINUSE", + "ONHOLD" + ] + } + } + } + } + } +} diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json index e30a193c1132bf4d8092bff08da09228224aa844..a342099a81fdd41a1f8845ce006ac4efeba095a6 100644 --- a/rest-api/api-docs/events.json +++ b/rest-api/api-docs/events.json @@ -76,6 +76,7 @@ } }, "subTypes": [ + "DeviceStateChanged", "PlaybackStarted", "PlaybackFinished", "ApplicationReplaced", @@ -98,6 +99,17 @@ "StasisStart" ] }, + "DeviceStateChanged": { + "id": "DeviceStateChanged", + "description": "Notification that a device state has changed.", + "properties": { + "device_state": { + "type": "DeviceState", + "description": "Device state object", + "required": true + } + } + }, "PlaybackStarted": { "id": "PlaybackStarted", "description": "Event showing the start of a media playback operation.", diff --git a/rest-api/resources.json b/rest-api/resources.json index 7e0b793fbb689276eacefcdbe58cc92bb2eac69a..495348a21b53d0ef4d532ce947a2e14dcfe71738 100644 --- a/rest-api/resources.json +++ b/rest-api/resources.json @@ -34,6 +34,10 @@ "path": "/api-docs/playbacks.{format}", "description": "Playback control resources" }, + { + "path": "/api-docs/deviceStates.{format}", + "description": "Device state resources" + }, { "path": "/api-docs/events.{format}", "description": "WebSocket resource"