Stasis application WebSocket support

This is the API that binds the Stasis dialplan application to external
Stasis applications. It also adds the beginnings of WebSocket
application support.

This module registers a dialplan function named Stasis, which is used
to put a channel into the named Stasis app. As a channel enters and
leaves the Stasis diaplan application, the Stasis app receives a
'stasis-start' and 'stasis-end' events.

Stasis apps register themselves using the stasis_app_register and
stasis_app_unregister functions. Messages are sent to an application
using stasis_app_send.

Finally, Stasis apps control channels through the use of the
stasis_app_control object, and the family of stasis_app_control_*
functions.

Other changes along for the ride are:
 * An ast_frame_dtor function that's RAII_VAR safe
 * Some common JSON encoders for name/number, timeval, and
   context/extension/priority

Review: https://reviewboard.asterisk.org/r/2361/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@384879 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
David M. Lee 2013-04-08 13:27:45 +00:00
parent 426095bc55
commit a2a53cc306
14 changed files with 1536 additions and 52 deletions

View File

@ -38,3 +38,4 @@ ifneq ($(findstring $(OSARCH), mingw32 cygwin ),)
LIBS+= -lres_smdi.so
endif
app_stasis.so: stasis_json.o

555
apps/app_stasis.c Normal file
View File

@ -0,0 +1,555 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2012 - 2013, Digium, Inc.
*
* David M. Lee, II <dlee@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 Stasis dialplan application.
*
* \author David M. Lee, II <dlee@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/app.h"
#include "asterisk/app_stasis.h"
#include "asterisk/astobj2.h"
#include "asterisk/channel.h"
#include "asterisk/module.h"
#include "asterisk/stasis.h"
#include "asterisk/strings.h"
/*** DOCUMENTATION
<application name="Stasis" language="en_US">
<synopsis>Invoke an external Stasis application.</synopsis>
<syntax>
<parameter name="app_name" required="true">
<para>Name of the application to invoke.</para>
</parameter>
<parameter name="args">
<para>Optional comma-delimited arguments for the application invocation.</para>
</parameter>
</syntax>
<description>
<para>
Invoke a Stasis application.
</para>
</description>
</application>
***/
/*! \brief Maximum number of arguments for the Stasis dialplan application */
#define MAX_ARGS 128
/*! \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_uniqueid[];
};
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_uniqueid, uniqueid, size - sizeof(*control));
return control;
}
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 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);
}
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)
{
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);
AST_APP_ARG(app_argv)[MAX_ARGS];
);
ast_assert(chan != NULL);
ast_assert(data != NULL);
/* parse the arguments */
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (args.argc < 1) {
ast_log(LOG_WARNING, "Stasis app_name argument missing\n");
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));
}
}
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, app_hash, app_compare);
if (__app_controls == NULL) {
return AST_MODULE_LOAD_FAILURE;
}
r |= ast_register_application_xml(stasis, app_stasis_exec);
return r;
}
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;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis dialplan application",
.load = load_module,
.unload = unload_module);

View File

@ -0,0 +1,6 @@
{
global:
LINKER_SYMBOL_PREFIXstasis_*;
local:
*;
};

77
apps/stasis_json.c Normal file
View File

@ -0,0 +1,77 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013, Digium, Inc.
*
* David M. Lee, II <dlee@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 Stasis application JSON converters.
*
* \author David M. Lee, II <dlee@digium.com>
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/app_stasis.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);
}

View File

@ -0,0 +1,138 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2012 - 2013, Digium, Inc.
*
* David M. Lee, II <dlee@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_APP_STASIS_H
#define _ASTERISK_APP_STASIS_H
/*! \file
*
* \brief Stasis Application API. See \ref app_stasis "Stasis Application API"
* for detailed documentation.
*
* \author David M. Lee, II <dlee@digium.com>
* \since 12
*
* \page app_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.
*
* Stasis apps register themselves using the \ref stasis_app_register and
* stasis_app_unregister functions. Messages are sent to an appliction using
* \ref stasis_app_send.
*
* Finally, Stasis apps control channels through the use of the \ref
* stasis_app_control object, and the family of \c stasis_app_control_*
* functions.
*/
#include "asterisk/channel.h"
#include "asterisk/json.h"
/*! @{ */
/*!
* \brief Callback for Stasis application handler.
*
* The message given to the handler is a borrowed copy. If you want to keep a
* reference to it, you should use \c ao2_ref() to keep it around.
*
* \param data Data ptr given when registered.
* \param app_name Name of the application being dispatched to.
* \param message Message to handle. (borrowed copy)
*/
typedef void (*stasis_app_cb)(void *data, const char *app_name,
struct ast_json *message);
/*!
* \brief Register a new Stasis application.
*
* If an application is already registered with the given name, the old
* application is sent a 'replaced' message and unregistered.
*
* \param app_name Name of this application.
* \param handler Callback for application messages.
* \param data Data blob to pass to the callback. Must be AO2 managed.
* \return 0 for success
* \return -1 for error.
*/
int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data);
/*!
* \brief Unregister a Stasis application.
* \param app_name Name of the application to unregister.
*/
void stasis_app_unregister(const char *app_name);
/*!
* \brief Send a message to the given Stasis application.
*
* The message given to the handler is a borrowed copy. If you want to keep a
* reference to it, you should use \c ao2_ref() to keep it around.
*
* \param app_name Name of the application to invoke.
* \param message Message to send (borrowed reference)
* \return 0 for success.
* \return -1 for error.
*/
int stasis_app_send(const char *app_name, struct ast_json *message);
/*! @} */
/*! @{ */
/*! \brief Handler for controlling a channel that's in a Stasis application */
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.
*/
struct stasis_app_control *stasis_app_control_find_by_channel(
const struct ast_channel *chan);
/*!
* \brief Exit \c app_stasis and continue execution in the dialplan.
*
* If the channel is no longer in \c app_stasis, this function does nothing.
*
* \param handler Handler for \c app_stasis
*/
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 */

View File

@ -39,11 +39,11 @@ extern "C" {
/*!
* \page Def_Frame AST Multimedia and signalling frames
* \section Def_AstFrame What is an ast_frame ?
* A frame of data read used to communicate between
* A frame of data read used to communicate between
* between channels and applications.
* Frames are divided into frame types and subclasses.
*
* \par Frame types
* \par Frame types
* \arg \b VOICE: Voice data, subclass is codec (AST_FORMAT_*)
* \arg \b VIDEO: Video data, subclass is codec (AST_FORMAT_*)
* \arg \b DTMF: A DTMF digit, subclass is the digit
@ -88,7 +88,7 @@ extern "C" {
*/
/*!
* \brief Frame types
* \brief Frame types
*
* \note It is important that the values of each frame type are never changed,
* because it will break backwards compatability with older versions.
@ -113,11 +113,11 @@ enum ast_frame_type {
AST_FRAME_IMAGE,
/*! HTML Frame */
AST_FRAME_HTML,
/*! Comfort Noise frame (subclass is level of CNG in -dBov),
/*! Comfort Noise frame (subclass is level of CNG in -dBov),
body may include zero or more 8-bit quantization coefficients */
AST_FRAME_CNG,
/*! Modem-over-IP data streams */
AST_FRAME_MODEM,
AST_FRAME_MODEM,
/*! DTMF begin event, subclass is the digit */
AST_FRAME_DTMF_BEGIN,
};
@ -137,24 +137,24 @@ union ast_frame_subclass {
*/
struct ast_frame {
/*! Kind of frame */
enum ast_frame_type frametype;
enum ast_frame_type frametype;
/*! Subclass, frame dependent */
union ast_frame_subclass subclass;
/*! Length of data */
int datalen;
int datalen;
/*! Number of samples in this frame */
int samples;
int samples;
/*! Was the data malloc'd? i.e. should we free it when we discard the frame? */
int mallocd;
int mallocd;
/*! The number of bytes allocated for a malloc'd frame header */
size_t mallocd_hdr_len;
/*! How many bytes exist _before_ "data" that can be used if needed */
int offset;
int offset;
/*! Optional source of frame for debugging */
const char *src;
const char *src;
/*! Pointer to actual data */
union { void *ptr; uint32_t uint32; char pad[8]; } data;
/*! Global delivery time */
/*! Global delivery time */
struct timeval delivery;
/*! For placing in a linked list */
AST_LIST_ENTRY(ast_frame) frame_list;
@ -197,7 +197,7 @@ extern struct ast_frame ast_null_frame;
* RTP header information into the space provided by AST_FRIENDLY_OFFSET instead
* of having to create a new buffer with the necessary space allocated.
*/
#define AST_FRIENDLY_OFFSET 64
#define AST_FRIENDLY_OFFSET 64
#define AST_MIN_OFFSET 32 /*! Make sure we keep at least this much handy */
/*! Need the header be free'd? */
@ -353,10 +353,10 @@ struct ast_control_pvt_cause_code {
#define AST_OPTION_FLAG_ANSWER 5
#define AST_OPTION_FLAG_WTF 6
/*! Verify touchtones by muting audio transmission
/*! Verify touchtones by muting audio transmission
* (and reception) and verify the tone is still present
* Option data is a single signed char value 0 or 1 */
#define AST_OPTION_TONE_VERIFY 1
#define AST_OPTION_TONE_VERIFY 1
/*! Put a compatible channel into TDD (TTY for the hearing-impared) mode
* Option data is a single signed char value 0 or 1 */
@ -370,7 +370,7 @@ struct ast_control_pvt_cause_code {
* Option data is a single signed char value 0 or 1 */
#define AST_OPTION_AUDIO_MODE 4
/*! Set channel transmit gain
/*! Set channel transmit gain
* Option data is a single signed char representing number of decibels (dB)
* to set gain to (on top of any gain specified in channel driver) */
#define AST_OPTION_TXGAIN 5
@ -380,7 +380,7 @@ struct ast_control_pvt_cause_code {
* to set gain to (on top of any gain specified in channel driver) */
#define AST_OPTION_RXGAIN 6
/* set channel into "Operator Services" mode
/* set channel into "Operator Services" mode
* Option data is a struct oprmode
*
* \note This option should never be sent over the network */
@ -433,7 +433,7 @@ struct ast_control_pvt_cause_code {
* Option data is a character buffer of suitable length */
#define AST_OPTION_DEVICE_NAME 16
/*! Get the CC agent type from the channel (Read only)
/*! Get the CC agent type from the channel (Read only)
* Option data is a character buffer of suitable length */
#define AST_OPTION_CC_AGENT_TYPE 17
@ -450,12 +450,12 @@ struct oprmode {
struct ast_option_header {
/* Always keep in network byte order */
#if __BYTE_ORDER == __BIG_ENDIAN
uint16_t flag:3;
uint16_t option:13;
uint16_t flag:3;
uint16_t option:13;
#else
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint16_t option:13;
uint16_t flag:3;
uint16_t option:13;
uint16_t flag:3;
#else
#error Byte order not defined
#endif
@ -463,19 +463,19 @@ struct ast_option_header {
uint8_t data[0];
};
/*! \brief Requests a frame to be allocated
*
* \param source
* Request a frame be allocated. source is an optional source of the frame,
* len is the requested length, or "0" if the caller will supply the buffer
/*! \brief Requests a frame to be allocated
*
* \param source
* Request a frame be allocated. source is an optional source of the frame,
* len is the requested length, or "0" if the caller will supply the buffer
*/
#if 0 /* Unimplemented */
struct ast_frame *ast_fralloc(char *source, int len);
#endif
/*!
/*!
* \brief Frees a frame or list of frames
*
*
* \param fr Frame to free, or head of list to free
* \param cache Whether to consider this frame for frame caching
*/
@ -483,6 +483,12 @@ void ast_frame_free(struct ast_frame *fr, int cache);
#define ast_frfree(fr) ast_frame_free(fr, 1)
/*!
* \brief NULL-safe wrapper for \ref ast_frfree, good for \ref RAII_VAR.
* \param frame Frame to free, or head of list to free.
*/
void ast_frame_dtor(struct ast_frame *frame);
/*! \brief Makes a frame independent of any static storage
* \param fr frame to act upon
* Take a frame, and if it's not been malloc'd, make a malloc'd copy
@ -498,7 +504,7 @@ void ast_frame_free(struct ast_frame *fr, int cache);
*/
struct ast_frame *ast_frisolate(struct ast_frame *fr);
/*! \brief Copies a frame
/*! \brief Copies a frame
* \param fr frame to copy
* Duplicates a frame -- should only rarely be used, typically frisolate is good enough
* \return Returns a frame on success, NULL on error
@ -507,7 +513,7 @@ struct ast_frame *ast_frdup(const struct ast_frame *fr);
void ast_swapcopy_samples(void *dst, const void *src, int samples);
/* Helpers for byteswapping native samples to/from
/* Helpers for byteswapping native samples to/from
little-endian and big-endian. */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ast_frame_byteswap_le(fr) do { ; } while(0)
@ -518,13 +524,13 @@ void ast_swapcopy_samples(void *dst, const void *src, int samples);
#endif
/*! \brief Parse an "allow" or "deny" line in a channel or device configuration
and update the capabilities and pref if provided.
and update the capabilities and pref if provided.
Video codecs are not added to codec preference lists, since we can not transcode
\return Returns number of errors encountered during parsing
*/
int ast_parse_allow_disallow(struct ast_codec_pref *pref, struct ast_format_cap *cap, const char *list, int allowing);
/*! \name AST_Smoother
/*! \name AST_Smoother
*/
/*@{ */
/*! \page ast_smooth The AST Frame Smoother
@ -584,7 +590,7 @@ struct ast_frame *ast_frame_enqueue(struct ast_frame *head, struct ast_frame *f,
/*! \brief Gets duration in ms of interpolation frame for a format */
static inline int ast_codec_interp_len(struct ast_format *format)
{
{
return (format->id == AST_FORMAT_ILBC) ? 30 : 20;
}

View File

@ -764,4 +764,48 @@ struct ast_json *ast_json_deep_copy(const struct ast_json *value);
/*!@}*/
/*!@{*/
/*!
* \brief Common JSON rendering functions for common 'objects'.
*/
/*!
* \brief Simple name/number pair.
* \param name Name
* \param number Number
* \return NULL if error (non-UTF8 characters, NULL inputs, etc.)
* \return JSON object with name and number fields
*/
struct ast_json *ast_json_name_number(const char *name, const char *number);
/*!
* \brief Construct a timeval as JSON.
*
* JSON does not define a standard date format (boo), but the de facto standard
* is to use ISO 8601 formatted string. We build a millisecond resolution string
* from the \c timeval
*
* \param tv \c timeval to encode.
* \param zone Text string of a standard system zoneinfo file. If NULL, the system localtime will be used.
* \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);
/*!
* \brief Construct a context/exten/priority as JSON.
*
* If a \c NULL is passed for \c context or \c exten, or -1 for \c priority,
* the fields is set to ast_json_null().
*
* \param context Context name.
* \param exten Extension.
* \param priority Dialplan priority.
* \return JSON object with \c context, \c exten and \c priority fields
*/
struct ast_json *ast_json_dialplan_cep(const char *context, const char *exten, int priority);
/*!@}*/
#endif /* _ASTERISK_JSON_H */

View File

@ -99,4 +99,9 @@ char *ast_strptime_locale(const char *s, const char *format, struct ast_tm *tm,
struct ast_test;
void ast_localtime_wakeup_monitor(struct ast_test *info);
/*! \brief ast_strftime for ISO8601 formatting timestamps. */
#define AST_ISO8601_FORMAT "%FT%T.%q%z"
/*! \brief Max length of an null terminated, millisecond resolution, ISO8601 timestamp string. */
#define AST_ISO8601_LEN 29
#endif /* _ASTERISK_LOCALTIME_H */

View File

@ -351,6 +351,13 @@ void ast_frame_free(struct ast_frame *frame, int cache)
}
}
void ast_frame_dtor(struct ast_frame *f)
{
if (f) {
ast_frfree(f);
}
}
/*!
* \brief 'isolates' a frame by duplicating non-malloc'ed components
* (header, src, data).

View File

@ -27,6 +27,7 @@
*/
/*** MODULEINFO
<depend>jansson</depend>
<support_level>core</support_level>
***/
@ -35,10 +36,12 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/json.h"
#include "asterisk/localtime.h"
#include "asterisk/module.h"
#include "asterisk/utils.h"
#include <jansson.h>
#include <time.h>
/*!
* \brief Function wrapper around ast_malloc macro.
@ -501,6 +504,37 @@ struct ast_json *ast_json_deep_copy(const struct ast_json *value)
return (struct ast_json *)json_deep_copy((json_t *)value);
}
struct ast_json *ast_json_name_number(const char *name, const char *number)
{
return ast_json_pack("{s: s, s: s}",
"name", name,
"number", number);
}
struct ast_json *ast_json_dialplan_cep(const char *context, const char *exten, int priority)
{
return ast_json_pack("{s: o, s: o, s: o}",
"context", context ? ast_json_string_create(context) : ast_json_null(),
"exten", exten ? ast_json_string_create(exten) : ast_json_null(),
"priority", priority != -1 ? ast_json_integer_create(priority) : ast_json_null());
}
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_strftime(buf, sizeof(buf),AST_ISO8601_FORMAT, &tm);
return ast_json_string_create(buf);
}
void ast_json_init(void)
{
/* Setup to use Asterisk custom allocators */

326
res/res_stasis_websocket.c Normal file
View File

@ -0,0 +1,326 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2012 - 2013, Digium, Inc.
*
* David M. Lee, II <dlee@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 HTTP binding for the Stasis API
*
* \author David M. Lee, II <dlee@digium.com>
*/
/*** MODULEINFO
<depend type="module">app_stasis</depend>
<depend type="module">res_http_websocket</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
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/strings.h"
#include "asterisk/utils.h"
/*! WebSocket protocol for Stasis */
static const char * const ws_protocol = "stasis";
/*! Message to send when out of memory */
static struct ast_json *oom_json;
/*! Number of buckets for the Stasis application hash table. Remember to keep it
* a prime number!
*/
#define APPS_NUM_BUCKETS 7
struct websocket_app {
char *name;
};
/*!
* \internal
* \brief Helper to write a JSON object to a WebSocket.
* \param session WebSocket session.
* \param message JSON message.
* \return 0 on success.
* \return -1 on error.
*/
static int websocket_write_json(struct ast_websocket *session,
struct ast_json *message)
{
RAII_VAR(char *, str, ast_json_dump_string(message), ast_free);
if (str == NULL) {
ast_log(LOG_ERROR, "Failed to encode JSON object\n");
return -1;
}
return ast_websocket_write(session, AST_WEBSOCKET_OPCODE_TEXT, str,
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;
};
static void session_dtor(void *obj)
{
struct stasis_ws_session_info *session = obj;
/* session_shutdown should have been called before */
ast_assert(session->ws_session == NULL);
ast_assert(session->websocket_apps == NULL);
}
static struct stasis_ws_session_info *session_create(
struct ast_websocket *ws_session)
{
RAII_VAR(struct stasis_ws_session_info *, session, NULL, ao2_cleanup);
session = ao2_alloc(sizeof(*session), session_dtor);
session->ws_session = ws_session;
session->websocket_apps =
ao2_container_alloc(APPS_NUM_BUCKETS, hash_app, compare_app);
if (!session->websocket_apps) {
return NULL;
}
ao2_ref(session, +1);
return session;
}
/*!
* \brief Explicitly shutdown a session.
*
* An explicit shutdown is necessary, since stasis-app has a reference to this
* session. We also need to be sure to null out the \c ws_session field, since
* the websocket is about to go away.
*
* \param session Session info struct.
*/
static void session_shutdown(struct stasis_ws_session_info *session)
{
struct ao2_iterator i;
struct websocket_app *app;
SCOPED_AO2LOCK(lock, session);
i = ao2_iterator_init(session->websocket_apps, 0);
while ((app = ao2_iterator_next(&i))) {
stasis_app_unregister(app->name);
ao2_cleanup(app);
}
ao2_iterator_destroy(&i);
ao2_cleanup(session->websocket_apps);
session->websocket_apps = NULL;
session->ws_session = NULL;
}
/*!
* \brief Callback handler for Stasis application messages.
*/
static void app_handler(void *data, const char *app_name,
struct ast_json *message)
{
struct stasis_ws_session_info *session = data;
int res;
res = ast_json_object_set(message, "application",
ast_json_string_create(app_name));
if(res != 0) {
return;
}
ao2_lock(session);
if (session->ws_session) {
websocket_write_json(session->ws_session, message);
}
ao2_unlock(session);
}
/*!
* \brief Register for all of the apps given.
* \param session Session info struct.
* \param app_list Comma seperated list of app names to register.
*/
static int session_register_apps(struct stasis_ws_session_info *session,
const char *app_list)
{
RAII_VAR(char *, to_free, NULL, ast_free);
char *apps, *app_name;
SCOPED_AO2LOCK(lock, session);
ast_assert(session->ws_session != NULL);
ast_assert(session->websocket_apps != NULL);
to_free = apps = ast_strdup(app_list);
if (!apps) {
websocket_write_json(session->ws_session, oom_json);
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) {
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);
}
return 0;
}
static void websocket_callback(struct ast_websocket *ws_session,
struct ast_variable *parameters,
struct ast_variable *headers)
{
RAII_VAR(struct stasis_ws_session_info *, stasis_session, NULL, ao2_cleanup);
struct ast_variable *param = NULL;
int res;
ast_debug(3, "Stasis web socket connection\n");
if (ast_websocket_set_nonblock(ws_session) != 0) {
ast_log(LOG_ERROR,
"Stasis web socket failed to set nonblock; closing\n");
goto end;
}
stasis_session = session_create(ws_session);
if (!stasis_session) {
websocket_write_json(ws_session, oom_json);
goto end;
}
for (param = parameters; param; param = param->next) {
if (strcmp(param->name, "app") == 0) {
int ret = session_register_apps(
stasis_session, param->value);
if (ret != 0) {
goto end;
}
}
}
if (ao2_container_count(stasis_session->websocket_apps) == 0) {
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
msg = ast_json_pack("{s: s, s: [s]}",
"error", "MissingParams",
"params", "app");
if (msg) {
websocket_write_json(ws_session, msg);
}
goto end;
}
while ((res = ast_wait_for_input(ast_websocket_fd(ws_session), -1)) > 0) {
char *payload;
uint64_t payload_len;
enum ast_websocket_opcode opcode;
int fragmented;
int read = ast_websocket_read(ws_session, &payload, &payload_len,
&opcode, &fragmented);
if (read) {
ast_log(LOG_ERROR,
"Stasis WebSocket read error; closing\n");
break;
}
if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
break;
}
}
end:
session_shutdown(stasis_session);
ast_websocket_unref(ws_session);
}
static int load_module(void)
{
int r = 0;
oom_json = ast_json_pack("{s: s}",
"error", "OutOfMemory");
if (!oom_json) {
/* ironic */
return AST_MODULE_LOAD_FAILURE;
}
r |= ast_websocket_add_protocol(ws_protocol, websocket_callback);
return r;
}
static int unload_module(void)
{
int r = 0;
ast_json_unref(oom_json);
oom_json = NULL;
r |= ast_websocket_remove_protocol(ws_protocol, websocket_callback);
return r;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, 0, "Stasis HTTP bindings",
.load = load_module,
.unload = unload_module,
.nonoptreq = "app_stasis,res_http_websocket"
);

View File

@ -67,17 +67,6 @@ static void dispose_jitterbuffer(struct ast_jb *jb)
jb->jbobj = NULL;
}
/*! \internal \brief Destructor for frames
* \param f The frame to destroy
*/
static void dispose_frame(struct ast_frame *f)
{
if (!f) {
return;
}
ast_frfree(f);
}
/*! \internal \brief Create a test frame
* \param timestamp the time in ms of the frame
* \param seqno the frame's sequence number
@ -217,8 +206,8 @@ static struct ast_jb default_jb = {
RAII_VAR(struct ast_jb *, jb, &default_jb, dispose_jitterbuffer); \
const struct ast_jb_impl *impl; \
struct ast_jb_conf conf; \
RAII_VAR(struct ast_frame *, expected_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, actual_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, expected_frame, NULL, ast_frame_dtor); \
RAII_VAR(struct ast_frame *, actual_frame, NULL, ast_frame_dtor); \
int res; \
\
switch (cmd) { \
@ -273,8 +262,8 @@ static struct ast_jb default_jb = {
RAII_VAR(struct ast_jb *, jb, &default_jb, dispose_jitterbuffer); \
const struct ast_jb_impl *impl; \
struct ast_jb_conf conf; \
RAII_VAR(struct ast_frame *, expected_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, actual_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, expected_frame, NULL, ast_frame_dtor); \
RAII_VAR(struct ast_frame *, actual_frame, NULL, ast_frame_dtor); \
int res; \
long next; \
int i; \
@ -336,7 +325,7 @@ static struct ast_jb default_jb = {
RAII_VAR(struct ast_jb *, jb, &default_jb, dispose_jitterbuffer); \
const struct ast_jb_impl *impl; \
struct ast_jb_conf conf; \
RAII_VAR(struct ast_frame *, expected_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, expected_frame, NULL, ast_frame_dtor); \
int res; \
int i; \
\
@ -401,8 +390,8 @@ static struct ast_jb default_jb = {
RAII_VAR(struct ast_jb *, jb, &default_jb, dispose_jitterbuffer); \
const struct ast_jb_impl *impl; \
struct ast_jb_conf conf; \
RAII_VAR(struct ast_frame *, actual_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, expected_frame, NULL, dispose_frame); \
RAII_VAR(struct ast_frame *, actual_frame, NULL, ast_frame_dtor); \
RAII_VAR(struct ast_frame *, expected_frame, NULL, ast_frame_dtor); \
int res; \
long next; \
int i; \

194
tests/test_app_stasis.c Normal file
View File

@ -0,0 +1,194 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013, Digium, Inc.
*
* David M. Lee, II <dlee@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 Test Stasis Application API.
* \author\verbatim David M. Lee, II <dlee@digium.com> \endverbatim
*
* \ingroup tests
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
<depend>app_stasis</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h"
#include "asterisk/test.h"
#include "asterisk/app_stasis.h"
static const char *test_category = "/stasis/app/";
AST_TEST_DEFINE(app_invoke_dne)
{
int res;
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->category = test_category;
info->summary = "Test stasis app invocation.";
info->description = "Test stasis app invocation.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
res = stasis_app_send("i-am-not-an-app", ast_json_null());
ast_test_validate(test, -1 == res);
return AST_TEST_PASS;
}
struct app_data {
int invocations;
struct ast_json *messages;
};
static void app_data_dtor(void *obj)
{
struct app_data *actual = obj;
ast_json_unref(actual->messages);
actual->messages = NULL;
}
static struct app_data *app_data_create(void)
{
struct app_data *res = ao2_alloc(sizeof(struct app_data), app_data_dtor);
if (!res) {
return NULL;
}
res->messages = ast_json_array_create();
return res;
}
static void test_handler(void *data, const char *app_name, struct ast_json *message)
{
struct app_data *actual = data;
int res;
++(actual->invocations);
res = ast_json_array_append(actual->messages, ast_json_copy(message));
ast_assert(res == 0);
}
AST_TEST_DEFINE(app_invoke_one)
{
RAII_VAR(struct app_data *, app_data, NULL, ao2_cleanup);
RAII_VAR(char *, app_name, NULL, stasis_app_unregister);
RAII_VAR(struct ast_json *, expected_message, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
int res;
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->category = test_category;
info->summary = "Test stasis app invocation.";
info->description = "Test stasis app invocation.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
app_name = "test-handler";
app_data = app_data_create();
stasis_app_register(app_name, test_handler, app_data);
message = ast_json_pack("{ s: o }", "test-message", ast_json_null());
expected_message = ast_json_pack("[o]", ast_json_ref(message));
res = stasis_app_send(app_name, message);
ast_test_validate(test, 0 == res);
ast_test_validate(test, 1 == app_data->invocations);
ast_test_validate(test, ast_json_equal(expected_message, app_data->messages));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(app_replaced)
{
RAII_VAR(struct app_data *, app_data1, NULL, ao2_cleanup);
RAII_VAR(struct app_data *, app_data2, NULL, ao2_cleanup);
RAII_VAR(char *, app_name, NULL, stasis_app_unregister);
RAII_VAR(struct ast_json *, expected_message1, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, expected_message2, NULL, ast_json_unref);
int res;
switch (cmd) {
case TEST_INIT:
info->name = __func__;
info->category = test_category;
info->summary = "Test stasis app invocation.";
info->description = "Test stasis app invocation.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
app_name = "test-handler";
app_data1 = app_data_create();
app_data2 = app_data_create();
stasis_app_register(app_name, test_handler, app_data1);
stasis_app_register(app_name, test_handler, app_data2);
expected_message1 = ast_json_pack("[{s: {}}]", "application-replaced");
message = ast_json_pack("{ s: o }", "test-message", ast_json_null());
expected_message2 = ast_json_pack("[o]", ast_json_ref(message));
res = stasis_app_send(app_name, message);
ast_test_validate(test, 0 == res);
ast_test_validate(test, 1 == app_data1->invocations);
ast_test_validate(test, ast_json_equal(expected_message1, app_data1->messages));
ast_test_validate(test, 1 == app_data2->invocations);
ast_test_validate(test, ast_json_equal(expected_message2, app_data2->messages));
return AST_TEST_PASS;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(app_invoke_dne);
AST_TEST_UNREGISTER(app_invoke_one);
AST_TEST_UNREGISTER(app_replaced);
return 0;
}
static int load_module(void)
{
AST_TEST_REGISTER(app_replaced);
AST_TEST_REGISTER(app_invoke_one);
AST_TEST_REGISTER(app_invoke_dne);
return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, 0, "Stasis Core testing",
.load = load_module,
.unload = unload_module,
.nonoptreq = "app_stasis"
);

View File

@ -1611,6 +1611,102 @@ AST_TEST_DEFINE(json_test_clever_circle)
return AST_TEST_PASS;
}
AST_TEST_DEFINE(json_test_name_number)
{
RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish);
RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
switch (cmd) {
case TEST_INIT:
info->name = "name_number";
info->category = "/main/json/";
info->summary = "JSON encoding of name/number pair.";
info->description = "Test JSON abstraction library.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
ast_test_validate(test, NULL == ast_json_name_number("name", NULL));
ast_test_validate(test, NULL == ast_json_name_number(NULL, "1234"));
ast_test_validate(test, NULL == ast_json_name_number(NULL, NULL));
expected = ast_json_pack("{s: s, s: s}",
"name", "Jenny",
"number", "867-5309");
uut = ast_json_name_number("Jenny", "867-5309");
ast_test_validate(test, ast_json_equal(expected, uut));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(json_test_timeval)
{
RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish);
RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
struct timeval tv = {};
switch (cmd) {
case TEST_INIT:
info->name = "timeval";
info->category = "/main/json/";
info->summary = "JSON encoding of timevals.";
info->description = "Test JSON abstraction library.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
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");
ast_test_validate(test, ast_json_equal(expected, uut));
return AST_TEST_PASS;
}
AST_TEST_DEFINE(json_test_cep)
{
RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish);
RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref);
switch (cmd) {
case TEST_INIT:
info->name = "cep";
info->category = "/main/json/";
info->summary = "JSON with circular references cannot be encoded.";
info->description = "Test JSON abstraction library.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
expected = ast_json_pack("{s: o, s: o, s: o}",
"context", ast_json_null(),
"exten", ast_json_null(),
"priority", ast_json_null());
uut = ast_json_dialplan_cep(NULL, NULL, -1);
ast_test_validate(test, ast_json_equal(expected, uut));
ast_json_unref(expected);
ast_json_unref(uut);
expected = ast_json_pack("{s: s, s: s, s: i}",
"context", "main",
"exten", "4321",
"priority", 7);
uut = ast_json_dialplan_cep("main", "4321", 7);
ast_test_validate(test, ast_json_equal(expected, uut));
return AST_TEST_PASS;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(json_test_false);
@ -1661,6 +1757,9 @@ static int unload_module(void)
AST_TEST_UNREGISTER(json_test_circular_object);
AST_TEST_UNREGISTER(json_test_circular_array);
AST_TEST_UNREGISTER(json_test_clever_circle);
AST_TEST_UNREGISTER(json_test_name_number);
AST_TEST_UNREGISTER(json_test_timeval);
AST_TEST_UNREGISTER(json_test_cep);
return 0;
}
@ -1714,6 +1813,9 @@ static int load_module(void)
AST_TEST_REGISTER(json_test_circular_object);
AST_TEST_REGISTER(json_test_circular_array);
AST_TEST_REGISTER(json_test_clever_circle);
AST_TEST_REGISTER(json_test_name_number);
AST_TEST_REGISTER(json_test_timeval);
AST_TEST_REGISTER(json_test_cep);
return AST_MODULE_LOAD_SUCCESS;
}