asterisk/res/stasis/app.c
Kinsey Moore 53dbe10f5c Fix build warnings
When AST_DEVMODE is not defined, ast_asserts are not compiled into the
binary. In some cases, this means variables are not referenced or are
set but unused which causes warnings to show up.

(closes issue ASTERISK-22446)
Reported by: Jason Parker (qwell)
........

Merged revisions 398521 from http://svn.asterisk.org/svn/asterisk/branches/12


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@398522 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2013-09-06 18:53:32 +00:00

742 lines
18 KiB
C

/*
* 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 support.
*
* \author David M. Lee, II <dlee@digium.com>
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "app.h"
#include "asterisk/callerid.h"
#include "asterisk/stasis_app.h"
#include "asterisk/stasis_bridges.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/stasis_message_router.h"
struct app {
/*! Aggregation topic for this application. */
struct stasis_topic *topic;
/*! Router for handling messages forwarded to \a topic. */
struct stasis_message_router *router;
/*! Subscription to watch for bridge merge messages */
struct stasis_subscription *bridge_merge_sub;
/*! Container of the channel forwards to this app's topic. */
struct ao2_container *forwards;
/*! 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[];
};
/*! Subscription info for a particular channel/bridge. */
struct app_forwards {
/*! Count of number of times this channel/bridge has been subscribed */
int interested;
/*! Forward for the regular topic */
struct stasis_subscription *topic_forward;
/*! Forward for the caching topic */
struct stasis_subscription *topic_cached_forward;
/*! Unique id of the object being forwarded */
char id[];
};
static void forwards_dtor(void *obj)
{
#ifdef AST_DEVMODE
struct app_forwards *forwards = obj;
#endif /* AST_DEVMODE */
ast_assert(forwards->topic_forward == NULL);
ast_assert(forwards->topic_cached_forward == NULL);
}
static void forwards_unsubscribe(struct app_forwards *forwards)
{
stasis_unsubscribe(forwards->topic_forward);
forwards->topic_forward = NULL;
stasis_unsubscribe(forwards->topic_cached_forward);
forwards->topic_cached_forward = NULL;
}
static struct app_forwards *forwards_create(struct app *app,
const char *id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
if (!app || ast_strlen_zero(id)) {
return NULL;
}
forwards = ao2_alloc(sizeof(*forwards) + strlen(id) + 1, forwards_dtor);
if (!forwards) {
return NULL;
}
strcpy(forwards->id, id);
ao2_ref(forwards, +1);
return forwards;
}
/*! Forward a channel's topics to an app */
static struct app_forwards *forwards_create_channel(struct app *app,
struct ast_channel *chan)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
if (!app || !chan) {
return NULL;
}
forwards = forwards_create(app, ast_channel_uniqueid(chan));
if (!forwards) {
return NULL;
}
forwards->topic_forward = stasis_forward_all(ast_channel_topic(chan),
app->topic);
if (!forwards->topic_forward) {
return NULL;
}
forwards->topic_cached_forward = stasis_forward_all(
ast_channel_topic_cached(chan), app->topic);
if (!forwards->topic_cached_forward) {
/* Half-subscribed is a bad thing */
stasis_unsubscribe(forwards->topic_forward);
forwards->topic_forward = NULL;
return NULL;
}
ao2_ref(forwards, +1);
return forwards;
}
/*! Forward a bridge's topics to an app */
static struct app_forwards *forwards_create_bridge(struct app *app,
struct ast_bridge *bridge)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
if (!app || !bridge) {
return NULL;
}
forwards = forwards_create(app, bridge->uniqueid);
if (!forwards) {
return NULL;
}
forwards->topic_forward = stasis_forward_all(ast_bridge_topic(bridge),
app->topic);
if (!forwards->topic_forward) {
return NULL;
}
forwards->topic_cached_forward = stasis_forward_all(
ast_bridge_topic_cached(bridge), app->topic);
if (!forwards->topic_cached_forward) {
/* Half-subscribed is a bad thing */
stasis_unsubscribe(forwards->topic_forward);
forwards->topic_forward = NULL;
return NULL;
}
ao2_ref(forwards, +1);
return forwards;
}
static int forwards_sort(const void *obj_left, const void *obj_right, int flags)
{
const struct app_forwards *object_left = obj_left;
const struct app_forwards *object_right = obj_right;
const char *right_key = obj_right;
int cmp;
switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
case OBJ_POINTER:
right_key = object_right->id;
/* Fall through */
case OBJ_KEY:
cmp = strcmp(object_left->id, right_key);
break;
case OBJ_PARTIAL_KEY:
/*
* We could also use a partial key struct containing a length
* so strlen() does not get called for every comparison instead.
*/
cmp = strncmp(object_left->id, right_key, strlen(right_key));
break;
default:
/* Sort can only work on something with a full or partial key. */
ast_assert(0);
cmp = 0;
break;
}
return cmp;
}
static void app_dtor(void *obj)
{
struct app *app = obj;
ast_verb(1, "Destroying Stasis app %s\n", app->name);
ast_assert(app->router == NULL);
ast_assert(app->bridge_merge_sub == NULL);
ao2_cleanup(app->topic);
app->topic = NULL;
ao2_cleanup(app->forwards);
app->forwards = NULL;
ao2_cleanup(app->data);
app->data = NULL;
}
static void sub_default_handler(void *data, struct stasis_subscription *sub,
struct stasis_topic *topic, struct stasis_message *message)
{
struct app *app = data;
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
if (stasis_subscription_final_message(sub, message)) {
ao2_cleanup(app);
}
/* By default, send any message that has a JSON representation */
json = stasis_message_to_json(message);
if (!json) {
return;
}
app_send(app, json);
}
/*! \brief Typedef for callbacks that get called on channel snapshot updates */
typedef struct ast_json *(*channel_snapshot_monitor)(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot,
const struct timeval *tv);
static struct ast_json *simple_channel_event(
const char *type,
struct ast_channel_snapshot *snapshot,
const struct timeval *tv)
{
return ast_json_pack("{s: s, s: o, s: o}",
"type", type,
"timestamp", ast_json_timeval(*tv, NULL),
"channel", ast_channel_snapshot_to_json(snapshot));
}
static struct ast_json *channel_created_event(
struct ast_channel_snapshot *snapshot,
const struct timeval *tv)
{
return simple_channel_event("ChannelCreated", snapshot, tv);
}
static struct ast_json *channel_destroyed_event(
struct ast_channel_snapshot *snapshot,
const struct timeval *tv)
{
return ast_json_pack("{s: s, s: o, s: i, s: s, s: o}",
"type", "ChannelDestroyed",
"timestamp", ast_json_timeval(*tv, NULL),
"cause", snapshot->hangupcause,
"cause_txt", ast_cause2str(snapshot->hangupcause),
"channel", ast_channel_snapshot_to_json(snapshot));
}
static struct ast_json *channel_state_change_event(
struct ast_channel_snapshot *snapshot,
const struct timeval *tv)
{
return simple_channel_event("ChannelStateChange", snapshot, tv);
}
/*! \brief Handle channel state changes */
static struct ast_json *channel_state(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot,
const struct timeval *tv)
{
struct ast_channel_snapshot *snapshot = new_snapshot ?
new_snapshot : old_snapshot;
if (!old_snapshot) {
return channel_created_event(snapshot, tv);
} else if (!new_snapshot) {
return channel_destroyed_event(snapshot, tv);
} else if (old_snapshot->state != new_snapshot->state) {
return channel_state_change_event(snapshot, tv);
}
return NULL;
}
static struct ast_json *channel_dialplan(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot,
const struct timeval *tv)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
/* No Newexten event on cache clear or first event */
if (!old_snapshot || !new_snapshot) {
return NULL;
}
/* Empty application is not valid for a Newexten event */
if (ast_strlen_zero(new_snapshot->appl)) {
return NULL;
}
if (ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) {
return NULL;
}
return ast_json_pack("{s: s, s: o, s: s, s: s, s: o}",
"type", "ChannelDialplan",
"timestamp", ast_json_timeval(*tv, NULL),
"dialplan_app", new_snapshot->appl,
"dialplan_app_data", new_snapshot->data,
"channel", ast_channel_snapshot_to_json(new_snapshot));
}
static struct ast_json *channel_callerid(
struct ast_channel_snapshot *old_snapshot,
struct ast_channel_snapshot *new_snapshot,
const struct timeval *tv)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
/* No NewCallerid event on cache clear or first event */
if (!old_snapshot || !new_snapshot) {
return NULL;
}
if (ast_channel_snapshot_caller_id_equal(old_snapshot, new_snapshot)) {
return NULL;
}
return ast_json_pack("{s: s, s: o, s: i, s: s, s: o}",
"type", "ChannelCallerId",
"timestamp", ast_json_timeval(*tv, NULL),
"caller_presentation", new_snapshot->caller_pres,
"caller_presentation_txt", ast_describe_caller_presentation(
new_snapshot->caller_pres),
"channel", ast_channel_snapshot_to_json(new_snapshot));
}
static channel_snapshot_monitor channel_monitors[] = {
channel_state,
channel_dialplan,
channel_callerid
};
static void sub_channel_update_handler(void *data,
struct stasis_subscription *sub,
struct stasis_topic *topic,
struct stasis_message *message)
{
struct app *app = data;
struct stasis_cache_update *update;
struct ast_channel_snapshot *new_snapshot;
struct ast_channel_snapshot *old_snapshot;
const struct timeval *tv;
int i;
ast_assert(stasis_message_type(message) == stasis_cache_update_type());
update = stasis_message_data(message);
ast_assert(update->type == ast_channel_snapshot_type());
new_snapshot = stasis_message_data(update->new_snapshot);
old_snapshot = stasis_message_data(update->old_snapshot);
/* Pull timestamp from the new snapshot, or from the update message
* when there isn't one. */
tv = update->new_snapshot ?
stasis_message_timestamp(update->new_snapshot) :
stasis_message_timestamp(message);
for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
msg = channel_monitors[i](old_snapshot, new_snapshot, tv);
if (msg) {
app_send(app, msg);
}
}
}
static struct ast_json *simple_bridge_event(
const char *type,
struct ast_bridge_snapshot *snapshot,
const struct timeval *tv)
{
return ast_json_pack("{s: s, s: o, s: o}",
"type", type,
"timestamp", ast_json_timeval(*tv, NULL),
"bridge", ast_bridge_snapshot_to_json(snapshot));
}
static void sub_bridge_update_handler(void *data,
struct stasis_subscription *sub,
struct stasis_topic *topic,
struct stasis_message *message)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
struct app *app = data;
struct stasis_cache_update *update;
struct ast_bridge_snapshot *new_snapshot;
struct ast_bridge_snapshot *old_snapshot;
const struct timeval *tv;
ast_assert(stasis_message_type(message) == stasis_cache_update_type());
update = stasis_message_data(message);
ast_assert(update->type == ast_bridge_snapshot_type());
new_snapshot = stasis_message_data(update->new_snapshot);
old_snapshot = stasis_message_data(update->old_snapshot);
tv = update->new_snapshot ?
stasis_message_timestamp(update->new_snapshot) :
stasis_message_timestamp(message);
if (!new_snapshot) {
json = simple_bridge_event("BridgeDestroyed", old_snapshot, tv);
} else if (!old_snapshot) {
json = simple_bridge_event("BridgeCreated", old_snapshot, tv);
}
if (!json) {
return;
}
app_send(app, json);
}
static void bridge_merge_handler(void *data, struct stasis_subscription *sub,
struct stasis_topic *topic, struct stasis_message *message)
{
struct app *app = data;
struct ast_bridge_merge_message *merge;
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
if (stasis_subscription_final_message(sub, message)) {
ao2_cleanup(app);
}
if (stasis_message_type(message) != ast_bridge_merge_message_type()) {
return;
}
merge = stasis_message_data(message);
/* Find out if we're subscribed to either bridge */
forwards = ao2_find(app->forwards, merge->from->uniqueid,
OBJ_SEARCH_KEY);
if (!forwards) {
forwards = ao2_find(app->forwards, merge->to->uniqueid,
OBJ_SEARCH_KEY);
}
if (!forwards) {
return;
}
/* Forward the message to the app */
stasis_forward_message(app->topic, topic, message);
}
struct app *app_create(const char *name, stasis_app_cb handler, void *data)
{
RAII_VAR(struct app *, app, NULL, ao2_cleanup);
size_t size;
int res = 0;
ast_assert(name != NULL);
ast_assert(handler != NULL);
ast_verb(1, "Creating Stasis app '%s'\n", name);
size = sizeof(*app) + strlen(name) + 1;
app = ao2_alloc_options(size, app_dtor, AO2_ALLOC_OPT_LOCK_MUTEX);
if (!app) {
return NULL;
}
app->forwards = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
AO2_CONTAINER_ALLOC_OPT_DUPS_OBJ_REJECT,
forwards_sort, NULL);
if (!app->forwards) {
return NULL;
}
app->topic = stasis_topic_create(name);
if (!app->topic) {
return NULL;
}
app->bridge_merge_sub = stasis_subscribe(ast_bridge_topic_all(),
bridge_merge_handler, app);
if (!app->bridge_merge_sub) {
return NULL;
}
/* Subscription holds a reference */
ao2_ref(app, +1);
app->router = stasis_message_router_create(app->topic);
if (!app->router) {
return NULL;
}
res |= stasis_message_router_add_cache_update(app->router,
ast_bridge_snapshot_type(), sub_bridge_update_handler, app);
res |= stasis_message_router_add_cache_update(app->router,
ast_channel_snapshot_type(), sub_channel_update_handler, app);
res |= stasis_message_router_set_default(app->router,
sub_default_handler, app);
if (res != 0) {
return NULL;
}
/* Router holds a reference */
ao2_ref(app, +1);
strncpy(app->name, name, size - sizeof(*app));
app->handler = handler;
ao2_ref(data, +1);
app->data = data;
ao2_ref(app, +1);
return app;
}
/*!
* \brief Send a message to the given application.
* \param app App to send the message to.
* \param message Message to send.
*/
void app_send(struct app *app, struct ast_json *message)
{
stasis_app_cb handler;
RAII_VAR(void *, data, NULL, ao2_cleanup);
/* Copy off mutable state with lock held */
{
SCOPED_AO2LOCK(lock, app);
handler = app->handler;
if (app->data) {
ao2_ref(app->data, +1);
data = app->data;
}
/* Name is immutable; no need to copy */
}
if (!handler) {
ast_verb(3,
"Inactive Stasis app '%s' missed message\n", app->name);
return;
}
handler(data, app->name, message);
}
void app_deactivate(struct app *app)
{
SCOPED_AO2LOCK(lock, app);
ast_verb(1, "Deactivating Stasis app '%s'\n", app->name);
app->handler = NULL;
ao2_cleanup(app->data);
app->data = NULL;
}
void app_shutdown(struct app *app)
{
SCOPED_AO2LOCK(lock, app);
ast_assert(app_is_finished(app));
stasis_message_router_unsubscribe(app->router);
app->router = NULL;
stasis_unsubscribe(app->bridge_merge_sub);
app->bridge_merge_sub = NULL;
}
int app_is_active(struct app *app)
{
SCOPED_AO2LOCK(lock, app);
return app->handler != NULL;
}
int app_is_finished(struct app *app)
{
SCOPED_AO2LOCK(lock, app);
return app->handler == NULL && ao2_container_count(app->forwards) == 0;
}
void app_update(struct app *app, stasis_app_cb handler, void *data)
{
SCOPED_AO2LOCK(lock, app);
if (app->handler) {
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
ast_verb(1, "Replacing Stasis app '%s'\n", app->name);
msg = ast_json_pack("{s: s, s: s}",
"type", "ApplicationReplaced",
"application", app->name);
if (msg) {
app_send(app, msg);
}
} else {
ast_verb(1, "Activating Stasis app '%s'\n", app->name);
}
app->handler = handler;
ao2_cleanup(app->data);
if (data) {
ao2_ref(data, +1);
}
app->data = data;
}
const char *app_name(const struct app *app)
{
return app->name;
}
int app_subscribe_channel(struct app *app, struct ast_channel *chan)
{
int res;
if (!app || !chan) {
return -1;
} else {
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
SCOPED_AO2LOCK(lock, app->forwards);
forwards = ao2_find(app->forwards, ast_channel_uniqueid(chan),
OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!forwards) {
/* Forwards not found, create one */
forwards = forwards_create_channel(app, chan);
if (!forwards) {
return -1;
}
res = ao2_link_flags(app->forwards, forwards,
OBJ_NOLOCK);
if (!res) {
return -1;
}
}
++forwards->interested;
return 0;
}
}
static int unsubscribe(struct app *app, const char *kind, const char *id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
SCOPED_AO2LOCK(lock, app->forwards);
forwards = ao2_find(app->forwards, id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!forwards) {
ast_log(LOG_ERROR,
"App '%s' not subscribed to %s '%s'",
app->name, kind, id);
return -1;
}
if (--forwards->interested == 0) {
/* No one is interested any more; unsubscribe */
forwards_unsubscribe(forwards);
ao2_find(app->forwards, forwards,
OBJ_POINTER | OBJ_NOLOCK | OBJ_UNLINK |
OBJ_NODATA);
}
return 0;
}
int app_unsubscribe_channel(struct app *app, struct ast_channel *chan)
{
if (!app || !chan) {
return -1;
}
return unsubscribe(app, "channel", ast_channel_uniqueid(chan));
}
int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge)
{
if (!app || !bridge) {
return -1;
} else {
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
SCOPED_AO2LOCK(lock, app->forwards);
forwards = ao2_find(app->forwards, bridge->uniqueid,
OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!forwards) {
/* Forwards not found, create one */
forwards = forwards_create_bridge(app, bridge);
if (!forwards) {
return -1;
}
ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK);
}
++forwards->interested;
return 0;
}
}
int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge)
{
if (!app || !bridge) {
return -1;
}
return unsubscribe(app, "bridge", bridge->uniqueid);
}