diff --git a/apps/Makefile b/apps/Makefile index 150f5a358a..1d5d2b38d5 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -37,5 +37,3 @@ ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) LIBS+= -lres_features.so -lres_ael_share.so -lres_monitor.so -lres_speech.so LIBS+= -lres_smdi.so endif - -app_stasis.so: stasis_json.o diff --git a/apps/app_stasis.c b/apps/app_stasis.c index 62b35b49e7..aa255ba3de 100644 --- a/apps/app_stasis.c +++ b/apps/app_stasis.c @@ -24,6 +24,7 @@ */ /*** MODULEINFO + res_stasis core ***/ @@ -32,14 +33,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/app.h" -#include "asterisk/app_stasis.h" -#include "asterisk/astobj2.h" -#include "asterisk/channel.h" -#include "asterisk/lock.h" #include "asterisk/module.h" #include "asterisk/stasis.h" -#include "asterisk/strings.h" -#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_app.h" /*** DOCUMENTATION @@ -67,387 +63,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*! \brief Dialplan application name */ static const char *stasis = "Stasis"; -/*! - * \brief Number of buckets for the Stasis application hash table. Remember to - * keep it a prime number! - */ -#define APPS_NUM_BUCKETS 127 - -/*! - * \brief Number of buckets for the Stasis application hash table. Remember to - * keep it a prime number! - */ -#define CONTROLS_NUM_BUCKETS 127 - -/*! - * \brief Stasis application container. Please call apps_registry() instead of - * directly accessing. - */ -struct ao2_container *__apps_registry; - -struct ao2_container *__app_controls; - -/*! Ref-counting accessor for the stasis applications container */ -static struct ao2_container *apps_registry(void) -{ - ao2_ref(__apps_registry, +1); - return __apps_registry; -} - -static struct ao2_container *app_controls(void) -{ - ao2_ref(__app_controls, +1); - return __app_controls; -} - -struct app { - /*! Callback function for this application. */ - stasis_app_cb handler; - /*! Opaque data to hand to callback function. */ - void *data; - /*! Name of the Stasis application */ - char name[]; -}; - -static void app_dtor(void *obj) -{ - struct app *app = obj; - - ao2_cleanup(app->data); - app->data = NULL; -} - -/*! Constructor for \ref app. */ -static struct app *app_create(const char *name, stasis_app_cb handler, void *data) -{ - struct app *app; - size_t size; - - ast_assert(name != NULL); - ast_assert(handler != NULL); - - size = sizeof(*app) + strlen(name) + 1; - app = ao2_alloc_options(size, app_dtor, AO2_ALLOC_OPT_LOCK_MUTEX); - - if (!app) { - return NULL; - } - - strncpy(app->name, name, size - sizeof(*app)); - app->handler = handler; - ao2_ref(data, +1); - app->data = data; - - return app; -} - -/*! AO2 hash function for \ref app */ -static int app_hash(const void *obj, const int flags) -{ - const struct app *app = obj; - const char *name = flags & OBJ_KEY ? obj : app->name; - - return ast_str_hash(name); -} - -/*! AO2 comparison function for \ref app */ -static int app_compare(void *lhs, void *rhs, int flags) -{ - const struct app *lhs_app = lhs; - const struct 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 | CMP_STOP; - } else { - return 0; - } -} - -/*! - * \brief Send a message to the given application. - * \param app App to send the message to. - * \param message Message to send. - */ -static void app_send(struct app *app, struct ast_json *message) -{ - app->handler(app->data, app->name, message); -} - -struct stasis_app_control { - /*! - * When set, /c app_stasis should exit and continue in the dialplan. - */ - int continue_to_dialplan:1; - /*! Uniqueid of the associated channel */ - char channel_id[]; -}; - -static struct stasis_app_control *control_create(const char *uniqueid) -{ - struct stasis_app_control *control; - size_t size; - - size = sizeof(*control) + strlen(uniqueid) + 1; - control = ao2_alloc(size, NULL); - if (!control) { - return NULL; - } - - strncpy(control->channel_id, uniqueid, size - sizeof(*control)); - - return control; -} - -/*! AO2 hash function for \ref stasis_app_control */ -static int control_hash(const void *obj, const int flags) -{ - const struct stasis_app_control *control = obj; - const char *id = flags & OBJ_KEY ? obj : control->channel_id; - - return ast_str_hash(id); -} - -/*! AO2 comparison function for \ref stasis_app_control */ -static int control_compare(void *lhs, void *rhs, int flags) -{ - const struct stasis_app_control *lhs_control = lhs; - const struct stasis_app_control *rhs_control = rhs; - const char *rhs_name = - flags & OBJ_KEY ? rhs : rhs_control->channel_id; - - if (strcmp(lhs_control->channel_id, rhs_name) == 0) { - return CMP_MATCH | CMP_STOP; - } else { - return 0; - } -} - -struct stasis_app_control *stasis_app_control_find_by_channel( - const struct ast_channel *chan) -{ - RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); - if (chan == NULL) { - return NULL; - } - - controls = app_controls(); - return ao2_find(controls, ast_channel_uniqueid(chan), OBJ_KEY); -} - -/*! - * \brief Test the \c continue_to_dialplan bit for the given \a app. - * - * The bit is also reset for the next call. - * - * \param app Application to check the \c continue_to_dialplan bit. - * \return Zero to remain in \c Stasis - * \return Non-zero to continue in the dialplan - */ -static int control_continue_test_and_reset(struct stasis_app_control *control) -{ - int r; - SCOPED_AO2LOCK(lock, control); - - r = control->continue_to_dialplan; - control->continue_to_dialplan = 0; - return r; -} - -void stasis_app_control_continue(struct stasis_app_control *control) -{ - SCOPED_AO2LOCK(lock, control); - control->continue_to_dialplan = 1; -} - -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_channel_snapshot *, snapshot, NULL, ao2_cleanup); - - struct ast_json *json_args; - int i; - - ast_assert(chan != NULL); - - /* Set channel info */ - snapshot = ast_channel_snapshot_create(chan); - if (!snapshot) { - return -1; - } - - msg = ast_json_pack("{s: {s: [], s: o}}", - "stasis-start", - "args", - "channel", ast_channel_snapshot_to_json(snapshot)); - - if (!msg) { - return -1; - } - - /* Append arguments to args array */ - json_args = ast_json_object_get( - ast_json_object_get(msg, "stasis-start"), - "args"); - ast_assert(json_args != NULL); - for (i = 0; i < argc; ++i) { - int r = ast_json_array_append(json_args, - ast_json_string_create(argv[i])); - if (r != 0) { - ast_log(LOG_ERROR, "Error appending start message\n"); - return -1; - } - } - - app_send(app, msg); - return 0; -} - -static int send_end_msg(struct 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); - - ast_assert(chan != NULL); - - /* Set channel info */ - snapshot = ast_channel_snapshot_create(chan); - if (snapshot == NULL) { - return -1; - } - msg = app_event_create("stasis-end", snapshot, NULL); - if (!msg) { - return -1; - } - - app_send(app, msg); - return 0; -} - -static void dtmf_handler(struct app *app, 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; - } - - extra = ast_json_pack( - "{s: o}", - "digit", ast_json_ref(ast_json_object_get(obj->blob, "digit"))); - if (!extra) { - return; - } - - msg = app_event_create("dtmf-received", obj->snapshot, extra); - if (!msg) { - return; - } - - app_send(app, msg); -} - -static void blob_handler(struct app *app, struct ast_channel_blob *blob) -{ - /* To simplify events, we'll only generate on DTMF end */ - if (strcmp(ast_channel_blob_json_type(blob), "dtmf_end") == 0) { - dtmf_handler(app, blob); - } -} - -static void sub_handler(void *data, struct stasis_subscription *sub, - struct stasis_topic *topic, - struct stasis_message *message) -{ - struct app *app = data; - if (ast_channel_snapshot_type() == stasis_message_type(message)) { - 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; - } - app_send(app, msg); - } else if (ast_channel_blob_type() == stasis_message_type(message)) { - struct ast_channel_blob *blob = stasis_message_data(message); - blob_handler(app, blob); - } - if (stasis_subscription_final_message(sub, message)) { - ao2_cleanup(data); - } -} - -/*! - * \brief In addition to running ao2_cleanup(), this function also removes the - * object from the app_controls() container. - */ -static void control_unlink(struct stasis_app_control *control) -{ - RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); - - if (!control) { - return; - } - - controls = app_controls(); - ao2_unlink_flags(controls, control, - OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); - ao2_cleanup(control); -} - /*! /brief Stasis dialplan application callback */ -static int app_stasis_exec(struct ast_channel *chan, const char *data) +static int app_exec(struct ast_channel *chan, const char *data) { - 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; char *parse = NULL; - int hungup = 0; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(app_name); @@ -466,152 +85,15 @@ static int app_stasis_exec(struct ast_channel *chan, const char *data) return -1; } - app = ao2_find(apps, args.app_name, OBJ_KEY); - if (!app) { - ast_log(LOG_ERROR, - "Stasis app '%s' not registered\n", args.app_name); - return -1; - } - - { - RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); - - controls = app_controls(); - control = control_create(ast_channel_uniqueid(chan)); - if (!control) { - ast_log(LOG_ERROR, "Allocated failed\n"); - return -1; - } - 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", - args.app_name, ast_channel_name(chan)); - return -1; - } - ao2_ref(app, +1); /* subscription now has a reference */ - - res = send_start_msg(app, chan, args.argc - 1, args.app_argv); - if (res != 0) { - ast_log(LOG_ERROR, - "Error sending start message to %s\n", args.app_name); - return res; - } - - while (!hungup && !control_continue_test_and_reset(control) && ast_waitfor(chan, -1) > -1) { - RAII_VAR(struct ast_frame *, f, ast_read(chan), ast_frame_dtor); - if (!f) { - ast_debug(3, "%s: No more frames. Must be done, I guess.\n", - ast_channel_uniqueid(chan)); - break; - } - - switch (f->frametype) { - case AST_FRAME_CONTROL: - if (f->subclass.integer == AST_CONTROL_HANGUP) { - ast_debug(3, "%s: Received hangup\n", - ast_channel_uniqueid(chan)); - hungup = 1; - } - break; - default: - /* Not handled; discard */ - break; - } - } - - res = send_end_msg(app, chan); - if (res != 0) { - ast_log(LOG_ERROR, - "Error sending end message to %s\n", args.app_name); - return res; - } - - return res; -} - -int stasis_app_send(const char *app_name, struct ast_json *message) -{ - RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup); - RAII_VAR(struct app *, app, NULL, ao2_cleanup); - - app = ao2_find(apps, app_name, OBJ_KEY); - - if (!app) { - /* XXX We can do a better job handling late binding, queueing up - * the call for a few seconds to wait for the app to register. - */ - ast_log(LOG_WARNING, - "Stasis app '%s' not registered\n", app_name); - return -1; - } - - app_send(app, message); - return 0; -} - -int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data) -{ - RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup); - RAII_VAR(struct app *, app, NULL, ao2_cleanup); - - SCOPED_LOCK(apps_lock, apps, ao2_lock, ao2_unlock); - - app = ao2_find(apps, app_name, OBJ_KEY | OBJ_NOLOCK); - - if (app) { - RAII_VAR(struct ast_json *, msg, 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); - - app->handler = handler; - ao2_cleanup(app->data); - ao2_ref(data, +1); - app->data = data; - } else { - app = app_create(app_name, handler, data); - if (app) { - ao2_link_flags(apps, app, OBJ_NOLOCK); - } else { - return -1; - } - } - - return 0; -} - -void stasis_app_unregister(const char *app_name) -{ - RAII_VAR(struct ao2_container *, apps, NULL, ao2_cleanup); - - if (app_name) { - apps = apps_registry(); - ao2_cleanup(ao2_find(apps, app_name, OBJ_KEY | OBJ_UNLINK)); - } + return stasis_app_exec( + chan, args.app_name, args.argc - 1, args.app_argv); } static int load_module(void) { int r = 0; - __apps_registry = - ao2_container_alloc(APPS_NUM_BUCKETS, app_hash, app_compare); - if (__apps_registry == NULL) { - return AST_MODULE_LOAD_FAILURE; - } - - __app_controls = ao2_container_alloc(CONTROLS_NUM_BUCKETS, - control_hash, control_compare); - if (__app_controls == NULL) { - return AST_MODULE_LOAD_FAILURE; - } - - r |= ast_register_application_xml(stasis, app_stasis_exec); + r |= ast_register_application_xml(stasis, app_exec); return r; } @@ -619,12 +101,6 @@ static int unload_module(void) { int r = 0; - ao2_cleanup(__apps_registry); - __apps_registry = NULL; - - ao2_cleanup(__app_controls); - __app_controls = NULL; - r |= ast_unregister_application(stasis); return r; } diff --git a/apps/app_stasis.exports.in b/apps/app_stasis.exports.in deleted file mode 100644 index 9616003233..0000000000 --- a/apps/app_stasis.exports.in +++ /dev/null @@ -1,6 +0,0 @@ -{ - global: - LINKER_SYMBOL_PREFIXstasis_*; - local: - *; -}; diff --git a/apps/stasis_json.c b/apps/stasis_json.c deleted file mode 100644 index a62aba183e..0000000000 --- a/apps/stasis_json.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Asterisk -- An open source telephony toolkit. - * - * Copyright (C) 2013, Digium, Inc. - * - * David M. Lee, II - * - * 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 Stasis application JSON converters. - * - * \author David M. Lee, II - */ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#include "asterisk/app_stasis.h" -#include "asterisk/stasis_channels.h" - -struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot) -{ - RAII_VAR(struct ast_json *, json_chan, NULL, ast_json_unref); - int r = 0; - - if (snapshot == NULL) { - return NULL; - } - - json_chan = ast_json_object_create(); - if (!json_chan) { ast_log(LOG_ERROR, "Error creating channel json object\n"); return NULL; } - - r = ast_json_object_set(json_chan, "name", ast_json_string_create(snapshot->name)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "state", ast_json_string_create(ast_state2str(snapshot->state))); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "accountcode", ast_json_string_create(snapshot->accountcode)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "peeraccount", ast_json_string_create(snapshot->peeraccount)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "userfield", ast_json_string_create(snapshot->userfield)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "uniqueid", ast_json_string_create(snapshot->uniqueid)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "linkedid", ast_json_string_create(snapshot->linkedid)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "parkinglot", ast_json_string_create(snapshot->parkinglot)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "hangupsource", ast_json_string_create(snapshot->hangupsource)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "appl", ast_json_string_create(snapshot->appl)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "data", ast_json_string_create(snapshot->data)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "dialplan", ast_json_dialplan_cep(snapshot->context, snapshot->exten, snapshot->priority)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "caller", ast_json_name_number(snapshot->caller_name, snapshot->caller_number)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "connected", ast_json_name_number(snapshot->connected_name, snapshot->connected_number)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - r = ast_json_object_set(json_chan, "creationtime", ast_json_timeval(&snapshot->creationtime, NULL)); - if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; } - - return ast_json_ref(json_chan); -} - diff --git a/include/asterisk/json.h b/include/asterisk/json.h index 64a1e7b7a2..62e21293b5 100644 --- a/include/asterisk/json.h +++ b/include/asterisk/json.h @@ -791,7 +791,7 @@ struct ast_json *ast_json_name_number(const char *name, const char *number); * \return JSON string with ISO 8601 formatted date/time. * \return \c NULL on error. */ -struct ast_json *ast_json_timeval(const struct timeval *tv, const char *zone); +struct ast_json *ast_json_timeval(const struct timeval tv, const char *zone); /*! * \brief Construct a context/exten/priority as JSON. diff --git a/include/asterisk/app_stasis.h b/include/asterisk/stasis_app.h similarity index 77% rename from include/asterisk/app_stasis.h rename to include/asterisk/stasis_app.h index 3001992d2e..caec19bbcf 100644 --- a/include/asterisk/app_stasis.h +++ b/include/asterisk/stasis_app.h @@ -16,26 +16,26 @@ * at the top of the source tree. */ -#ifndef _ASTERISK_APP_STASIS_H -#define _ASTERISK_APP_STASIS_H +#ifndef _ASTERISK_STASIS_APP_H +#define _ASTERISK_STASIS_APP_H /*! \file * - * \brief Stasis Application API. See \ref app_stasis "Stasis Application API" + * \brief Stasis Application API. See \ref res_stasis "Stasis Application API" * for detailed documentation. * * \author David M. Lee, II * \since 12 * - * \page app_stasis Stasis Application API + * \page res_stasis Stasis Application API * * This is the API that binds the Stasis dialplan application to external * Stasis applications, such as \c res_stasis_websocket. * - * This module registers a dialplan function named \c Stasis, which is used to - * put a channel into the named Stasis app. As a channel enters and leaves the - * Stasis diaplan applcation, the Stasis app receives a \c 'stasis-start' and \c - * 'stasis-end' events. + * The associated \c res_stasis module registers a dialplan function named \c + * Stasis, which uses \c res_stasis to put a channel into the named Stasis + * app. As a channel enters and leaves the Stasis diaplan application, the + * Stasis app receives a \c 'stasis-start' and \c 'stasis-end' events. * * Stasis apps register themselves using the \ref stasis_app_register and * stasis_app_unregister functions. Messages are sent to an appliction using @@ -53,6 +53,25 @@ struct ast_channel_snapshot; /*! @{ */ +/*! + * \brief Control a channel using \c stasis_app. + * + * This function blocks until the channel hangs up, or + * stasis_app_control_continue() is called on the channel's \ref + * stasis_app_control struct. + * + * \param chan Channel to control with Stasis. + * \param app_name Application controlling the channel. + * \param argc Number of arguments for the application. + * \param argv Arguments for the application. + */ +int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, + char *argv[]); + +/*! @} */ + +/*! @{ */ + /*! * \brief Callback for Stasis application handler. * @@ -110,7 +129,7 @@ struct stasis_app_control; * \brief Returns the handler for the given channel * \param chan Channel to handle. * \return NULL channel not in Stasis application - * \return Pointer to app_stasis handler. + * \return Pointer to stasis handler. */ struct stasis_app_control *stasis_app_control_find_by_channel( const struct ast_channel *chan); @@ -126,15 +145,4 @@ void stasis_app_control_continue(struct stasis_app_control *handler); /*! @} */ -/*! @{ */ - -/*! - * \brief Build a JSON object from a \ref ast_channel_snapshot. - * \return JSON object representing channel snapshot. - * \return \c NULL on error - */ -struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot); - -/*! @} */ - -#endif /* _ASTERISK_APP_STASIS_H */ +#endif /* _ASTERISK_STASIS_APP_H */ diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h index c5464ba03a..41d2da8185 100644 --- a/include/asterisk/stasis_channels.h +++ b/include/asterisk/stasis_channels.h @@ -291,6 +291,13 @@ void ast_channel_publish_dial(struct ast_channel *caller, /*! @} */ +/*! + * \brief Build a JSON object from a \ref ast_channel_snapshot. + * \return JSON object representing channel snapshot. + * \return \c NULL on error + */ +struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot); + /*! * \brief Dispose of the stasis channel topics and message types */ diff --git a/main/json.c b/main/json.c index 07d2c5e95b..2aa53875b0 100644 --- a/main/json.c +++ b/main/json.c @@ -519,16 +519,12 @@ struct ast_json *ast_json_dialplan_cep(const char *context, const char *exten, i "priority", priority != -1 ? ast_json_integer_create(priority) : ast_json_null()); } -struct ast_json *ast_json_timeval(const struct timeval *tv, const char *zone) +struct ast_json *ast_json_timeval(const struct timeval tv, const char *zone) { char buf[AST_ISO8601_LEN]; struct ast_tm tm = {}; - if (tv == NULL) { - return NULL; - } - - ast_localtime(tv, &tm, zone); + ast_localtime(&tv, &tm, zone); ast_strftime(buf, sizeof(buf),AST_ISO8601_FORMAT, &tm); diff --git a/main/manager_channels.c b/main/manager_channels.c index 03cf71fd8c..004467e3bc 100644 --- a/main/manager_channels.c +++ b/main/manager_channels.c @@ -665,7 +665,7 @@ static void channel_dtmf_begin(struct ast_channel_blob *obj) const char *direction = ast_json_string_get(ast_json_object_get(obj->blob, "direction")); - channel_event_string = manager_build_channel_state_string(obj->snapshot); + channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; @@ -706,7 +706,7 @@ static void channel_dtmf_end(struct ast_channel_blob *obj) long duration_ms = ast_json_integer_get(ast_json_object_get(obj->blob, "duration_ms")); - channel_event_string = manager_build_channel_state_string(obj->snapshot); + channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; diff --git a/main/stasis_channels.c b/main/stasis_channels.c index 363ceb7e42..b40f135725 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -458,6 +458,37 @@ void ast_channel_publish_varset(struct ast_channel *chan, const char *name, cons publish_message_for_channel_topics(msg, chan); } +struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot) +{ + RAII_VAR(struct ast_json *, json_chan, NULL, ast_json_unref); + + if (snapshot == NULL) { + return NULL; + } + + json_chan = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s, s: s, s: s," + " s: s, s: s, s: s, s: s, s: o, s: o, s: o," + " s: o" + "}", + "name", snapshot->name, + "state", ast_state2str(snapshot->state), + "accountcode", snapshot->accountcode, + "peeraccount", snapshot->peeraccount, + "userfield", snapshot->userfield, + "uniqueid", snapshot->uniqueid, + "linkedid", snapshot->linkedid, + "parkinglot", snapshot->parkinglot, + "hangupsource", snapshot->hangupsource, + "appl", snapshot->appl, + "data", snapshot->data, + "dialplan", ast_json_dialplan_cep(snapshot->context, snapshot->exten, snapshot->priority), + "caller", ast_json_name_number(snapshot->caller_name, snapshot->caller_number), + "connected", ast_json_name_number(snapshot->connected_name, snapshot->connected_number), + "creationtime", ast_json_timeval(snapshot->creationtime, NULL)); + + return ast_json_ref(json_chan); +} + void ast_stasis_channels_shutdown(void) { ao2_cleanup(channel_snapshot_type); diff --git a/res/res_stasis.c b/res/res_stasis.c new file mode 100644 index 0000000000..3527adaa58 --- /dev/null +++ b/res/res_stasis.c @@ -0,0 +1,589 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * David M. Lee, II + * + * 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 Stasis application support. + * + * \author David M. Lee, II + */ + +/*** MODULEINFO + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/astobj2.h" +#include "asterisk/channel.h" +#include "asterisk/lock.h" +#include "asterisk/module.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_app.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/strings.h" + +/*! + * \brief Number of buckets for the Stasis application hash table. Remember to + * keep it a prime number! + */ +#define APPS_NUM_BUCKETS 127 + +/*! + * \brief Number of buckets for the Stasis application hash table. Remember to + * keep it a prime number! + */ +#define CONTROLS_NUM_BUCKETS 127 + +/*! + * \brief Stasis application container. Please call apps_registry() instead of + * directly accessing. + */ +struct ao2_container *__apps_registry; + +struct ao2_container *__app_controls; + +/*! Ref-counting accessor for the stasis applications container */ +static struct ao2_container *apps_registry(void) +{ + ao2_ref(__apps_registry, +1); + return __apps_registry; +} + +static struct ao2_container *app_controls(void) +{ + ao2_ref(__app_controls, +1); + return __app_controls; +} + +struct app { + /*! Callback function for this application. */ + stasis_app_cb handler; + /*! Opaque data to hand to callback function. */ + void *data; + /*! Name of the Stasis application */ + char name[]; +}; + +static void app_dtor(void *obj) +{ + struct app *app = obj; + + ao2_cleanup(app->data); + app->data = NULL; +} + +/*! Constructor for \ref app. */ +static struct app *app_create(const char *name, stasis_app_cb handler, void *data) +{ + struct app *app; + size_t size; + + ast_assert(name != NULL); + ast_assert(handler != NULL); + + size = sizeof(*app) + strlen(name) + 1; + app = ao2_alloc_options(size, app_dtor, AO2_ALLOC_OPT_LOCK_MUTEX); + + if (!app) { + return NULL; + } + + strncpy(app->name, name, size - sizeof(*app)); + app->handler = handler; + ao2_ref(data, +1); + app->data = data; + + return app; +} + +/*! AO2 hash function for \ref app */ +static int app_hash(const void *obj, const int flags) +{ + const struct app *app = obj; + const char *name = flags & OBJ_KEY ? obj : app->name; + + return ast_str_hash(name); +} + +/*! AO2 comparison function for \ref app */ +static int app_compare(void *lhs, void *rhs, int flags) +{ + const struct app *lhs_app = lhs; + const struct 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 | CMP_STOP; + } else { + return 0; + } +} + +/*! + * \brief Send a message to the given application. + * \param app App to send the message to. + * \param message Message to send. + */ +static void app_send(struct app *app, struct ast_json *message) +{ + app->handler(app->data, app->name, message); +} + +struct stasis_app_control { + /*! + * When set, /c app_stasis should exit and continue in the dialplan. + */ + int continue_to_dialplan:1; + /*! Uniqueid of the associated channel */ + char channel_id[]; +}; + +static struct stasis_app_control *control_create(const char *uniqueid) +{ + struct stasis_app_control *control; + size_t size; + + size = sizeof(*control) + strlen(uniqueid) + 1; + control = ao2_alloc(size, NULL); + if (!control) { + return NULL; + } + + strncpy(control->channel_id, uniqueid, size - sizeof(*control)); + + return control; +} + +/*! AO2 hash function for \ref stasis_app_control */ +static int control_hash(const void *obj, const int flags) +{ + const struct stasis_app_control *control = obj; + const char *id = flags & OBJ_KEY ? obj : control->channel_id; + + return ast_str_hash(id); +} + +/*! AO2 comparison function for \ref stasis_app_control */ +static int control_compare(void *lhs, void *rhs, int flags) +{ + const struct stasis_app_control *lhs_control = lhs; + const struct stasis_app_control *rhs_control = rhs; + const char *rhs_name = + flags & OBJ_KEY ? rhs : rhs_control->channel_id; + + if (strcmp(lhs_control->channel_id, rhs_name) == 0) { + return CMP_MATCH | CMP_STOP; + } else { + return 0; + } +} + +struct stasis_app_control *stasis_app_control_find_by_channel( + const struct ast_channel *chan) +{ + RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); + if (chan == NULL) { + return NULL; + } + + controls = app_controls(); + return ao2_find(controls, ast_channel_uniqueid(chan), OBJ_KEY); +} + +/*! + * \brief Test the \c continue_to_dialplan bit for the given \a app. + * + * The bit is also reset for the next call. + * + * \param app Application to check the \c continue_to_dialplan bit. + * \return Zero to remain in \c Stasis + * \return Non-zero to continue in the dialplan + */ +static int control_continue_test_and_reset(struct stasis_app_control *control) +{ + int r; + SCOPED_AO2LOCK(lock, control); + + r = control->continue_to_dialplan; + control->continue_to_dialplan = 0; + return r; +} + +void stasis_app_control_continue(struct stasis_app_control *control) +{ + SCOPED_AO2LOCK(lock, control); + control->continue_to_dialplan = 1; +} + +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_channel_snapshot *, snapshot, NULL, ao2_cleanup); + + struct ast_json *json_args; + int i; + + ast_assert(chan != NULL); + + /* Set channel info */ + snapshot = ast_channel_snapshot_create(chan); + if (!snapshot) { + return -1; + } + + msg = ast_json_pack("{s: {s: [], s: o}}", + "stasis-start", + "args", + "channel", ast_channel_snapshot_to_json(snapshot)); + + if (!msg) { + return -1; + } + + /* Append arguments to args array */ + json_args = ast_json_object_get( + ast_json_object_get(msg, "stasis-start"), + "args"); + ast_assert(json_args != NULL); + for (i = 0; i < argc; ++i) { + int r = ast_json_array_append(json_args, + ast_json_string_create(argv[i])); + if (r != 0) { + ast_log(LOG_ERROR, "Error appending start message\n"); + return -1; + } + } + + app_send(app, msg); + return 0; +} + +static int send_end_msg(struct 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); + + ast_assert(chan != NULL); + + /* Set channel info */ + snapshot = ast_channel_snapshot_create(chan); + if (snapshot == NULL) { + return -1; + } + msg = app_event_create("stasis-end", snapshot, NULL); + if (!msg) { + return -1; + } + + app_send(app, msg); + return 0; +} + +static void dtmf_handler(struct app *app, 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; + } + + extra = ast_json_pack( + "{s: o}", + "digit", ast_json_ref(ast_json_object_get(obj->blob, "digit"))); + if (!extra) { + return; + } + + msg = app_event_create("dtmf-received", obj->snapshot, extra); + if (!msg) { + return; + } + + app_send(app, msg); +} + +static void blob_handler(struct app *app, struct ast_channel_blob *blob) +{ + /* To simplify events, we'll only generate on DTMF end */ + if (strcmp(ast_channel_blob_json_type(blob), "dtmf_end") == 0) { + dtmf_handler(app, blob); + } +} + +static void sub_handler(void *data, struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct app *app = data; + if (ast_channel_snapshot_type() == stasis_message_type(message)) { + 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; + } + app_send(app, msg); + } else if (ast_channel_blob_type() == stasis_message_type(message)) { + struct ast_channel_blob *blob = stasis_message_data(message); + blob_handler(app, blob); + } + if (stasis_subscription_final_message(sub, message)) { + ao2_cleanup(data); + } +} + +/*! + * \brief In addition to running ao2_cleanup(), this function also removes the + * object from the app_controls() container. + */ +static void control_unlink(struct stasis_app_control *control) +{ + RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); + + if (!control) { + return; + } + + controls = app_controls(); + ao2_unlink_flags(controls, control, + OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA); + ao2_cleanup(control); +} + +/*! /brief Stasis dialplan application callback */ +int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, + char *argv[]) +{ + 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; + + ast_assert(chan != NULL); + + app = ao2_find(apps, app_name, OBJ_KEY); + if (!app) { + ast_log(LOG_ERROR, + "Stasis app '%s' not registered\n", app_name); + return -1; + } + + { + RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); + + controls = app_controls(); + control = control_create(ast_channel_uniqueid(chan)); + if (!control) { + ast_log(LOG_ERROR, "Allocated failed\n"); + return -1; + } + 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; + } + + while (!hungup && !control_continue_test_and_reset(control) && ast_waitfor(chan, -1) > -1) { + RAII_VAR(struct ast_frame *, f, ast_read(chan), ast_frame_dtor); + if (!f) { + ast_debug(3, "%s: No more frames. Must be done, I guess.\n", ast_channel_uniqueid(chan)); + break; + } + + switch (f->frametype) { + case AST_FRAME_CONTROL: + if (f->subclass.integer == AST_CONTROL_HANGUP) { + ast_debug(3, "%s: Received hangup\n", + ast_channel_uniqueid(chan)); + hungup = 1; + } + break; + default: + /* Not handled; discard */ + break; + } + } + + res = send_end_msg(app, chan); + if (res != 0) { + ast_log(LOG_ERROR, + "Error sending end message to %s\n", app_name); + return res; + } + + return res; +} + +int stasis_app_send(const char *app_name, struct ast_json *message) +{ + RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup); + RAII_VAR(struct app *, app, NULL, ao2_cleanup); + + app = ao2_find(apps, app_name, OBJ_KEY); + + if (!app) { + /* XXX We can do a better job handling late binding, queueing up + * the call for a few seconds to wait for the app to register. + */ + ast_log(LOG_WARNING, + "Stasis app '%s' not registered\n", app_name); + return -1; + } + + app_send(app, message); + return 0; +} + +int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data) +{ + RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup); + RAII_VAR(struct app *, app, NULL, ao2_cleanup); + + SCOPED_LOCK(apps_lock, apps, ao2_lock, ao2_unlock); + + app = ao2_find(apps, app_name, OBJ_KEY | OBJ_NOLOCK); + + if (app) { + RAII_VAR(struct ast_json *, msg, 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); + + app->handler = handler; + ao2_cleanup(app->data); + ao2_ref(data, +1); + app->data = data; + } else { + app = app_create(app_name, handler, data); + if (app) { + ao2_link_flags(apps, app, OBJ_NOLOCK); + } else { + return -1; + } + } + + return 0; +} + +void stasis_app_unregister(const char *app_name) +{ + RAII_VAR(struct ao2_container *, apps, NULL, ao2_cleanup); + + if (app_name) { + apps = apps_registry(); + ao2_cleanup(ao2_find(apps, app_name, OBJ_KEY | OBJ_UNLINK)); + } +} + +static int load_module(void) +{ + int r = 0; + + __apps_registry = + ao2_container_alloc(APPS_NUM_BUCKETS, app_hash, app_compare); + if (__apps_registry == NULL) { + return AST_MODULE_LOAD_FAILURE; + } + + __app_controls = ao2_container_alloc(CONTROLS_NUM_BUCKETS, + control_hash, control_compare); + if (__app_controls == NULL) { + return AST_MODULE_LOAD_FAILURE; + } + + return r; +} + +static int unload_module(void) +{ + int r = 0; + + ao2_cleanup(__apps_registry); + __apps_registry = NULL; + + ao2_cleanup(__app_controls); + __app_controls = NULL; + + return r; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, + "Stasis application support", + .load = load_module, + .unload = unload_module); diff --git a/res/res_stasis.exports.in b/res/res_stasis.exports.in new file mode 100644 index 0000000000..0ad493c49e --- /dev/null +++ b/res/res_stasis.exports.in @@ -0,0 +1,6 @@ +{ + global: + LINKER_SYMBOL_PREFIXstasis_app_*; + local: + *; +}; diff --git a/res/res_stasis_websocket.c b/res/res_stasis_websocket.c index bfaeea07ce..3ef0085f3d 100644 --- a/res/res_stasis_websocket.c +++ b/res/res_stasis_websocket.c @@ -24,7 +24,7 @@ */ /*** MODULEINFO - app_stasis + res_stasis res_http_websocket core ***/ @@ -33,11 +33,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") -#include "asterisk/app_stasis.h" #include "asterisk/astobj2.h" #include "asterisk/http_websocket.h" #include "asterisk/json.h" #include "asterisk/module.h" +#include "asterisk/stasis_app.h" #include "asterisk/strings.h" #include "asterisk/utils.h" @@ -322,6 +322,6 @@ static int unload_module(void) AST_MODULE_INFO(ASTERISK_GPL_KEY, 0, "Stasis HTTP bindings", .load = load_module, .unload = unload_module, - .nonoptreq = "app_stasis,res_http_websocket", + .nonoptreq = "res_stasis,res_http_websocket", .load_pri = AST_MODPRI_APP_DEPEND, ); diff --git a/tests/test_json.c b/tests/test_json.c index 9b4be5bebf..e87003f747 100644 --- a/tests/test_json.c +++ b/tests/test_json.c @@ -1659,12 +1659,11 @@ AST_TEST_DEFINE(json_test_timeval) break; } - ast_test_validate(test, NULL == ast_json_timeval(NULL, NULL)); expected = ast_json_string_create("2013-02-07T09:32:34.314-0600"); tv.tv_sec = 1360251154; tv.tv_usec = 314159; - uut = ast_json_timeval(&tv, "America/Chicago"); + uut = ast_json_timeval(tv, "America/Chicago"); ast_test_validate(test, ast_json_equal(expected, uut)); diff --git a/tests/test_app_stasis.c b/tests/test_res_stasis.c similarity index 94% rename from tests/test_app_stasis.c rename to tests/test_res_stasis.c index 02eb85af0b..0e7daf5dde 100644 --- a/tests/test_app_stasis.c +++ b/tests/test_res_stasis.c @@ -25,7 +25,7 @@ /*** MODULEINFO TEST_FRAMEWORK - app_stasis + res_stasis core ***/ @@ -35,9 +35,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/module.h" #include "asterisk/test.h" -#include "asterisk/app_stasis.h" +#include "asterisk/stasis_app.h" -static const char *test_category = "/stasis/app/"; +static const char *test_category = "/stasis/res/"; AST_TEST_DEFINE(app_invoke_dne) { @@ -187,8 +187,8 @@ static int load_module(void) return AST_MODULE_LOAD_SUCCESS; } -AST_MODULE_INFO(ASTERISK_GPL_KEY, 0, "Stasis Core testing", - .load = load_module, - .unload = unload_module, - .nonoptreq = "app_stasis" +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Stasis Core testing", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis", ); diff --git a/tests/test_stasis_channels.c b/tests/test_stasis_channels.c index fe398379b1..e3527baf33 100644 --- a/tests/test_stasis_channels.c +++ b/tests/test_stasis_channels.c @@ -199,11 +199,76 @@ AST_TEST_DEFINE(multi_channel_blob_snapshots) return AST_TEST_PASS; } +AST_TEST_DEFINE(channel_snapshot_json) +{ + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, actual, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = test_category; + info->summary = "Test creation of ast_channel_blob objects"; + info->description = "Test creation of ast_channel_blob objects"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_validate(test, NULL == ast_channel_snapshot_to_json(NULL)); + + chan = ast_channel_alloc(0, AST_STATE_DOWN, "cid_num", "cid_name", "acctcode", "exten", "context", NULL, 0, "TEST/name"); + ast_test_validate(test, NULL != chan); + snapshot = ast_channel_snapshot_create(chan); + ast_test_validate(test, NULL != snapshot); + + actual = ast_channel_snapshot_to_json(snapshot); + expected = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s, s: s, s: s," + " s: s, s: s, s: s, s: s," + " s: { s: s, s: s, s: i }," + " s: { s: s, s: s }," + " s: { s: s, s: s }," + " s: o" + "}", + "name", "TEST/name", + "state", "Down", + "accountcode", "acctcode", + "peeraccount", "", + "userfield", "", + "uniqueid", ast_channel_uniqueid(chan), + "linkedid", ast_channel_uniqueid(chan), + "parkinglot", "", + "hangupsource", "", + "appl", "", + "data", "", + "dialplan", + "context", "context", + "exten", "exten", + "priority", 1, + "caller", + "name", "cid_name", + "number", "cid_num", + "connected", + "name", "", + "number", "", + "creationtime", + ast_json_timeval( + ast_channel_creationtime(chan), NULL)); + + ast_test_validate(test, ast_json_equal(expected, actual)); + + return AST_TEST_PASS; +} + static int unload_module(void) { AST_TEST_UNREGISTER(channel_blob_create); AST_TEST_UNREGISTER(multi_channel_blob_create); AST_TEST_UNREGISTER(multi_channel_blob_snapshots); + AST_TEST_UNREGISTER(channel_snapshot_json); return 0; } @@ -213,6 +278,7 @@ static int load_module(void) AST_TEST_REGISTER(channel_blob_create); AST_TEST_REGISTER(multi_channel_blob_create); AST_TEST_REGISTER(multi_channel_blob_snapshots); + AST_TEST_REGISTER(channel_snapshot_json); return AST_MODULE_LOAD_SUCCESS; }