diff options
author | Kinsey Moore <kmoore@digium.com> | 2013-05-10 13:13:06 +0000 |
---|---|---|
committer | Kinsey Moore <kmoore@digium.com> | 2013-05-10 13:13:06 +0000 |
commit | 7ce05bfb9b5f06c18451e37bb5b91cd543d6ba84 (patch) | |
tree | b4e833909ec2ba776d39848ecf8d28f74b341424 /res | |
parent | 2cfedc12adf64cc24e855bd9ea30df3aa1b759ce (diff) |
Add channel events for res_stasis apps
This change adds a framework in res_stasis for handling events from
channel topics. JSON event generation and validation code is created
from event documentation in rest-api/api-docs/events.json to assist in
JSON event generation, ensure consistency, and ensure that accurate
documentation is available for ALL events that are received by
res_stasis applications.
The userevent application has been refactored along with the code that
handles userevent channel blob events to pass the headers as key/value
pairs in the JSON blob. As a side-effect, app_userevent now handles
duplicate keys by overwriting the previous value.
Review: https://reviewboard.asterisk.org/r/2428/
(closes issue ASTERISK-21180)
Patch-By: Kinsey Moore <kmoore@digium.com>
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@388275 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res')
-rw-r--r-- | res/res_stasis.c | 437 | ||||
-rw-r--r-- | res/res_stasis_http_events.c | 519 | ||||
-rw-r--r-- | res/res_stasis_websocket.c | 46 | ||||
-rw-r--r-- | res/stasis_http/resource_endpoints.h | 2 | ||||
-rw-r--r-- | res/stasis_http/resource_events.h | 254 | ||||
-rw-r--r-- | res/stasis_http/resource_recordings.h | 4 | ||||
-rw-r--r-- | res/stasis_http/resource_sounds.h | 2 |
7 files changed, 1098 insertions, 166 deletions
diff --git a/res/res_stasis.c b/res/res_stasis.c index 3d003e40a..a1910abea 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -39,6 +39,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_app.h" #include "asterisk/stasis_channels.h" #include "asterisk/strings.h" +#include "asterisk/stasis_message_router.h" +#include "asterisk/callerid.h" +#include "stasis_http/resource_events.h" /*! Time to wait for a frame in the application */ #define MAX_WAIT_MS 200 @@ -56,6 +59,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define CONTROLS_NUM_BUCKETS 127 /*! + * \brief Number of buckets for the channels container for app instances. Remember + * to keep it a prime number! + */ +#define APP_CHANNELS_BUCKETS 7 + +/*! + * \brief Number of buckets for the blob_handlers container. Remember to keep + * it a prime number! + */ +#define BLOB_HANDLER_BUCKETS 7 + +/*! * \brief Stasis application container. Please call apps_registry() instead of * directly accessing. */ @@ -63,6 +78,9 @@ struct ao2_container *__apps_registry; struct ao2_container *__app_controls; +/*! \brief Message router for the channel caching topic */ +struct stasis_message_router *channel_router; + /*! Ref-counting accessor for the stasis applications container */ static struct ao2_container *apps_registry(void) { @@ -81,6 +99,8 @@ struct app { stasis_app_cb handler; /*! Opaque data to hand to callback function. */ void *data; + /*! List of channel identifiers this app instance is interested in */ + struct ao2_container *channels; /*! Name of the Stasis application */ char name[]; }; @@ -91,12 +111,14 @@ static void app_dtor(void *obj) ao2_cleanup(app->data); app->data = NULL; + ao2_cleanup(app->channels); + app->channels = NULL; } /*! Constructor for \ref app. */ static struct app *app_create(const char *name, stasis_app_cb handler, void *data) { - struct app *app; + RAII_VAR(struct app *, app, NULL, ao2_cleanup); size_t size; ast_assert(name != NULL); @@ -114,6 +136,12 @@ static struct app *app_create(const char *name, stasis_app_cb handler, void *dat ao2_ref(data, +1); app->data = data; + app->channels = ast_str_container_alloc(APP_CHANNELS_BUCKETS); + if (!app->channels) { + return NULL; + } + + ao2_ref(app, +1); return app; } @@ -140,6 +168,27 @@ static int app_compare(void *lhs, void *rhs, int flags) } } +static int app_add_channel(struct app* app, const struct ast_channel *chan) +{ + const char *uniqueid; + ast_assert(chan != NULL); + ast_assert(app != NULL); + + uniqueid = ast_channel_uniqueid(chan); + if (!ast_str_container_add(app->channels, uniqueid)) { + return -1; + } + return 0; +} + +static void app_remove_channel(struct app* app, const struct ast_channel *chan) +{ + ast_assert(chan != NULL); + ast_assert(app != NULL); + + ao2_find(app->channels, ast_channel_uniqueid(chan), OBJ_KEY | OBJ_NODATA | OBJ_UNLINK); +} + /*! * \brief Send a message to the given application. * \param app App to send the message to. @@ -316,6 +365,9 @@ void stasis_app_control_continue(struct stasis_app_control *control) control->continue_to_dialplan = 1; } +/*! \brief Typedef for blob handler callbacks */ +typedef struct ast_json *(*channel_blob_handler_cb)(struct ast_channel_blob *); + static int OK = 0; static int FAIL = -1; @@ -343,43 +395,11 @@ int stasis_app_control_answer(struct stasis_app_control *control) return *retval; } -static struct ast_json *app_event_create( - const char *event_name, - const struct ast_channel_snapshot *snapshot, - const struct ast_json *extra_info) -{ - RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); - RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); - - if (extra_info) { - event = ast_json_deep_copy(extra_info); - } else { - event = ast_json_object_create(); - } - - if (snapshot) { - int ret; - - /* Mustn't already have a channel field */ - ast_assert(ast_json_object_get(event, "channel") == NULL); - - ret = ast_json_object_set( - event, - "channel", ast_channel_snapshot_to_json(snapshot)); - if (ret != 0) { - return NULL; - } - } - - message = ast_json_pack("{s: o}", event_name, ast_json_ref(event)); - - return ast_json_ref(message); -} - static int send_start_msg(struct app *app, struct ast_channel *chan, int argc, char *argv[]) { RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); struct ast_json *json_args; @@ -393,19 +413,13 @@ static int send_start_msg(struct app *app, struct ast_channel *chan, return -1; } - msg = ast_json_pack("{s: {s: [], s: o}}", - "stasis-start", - "args", - "channel", ast_channel_snapshot_to_json(snapshot)); - - if (!msg) { + blob = ast_json_pack("{s: []}", "args"); + if (!blob) { return -1; } /* Append arguments to args array */ - json_args = ast_json_object_get( - ast_json_object_get(msg, "stasis-start"), - "args"); + json_args = ast_json_object_get(blob, "args"); ast_assert(json_args != NULL); for (i = 0; i < argc; ++i) { int r = ast_json_array_append(json_args, @@ -416,6 +430,11 @@ static int send_start_msg(struct app *app, struct ast_channel *chan, } } + msg = stasis_json_event_stasis_start_create(snapshot, blob); + if (!msg) { + return -1; + } + app_send(app, msg); return 0; } @@ -432,7 +451,8 @@ static int send_end_msg(struct app *app, struct ast_channel *chan) if (snapshot == NULL) { return -1; } - msg = app_event_create("stasis-end", snapshot, NULL); + + msg = stasis_json_event_stasis_end_create(snapshot); if (!msg) { return -1; } @@ -441,62 +461,201 @@ static int send_end_msg(struct app *app, struct ast_channel *chan) return 0; } -static void dtmf_handler(struct app *app, struct ast_channel_blob *obj) +static int app_watching_channel_cb(void *obj, void *arg, int flags) { - RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); - RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); - const char *direction; + RAII_VAR(char *, uniqueid, NULL, ao2_cleanup); + struct app *app = obj; + char *chan_uniqueid = arg; - /* To simplify events, we'll only generate on receive */ - direction = ast_json_string_get( - ast_json_object_get(obj->blob, "direction")); + uniqueid = ao2_find(app->channels, chan_uniqueid, OBJ_KEY); + return uniqueid ? CMP_MATCH : 0; +} - if (strcmp("Received", direction) != 0) { - return; +static struct ao2_container *get_watching_apps(const char *uniqueid) +{ + RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup); + struct ao2_container *watching_apps; + char *uniqueid_dup; + RAII_VAR(struct ao2_iterator *,watching_apps_iter, NULL, ao2_iterator_destroy); + ast_assert(uniqueid != NULL); + ast_assert(apps != NULL); + + uniqueid_dup = ast_strdupa(uniqueid); + + watching_apps_iter = ao2_callback(apps, OBJ_MULTIPLE, app_watching_channel_cb, uniqueid_dup); + watching_apps = watching_apps_iter->c; + + if (!ao2_container_count(watching_apps)) { + return NULL; } - extra = ast_json_pack( - "{s: o}", - "digit", ast_json_ref(ast_json_object_get(obj->blob, "digit"))); - if (!extra) { - return; + ao2_ref(watching_apps, +1); + return watching_apps_iter->c; +} + +/*! \brief Typedef for callbacks that get called on channel snapshot updates */ +typedef struct ast_json *(*channel_snapshot_monitor)( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot); + +/*! \brief Handle channel state changes */ +static struct ast_json *channel_state( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + struct ast_channel_snapshot *snapshot = new_snapshot ? new_snapshot : old_snapshot; + + if (!old_snapshot) { + return stasis_json_event_channel_created_create(snapshot); + } else if (!new_snapshot) { + json = ast_json_pack("{s: i, s: s}", + "cause", snapshot->hangupcause, + "cause_txt", ast_cause2str(snapshot->hangupcause)); + if (!json) { + return NULL; + } + return stasis_json_event_channel_destroyed_create(snapshot, json); + } else if (old_snapshot->state != new_snapshot->state) { + return stasis_json_event_channel_state_change_create(snapshot); } - msg = app_event_create("dtmf-received", obj->snapshot, extra); - if (!msg) { - return; + return NULL; +} + +static struct ast_json *channel_dialplan( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + + /* No Newexten event on cache clear */ + if (!new_snapshot) { + return NULL; + } + + /* Empty application is not valid for a Newexten event */ + if (ast_strlen_zero(new_snapshot->appl)) { + return NULL; + } + + if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) { + return NULL; + } + + json = ast_json_pack("{s: s, s: s}", + "application", new_snapshot->appl, + "application_data", new_snapshot->data); + if (!json) { + return NULL; + } + + return stasis_json_event_channel_dialplan_create(new_snapshot, json); +} + +static struct ast_json *channel_callerid( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + + /* No NewCallerid event on cache clear or first event */ + if (!old_snapshot || !new_snapshot) { + return NULL; } + if (ast_channel_snapshot_caller_id_equal(old_snapshot, new_snapshot)) { + return NULL; + } + + json = ast_json_pack("{s: i, s: s}", + "caller_presentation", new_snapshot->caller_pres, + "caller_presentation_txt", ast_describe_caller_presentation(new_snapshot->caller_pres)); + if (!json) { + return NULL; + } + + return stasis_json_event_channel_caller_id_create(new_snapshot, json); +} + +static struct ast_json *channel_snapshot( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + if (!new_snapshot) { + return NULL; + } + + return stasis_json_event_channel_snapshot_create(new_snapshot); +} + +channel_snapshot_monitor channel_monitors[] = { + channel_snapshot, + channel_state, + channel_dialplan, + channel_callerid +}; + +static int app_send_cb(void *obj, void *arg, int flags) +{ + struct app *app = obj; + struct ast_json *msg = arg; + app_send(app, msg); + return 0; } -static void sub_handler(void *data, struct stasis_subscription *sub, - struct stasis_topic *topic, - struct stasis_message *message) +static void sub_snapshot_handler(void *data, + struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) { - struct app *app = data; + RAII_VAR(struct ao2_container *, watching_apps, NULL, ao2_cleanup); + struct stasis_cache_update *update = stasis_message_data(message); + struct ast_channel_snapshot *new_snapshot = stasis_message_data(update->new_snapshot); + struct ast_channel_snapshot *old_snapshot = stasis_message_data(update->old_snapshot); + int i; - if (stasis_subscription_final_message(sub, message)) { - ao2_cleanup(data); + watching_apps = get_watching_apps(new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid); + if (!watching_apps) { return; } - if (ast_channel_snapshot_type() == stasis_message_type(message)) { + for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) { RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); - struct ast_channel_snapshot *snapshot = - stasis_message_data(message); - msg = app_event_create("channel-state-change", snapshot, NULL); - if (!msg) { - return; + msg = channel_monitors[i](old_snapshot, new_snapshot); + if (msg) { + ao2_callback(watching_apps, OBJ_NODATA, app_send_cb, msg); } - app_send(app, msg); - } else if (ast_channel_dtmf_end_type() == stasis_message_type(message)) { - /* To simplify events, we'll only generate on DTMF end */ - struct ast_channel_blob *blob = stasis_message_data(message); - dtmf_handler(app, blob); } +} +static void distribute_message(struct ao2_container *apps, struct ast_json *msg) +{ + ao2_callback(apps, OBJ_NODATA, app_send_cb, msg); +} + +static void generic_blob_handler(struct ast_channel_blob *obj, channel_blob_handler_cb handler_cb) +{ + RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); + RAII_VAR(struct ao2_container *, watching_apps, NULL, ao2_cleanup); + + if (!obj->snapshot) { + return; + } + + watching_apps = get_watching_apps(obj->snapshot->uniqueid); + if (!watching_apps) { + return; + } + + msg = handler_cb(obj); + if (!msg) { + return; + } + + distribute_message(watching_apps, msg); } /*! @@ -544,8 +703,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup); RAII_VAR(struct app *, app, NULL, ao2_cleanup); RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink); - RAII_VAR(struct stasis_subscription *, subscription, NULL, - stasis_unsubscribe); int res = 0; int hungup = 0; @@ -570,21 +727,17 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, ao2_link(controls, control); } - subscription = - stasis_subscribe(ast_channel_topic(chan), sub_handler, app); - if (subscription == NULL) { - ast_log(LOG_ERROR, "Error subscribing app %s to channel %s\n", - app_name, ast_channel_name(chan)); - return -1; - } - ao2_ref(app, +1); /* subscription now has a reference */ - res = send_start_msg(app, chan, argc, argv); if (res != 0) { ast_log(LOG_ERROR, "Error sending start message to %s\n", app_name); return res; } + if (app_add_channel(app, chan)) { + ast_log(LOG_ERROR, "Error adding listener for channel %s to app %s\n", ast_channel_name(chan), app_name); + return -1; + } + while (1) { RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor); int r; @@ -634,6 +787,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, } } + app_remove_channel(app, chan); res = send_end_msg(app, chan); if (res != 0) { ast_log(LOG_ERROR, @@ -675,10 +829,16 @@ int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data) if (app) { RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); SCOPED_LOCK(app_lock, app, ao2_lock, ao2_unlock); - msg = app_event_create("application-replaced", NULL, NULL); - app->handler(app->data, app_name, msg); + blob = ast_json_pack("{s: s}", "application", app_name); + if (blob) { + msg = stasis_json_event_application_replaced_create(blob); + if (msg) { + app->handler(app->data, app_name, msg); + } + } app->handler = handler; ao2_cleanup(app->data); @@ -706,6 +866,82 @@ void stasis_app_unregister(const char *app_name) } } +static struct ast_json *handle_blob_dtmf(struct ast_channel_blob *obj) +{ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); + const char *direction; + + /* To simplify events, we'll only generate on receive */ + direction = ast_json_string_get( + ast_json_object_get(obj->blob, "direction")); + + if (strcmp("Received", direction) != 0) { + return NULL; + } + + extra = ast_json_pack( + "{s: o}", + "digit", ast_json_ref(ast_json_object_get(obj->blob, "digit"))); + if (!extra) { + return NULL; + } + + return stasis_json_event_channel_dtmf_received_create(obj->snapshot, extra); +} + +/* To simplify events, we'll only generate on DTMF end (dtmf_end type) */ +static void sub_dtmf_handler(void *data, + struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct ast_channel_blob *obj = stasis_message_data(message); + generic_blob_handler(obj, handle_blob_dtmf); +} + +static struct ast_json *handle_blob_userevent(struct ast_channel_blob *obj) +{ + return stasis_json_event_channel_userevent_create(obj->snapshot, obj->blob); +} + +static void sub_userevent_handler(void *data, + struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct ast_channel_blob *obj = stasis_message_data(message); + generic_blob_handler(obj, handle_blob_userevent); +} + +static struct ast_json *handle_blob_hangup_request(struct ast_channel_blob *obj) +{ + return stasis_json_event_channel_hangup_request_create(obj->snapshot, obj->blob); +} + +static void sub_hangup_request_handler(void *data, + struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct ast_channel_blob *obj = stasis_message_data(message); + generic_blob_handler(obj, handle_blob_hangup_request); +} + +static struct ast_json *handle_blob_varset(struct ast_channel_blob *obj) +{ + return stasis_json_event_channel_varset_create(obj->snapshot, obj->blob); +} + +static void sub_varset_handler(void *data, + struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct ast_channel_blob *obj = stasis_message_data(message); + generic_blob_handler(obj, handle_blob_varset); +} + static int load_module(void) { int r = 0; @@ -722,13 +958,30 @@ static int load_module(void) return AST_MODULE_LOAD_FAILURE; } - return r; + channel_router = stasis_message_router_create(stasis_caching_get_topic(ast_channel_topic_all_cached())); + if (!channel_router) { + return AST_MODULE_LOAD_FAILURE; + } + + r |= stasis_message_router_add(channel_router, stasis_cache_update_type(), sub_snapshot_handler, NULL); + r |= stasis_message_router_add(channel_router, ast_channel_user_event_type(), sub_userevent_handler, NULL); + r |= stasis_message_router_add(channel_router, ast_channel_varset_type(), sub_varset_handler, NULL); + r |= stasis_message_router_add(channel_router, ast_channel_dtmf_begin_type(), sub_dtmf_handler, NULL); + r |= stasis_message_router_add(channel_router, ast_channel_hangup_request_type(), sub_hangup_request_handler, NULL); + if (r) { + return AST_MODULE_LOAD_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) { int r = 0; + stasis_message_router_unsubscribe(channel_router); + channel_router = NULL; + ao2_cleanup(__apps_registry); __apps_registry = NULL; diff --git a/res/res_stasis_http_events.c b/res/res_stasis_http_events.c index 62ed44f96..98d845d58 100644 --- a/res/res_stasis_http_events.c +++ b/res/res_stasis_http_events.c @@ -42,6 +42,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/module.h" #include "stasis_http/resource_events.h" +#include "asterisk/stasis_channels.h" /*! * \brief Parameter parsing callback for /events. @@ -76,6 +77,524 @@ static struct stasis_rest_handlers events = { .children = { } }; +struct ast_json *stasis_json_event_channel_snapshot_create( + struct ast_channel_snapshot *channel_snapshot + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + int ret; + + ast_assert(channel_snapshot != NULL); + + event = ast_json_object_create(); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_destroyed_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "cause"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + validator = ast_json_object_get(blob, "cause_txt"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_destroyed", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_caller_id_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "caller_presentation_txt"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + validator = ast_json_object_get(blob, "caller_presentation"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_caller_id", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_hangup_request_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "soft"); + if (validator) { + /* do validation? XXX */ + } + + validator = ast_json_object_get(blob, "cause"); + if (validator) { + /* do validation? XXX */ + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_hangup_request", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_application_replaced_create( + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "application"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + message = ast_json_pack("{s: o}", "application_replaced", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_varset_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "variable"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + validator = ast_json_object_get(blob, "value"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_varset", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_userevent_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "eventname"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_created_create( + struct ast_channel_snapshot *channel_snapshot + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + int ret; + + ast_assert(channel_snapshot != NULL); + + event = ast_json_object_create(); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_created", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_stasis_start_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "args"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "stasis_start", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_dialplan_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "application"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + validator = ast_json_object_get(blob, "application_data"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_dialplan", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_state_change_create( + struct ast_channel_snapshot *channel_snapshot + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + int ret; + + ast_assert(channel_snapshot != NULL); + + event = ast_json_object_create(); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_state_change", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_channel_dtmf_received_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + struct ast_json *validator; + int ret; + + ast_assert(channel_snapshot != NULL); + ast_assert(blob != NULL); + ast_assert(ast_json_object_get(blob, "channel") == NULL); + ast_assert(ast_json_object_get(blob, "type") == NULL); + + validator = ast_json_object_get(blob, "digit"); + if (validator) { + /* do validation? XXX */ + } else { + /* fail message generation if the required parameter doesn't exist */ + return NULL; + } + + event = ast_json_deep_copy(blob); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "channel_dtmf_received", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + +struct ast_json *stasis_json_event_stasis_end_create( + struct ast_channel_snapshot *channel_snapshot + ) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, event, NULL, ast_json_unref); + int ret; + + ast_assert(channel_snapshot != NULL); + + event = ast_json_object_create(); + if (!event) { + return NULL; + } + + ret = ast_json_object_set(event, + "channel", ast_channel_snapshot_to_json(channel_snapshot)); + if (ret) { + return NULL; + } + + message = ast_json_pack("{s: o}", "stasis_end", ast_json_ref(event)); + if (!message) { + return NULL; + } + + return ast_json_ref(message); +} + static int load_module(void) { return stasis_http_add_handler(&events); diff --git a/res/res_stasis_websocket.c b/res/res_stasis_websocket.c index 149d803c4..4cf5b9498 100644 --- a/res/res_stasis_websocket.c +++ b/res/res_stasis_websocket.c @@ -52,10 +52,6 @@ static struct ast_json *oom_json; */ #define APPS_NUM_BUCKETS 7 -struct websocket_app { - char *name; -}; - /*! * \internal * \brief Helper to write a JSON object to a WebSocket. @@ -78,35 +74,6 @@ static int websocket_write_json(struct ast_websocket *session, strlen(str)); } -/*! Hash function for websocket_app */ -static int hash_app(const void *obj, const int flags) -{ - const struct websocket_app *app = obj; - const char *name = flags & OBJ_KEY ? obj : app->name; - - return ast_str_hash(name); -} - -/*! Comparison function for websocket_app */ -static int compare_app(void *lhs, void *rhs, int flags) -{ - const struct websocket_app *lhs_app = lhs; - const struct websocket_app *rhs_app = rhs; - const char *rhs_name = flags & OBJ_KEY ? rhs : rhs_app->name; - - if (strcmp(lhs_app->name, rhs_name) == 0) { - return CMP_MATCH; - } else { - return 0; - } -} - -static void app_dtor(void *obj) -{ - struct websocket_app *app = obj; - ast_free(app->name); -} - struct stasis_ws_session_info { struct ast_websocket *ws_session; struct ao2_container *websocket_apps; @@ -132,7 +99,7 @@ static struct stasis_ws_session_info *session_create( session->ws_session = ws_session; session->websocket_apps = - ao2_container_alloc(APPS_NUM_BUCKETS, hash_app, compare_app); + ast_str_container_alloc(APPS_NUM_BUCKETS); if (!session->websocket_apps) { return NULL; @@ -154,12 +121,12 @@ static struct stasis_ws_session_info *session_create( static void session_shutdown(struct stasis_ws_session_info *session) { struct ao2_iterator i; - struct websocket_app *app; + char *app; SCOPED_AO2LOCK(lock, session); i = ao2_iterator_init(session->websocket_apps, 0); while ((app = ao2_iterator_next(&i))) { - stasis_app_unregister(app->name); + stasis_app_unregister(app); ao2_cleanup(app); } ao2_iterator_destroy(&i); @@ -212,15 +179,10 @@ static int session_register_apps(struct stasis_ws_session_info *session, return -1; } while ((app_name = strsep(&apps, ","))) { - RAII_VAR(struct websocket_app *, app, NULL, ao2_cleanup); - - app = ao2_alloc(sizeof(*app), app_dtor); - if (!app) { + if (ast_str_container_add(session->websocket_apps, app_name)) { websocket_write_json(session->ws_session, oom_json); return -1; } - app->name = ast_strdup(app_name); - ao2_link(session->websocket_apps, app); stasis_app_register(app_name, app_handler, session); } diff --git a/res/stasis_http/resource_endpoints.h b/res/stasis_http/resource_endpoints.h index b534fb047..a0262c064 100644 --- a/res/stasis_http/resource_endpoints.h +++ b/res/stasis_http/resource_endpoints.h @@ -83,8 +83,8 @@ void stasis_http_get_endpoint(struct ast_variable *headers, struct ast_get_endpo * JSON models * * Endpoint + * - resource: string (required) * - technology: string (required) - * - name: string (required) */ #endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */ diff --git a/res/stasis_http/resource_events.h b/res/stasis_http/resource_events.h index 10799001d..150939d22 100644 --- a/res/stasis_http/resource_events.h +++ b/res/stasis_http/resource_events.h @@ -55,42 +55,240 @@ struct ast_event_websocket_args { */ void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response); +struct ast_channel_snapshot; +struct ast_bridge_snapshot; + +/*! + * \brief Some part of channel state changed. + * + * \param channel The channel to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_snapshot_create( + struct ast_channel_snapshot *channel_snapshot + ); + +/*! + * \brief Notification that a channel has been destroyed. + * + * \param channel The channel to be used to generate this event + * \param blob JSON blob containing the following parameters: + * - cause: integer - Integer representation of the cause of the hangup (required) + * - cause_txt: string - Text representation of the cause of the hangup (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_destroyed_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief Channel changed Caller ID. + * + * \param channel The channel that changed Caller ID. + * \param blob JSON blob containing the following parameters: + * - caller_presentation_txt: string - The text representation of the Caller Presentation value. (required) + * - caller_presentation: integer - The integer representation of the Caller Presentation value. (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_caller_id_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief A hangup was requested on the channel. + * + * \param channel The channel on which the hangup was requested. + * \param blob JSON blob containing the following parameters: + * - soft: boolean - Whether the hangup request was a soft hangup request. + * - cause: integer - Integer representation of the cause of the hangup. + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_hangup_request_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief Notification that another WebSocket has taken over for an application. + * + * \param blob JSON blob containing the following parameters: + * - application: string (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_application_replaced_create( + struct ast_json *blob + ); + +/*! + * \brief Channel variable changed. + * + * \param channel The channel on which the variable was set. + * \param blob JSON blob containing the following parameters: + * - variable: string - The variable that changed. (required) + * - value: string - The new value of the variable. (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_varset_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief User-generated event with additional user-defined fields in the object. + * + * \param channel The channel that signaled the user event. + * \param blob JSON blob containing the following parameters: + * - eventname: string - The name of the user event. (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_userevent_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief Notification that a channel has been created. + * + * \param channel The channel to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_created_create( + struct ast_channel_snapshot *channel_snapshot + ); + +/*! + * \brief Notification that a channel has entered a Stasis appliction. + * + * \param channel The channel to be used to generate this event + * \param blob JSON blob containing the following parameters: + * - args: List[string] - Arguments to the application (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_stasis_start_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief Channel changed location in the dialplan. + * + * \param channel The channel that changed dialplan location. + * \param blob JSON blob containing the following parameters: + * - application: string - The application that the channel is currently in. (required) + * - application_data: string - The data that was passed to the application when it was invoked. (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_dialplan_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief Notification of a channel's state change. + * + * \param channel The channel to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_state_change_create( + struct ast_channel_snapshot *channel_snapshot + ); + +/*! + * \brief DTMF received on a channel. + * + * \param channel The channel on which DTMF was received + * \param blob JSON blob containing the following parameters: + * - digit: string - DTMF digit received (0-9, A-E, # or *) (required) + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_dtmf_received_create( + struct ast_channel_snapshot *channel_snapshot, + struct ast_json *blob + ); + +/*! + * \brief Notification that a channel has left a Stasis appliction. + * + * \param channel The channel to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_stasis_end_create( + struct ast_channel_snapshot *channel_snapshot + ); + /* * JSON models * - * DtmfReceived - * - digit: string - * - channel: Channel - * BridgeCreated - * - bridge: Bridge - * BridgeDestroyed - * - bridge: Bridge + * ChannelSnapshot + * ChannelDestroyed + * - cause: integer (required) + * - cause_txt: string (required) + * ChannelCallerId + * - caller_presentation_txt: string (required) + * - caller_presentation: integer (required) + * ChannelHangupRequest + * - soft: boolean + * - cause: integer * ApplicationReplaced - * - application: string - * ChannelLeftBridge - * - bridge: Bridge - * - channel: Channel + * - application: string (required) + * ChannelVarset + * - variable: string (required) + * - value: string (required) + * ChannelUserevent + * - eventname: string (required) + * ChannelCreated * StasisStart - * - args: List[string] - * - channel_info: Channel - * StasisEnd - * - channel_info: Channel + * - args: List[string] (required) + * ChannelDialplan + * - application: string (required) + * - application_data: string (required) * ChannelStateChange - * - channel_info: Channel - * ChannelEnteredBridge - * - bridge: Bridge - * - channel: Channel + * ChannelDtmfReceived + * - digit: string (required) * Event - * - stasis_start: StasisStart - * - channel_entered_bridge: ChannelEnteredBridge - * - channel_left_bridge: ChannelLeftBridge - * - application_replaced: ApplicationReplaced - * - channel_state_change: ChannelStateChange - * - bridge_created: BridgeCreated + * - channel_created: ChannelCreated + * - channel_destroyed: ChannelDestroyed + * - channel_dialplan: ChannelDialplan + * - channel_varset: ChannelVarset + * - application_replaced: ApplicationReplaced + * - channel_state_change: ChannelStateChange + * - stasis_start: StasisStart * - application: string (required) - * - stasis_end: StasisEnd - * - dtmf_received: DtmfReceived - * - bridge_destroyed: BridgeDestroyed + * - channel_hangup_request: ChannelHangupRequest + * - channel_userevent: ChannelUserevent + * - channel_snapshot: ChannelSnapshot + * - channel_dtmf_received: ChannelDtmfReceived + * - channel_caller_id: ChannelCallerId + * - stasis_end: StasisEnd + * StasisEnd */ #endif /* _ASTERISK_RESOURCE_EVENTS_H */ diff --git a/res/stasis_http/resource_recordings.h b/res/stasis_http/resource_recordings.h index 549a36106..d062da996 100644 --- a/res/stasis_http/resource_recordings.h +++ b/res/stasis_http/resource_recordings.h @@ -196,8 +196,8 @@ void stasis_http_unmute_recording(struct ast_variable *headers, struct ast_unmut * Recording * - id: string (required) * StoredRecording - * - durationSeconds: int - * - time: Date + * - durationSeconds: int + * - time: Date * - id: string (required) * - formats: List[string] (required) * LiveRecording diff --git a/res/stasis_http/resource_sounds.h b/res/stasis_http/resource_sounds.h index 4af8b9dfd..9d724b359 100644 --- a/res/stasis_http/resource_sounds.h +++ b/res/stasis_http/resource_sounds.h @@ -71,7 +71,7 @@ void stasis_http_get_stored_sound(struct ast_variable *headers, struct ast_get_s * * Sound * - lang: string (required) - * - text: string + * - text: string * - id: string (required) * - formats: List[string] (required) */ |