names. This setting is configurable for cdr_adaptive_odbc via the
quoted_identifiers in configuration file cdr_adaptive_odbc.conf.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.5.0 to Asterisk 13.6.0 ------------
+------------------------------------------------------------------------------
+
+Dialplan Functions
+------------------
+ * The CHANNEL function, when used on a PJSIP channel, now exposes a 'call-id'
+ extraction option when using with the 'pjsip' signalling option. It will
+ return the SIP Call-ID associated with the INVITE request that established
+ the PJSIP channel.
+
+ARI
+------------------
+ * Two new endpoint related events are now available: PeerStatusChange and
+ ContactStatusChange. In particular, these events are useful when subscribing
+ to all event sources, as they provide additional endpoint related
+ information beyond the addition/removal of channels from an endpoint.
+
+ * Added the ability to subscribe to all ARI events in Asterisk, regardless
+ of whether the application 'controls' the resource. This is useful for
+ scenarios where an ARI application merely wants to observe the system,
+ as opposed to control it. There are two ways to accomplish this:
+ (1) Via the WebSocket connection URI. A new query paramter, 'subscribeAll',
+ has been added that, when present and True, will subscribe all
+ specified applications to all ARI event sources in Asterisk.
+ (2) Via the applications resource. An ARI client can, at any time, subscribe
+ to all resources in an event source merely by not providing an explicit
+ resource. For example, subscribing to an event source of 'channels:'
+ as opposed to 'channels:12345' will subscribe the application to all
+ channels.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.4.0 to Asterisk 13.5.0 ------------
------------------------------------------------------------------------------
}
if (cur->rtp) {
+ ast_rtp_instance_stop(cur->rtp);
ast_rtp_instance_destroy(cur->rtp);
cur->rtp = NULL;
}
i += pq931Msg->causeIE->length;
}
- /*Add progress indicator IE
- if(pq931Msg->messageType == Q931AlertingMsg || pq931Msg->messageType == Q931CallProceedingMsg)
+ /* Add progress indicator IE */
+ if(pq931Msg->messageType == Q931AlertingMsg || pq931Msg->messageType == Q931ProgressMsg)
{
msgbuf[i++] = Q931ProgressIndicatorIE;
msgbuf[i++] = 2; //Length is 2 octet
msgbuf[i++] = 0x80; //PI=8
msgbuf[i++] = 0x88;
- }*/
+ }
/*Add display ie. for all but Status message as per ASTERISK-18748 */
if(!ooUtilsIsStrEmpty(call->ourCallerId) && (pq931Msg->messageType != Q931StatusMsg))
chans[0] = chan;
chans[1] = peer;
- /* we need to stream the announcment while monitoring the caller for a hangup */
+ /* we need to stream the announcement while monitoring the caller for a hangup */
/* stream the file */
res = ast_streamfile(peer, opt_args[OPT_ARG_ANNOUNCE], ast_channel_language(peer));
if (ast_pbx_start(peer)) {
ast_autoservice_chan_hangup_peer(chan, peer);
}
- hanguptree(&out_chans, NULL, ast_test_flag64(&opts, OPT_CANCEL_ELSEWHERE) ? 1 : 0);
if (continue_exec)
*continue_exec = 1;
res = 0;
static int page_exec(struct ast_channel *chan, const char *data)
{
- char *tech, *resource, *tmp;
- char confbridgeopts[128], originator[AST_CHANNEL_NAME];
+ char *tech;
+ char *resource;
+ char *tmp;
+ char *predial_callee = NULL;
+ char confbridgeopts[128];
+ char originator[AST_CHANNEL_NAME];
struct page_options options = { { 0, }, { 0, } };
unsigned int confid = ast_random();
struct ast_app *app;
- int res = 0, pos = 0, i = 0;
+ int res = 0;
+ int pos = 0;
+ int i = 0;
struct ast_dial **dial_list;
unsigned int num_dials;
int timeout = 0;
return -1;
}
+ /* PREDIAL: Preprocess any callee gosub arguments. */
+ if (ast_test_flag(&options.flags, PAGE_PREDIAL_CALLEE)
+ && !ast_strlen_zero(options.opts[OPT_ARG_PREDIAL_CALLEE])) {
+ ast_replace_subargument_delimiter(options.opts[OPT_ARG_PREDIAL_CALLEE]);
+ predial_callee =
+ (char *) ast_app_expand_sub_args(chan, options.opts[OPT_ARG_PREDIAL_CALLEE]);
+ }
+
+ /* PREDIAL: Run gosub on the caller's channel */
if (ast_test_flag(&options.flags, PAGE_PREDIAL_CALLER)
&& !ast_strlen_zero(options.opts[OPT_ARG_PREDIAL_CALLER])) {
ast_replace_subargument_delimiter(options.opts[OPT_ARG_PREDIAL_CALLER]);
/* Set ANSWER_EXEC as global option */
ast_dial_option_global_enable(dial, AST_DIAL_OPTION_ANSWER_EXEC, confbridgeopts);
- if (ast_test_flag(&options.flags, PAGE_PREDIAL_CALLEE)
- && !ast_strlen_zero(options.opts[OPT_ARG_PREDIAL_CALLEE])) {
- ast_dial_option_global_enable(dial, AST_DIAL_OPTION_PREDIAL, options.opts[OPT_ARG_PREDIAL_CALLEE]);
+ if (predial_callee) {
+ ast_dial_option_global_enable(dial, AST_DIAL_OPTION_PREDIAL, predial_callee);
}
if (timeout) {
dial_list[pos++] = dial;
}
+ ast_free(predial_callee);
+
if (!ast_test_flag(&options.flags, PAGE_QUIET)) {
res = ast_streamfile(chan, "beep", ast_channel_language(chan));
if (!res)
struct local_optimization caller_optimize;
/*! Local channel optimization details for the member */
struct local_optimization member_optimize;
+ /*! Member channel */
+ struct ast_channel *member_channel;
+ /*! Caller channel */
+ struct ast_channel *caller_channel;
};
/*!
ao2_cleanup(queue_data->member);
queue_unref(queue_data->queue);
ast_string_field_free_memory(queue_data);
+
+ ao2_ref(queue_data->member_channel, -1);
+ ao2_ref(queue_data->caller_channel, -1);
}
/*!
queue_data->caller_pos = qe->opos;
ao2_ref(mem, +1);
queue_data->member = mem;
+
+ /*
+ * During transfers it's possible for both the member and/or caller
+ * channel(s) to not be available. Adding a reference here ensures
+ * that the channels remain until app_queue is completely done with
+ * them.
+ */
+ queue_data->member_channel = ao2_bump(peer);
+ queue_data->caller_channel = ao2_bump(qe->chan);
+
return queue_data;
}
}
chan = ast_channel_get_by_name(channel_blob->snapshot->name);
- if (chan && ast_channel_has_role(chan, AST_TRANSFERER_ROLE_NAME)) {
+ if (chan && (ast_channel_has_role(chan, AST_TRANSFERER_ROLE_NAME) ||
+ !ast_strlen_zero(pbx_builtin_getvar_helper(chan, "ATTENDEDTRANSFER")) ||
+ !ast_strlen_zero(pbx_builtin_getvar_helper(chan, "BLINDTRANSFER")))) {
/* Channel that is hanging up is doing it as part of a transfer.
* We'll get a transfer event later
*/
}
count++;
} while (ast_fileexists(tmp, ext, ast_channel_language(chan)) > 0);
- pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
} else
ast_copy_string(tmp, args.filename, sizeof(tmp));
+
+ pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
/* end of routine mentioned */
if (ast_channel_state(chan) != AST_STATE_UP) {
return stmt;
}
-#define LENGTHEN_BUF1(size) \
+#define LENGTHEN_BUF(size, var_sql) \
do { \
/* Lengthen buffer, if necessary */ \
- if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
- if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 1) / 512 + 1) * 512) != 0) { \
+ if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
+ if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
ast_free(sql); \
ast_free(sql2); \
} \
} while (0)
-#define LENGTHEN_BUF2(size) \
- do { \
- if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) { \
- if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) { \
- ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CDR '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
- ast_free(sql); \
- ast_free(sql2); \
- AST_RWLIST_UNLOCK(&odbc_tables); \
- return -1; \
- } \
- } \
- } while (0)
+#define LENGTHEN_BUF1(size) \
+ LENGTHEN_BUF(size, sql);
+#define LENGTHEN_BUF2(size) \
+ LENGTHEN_BUF(size, sql2);
static int odbc_log(struct ast_cdr *cdr)
{
return stmt;
}
-#define LENGTHEN_BUF1(size) \
+#define LENGTHEN_BUF(size, var_sql) \
do { \
/* Lengthen buffer, if necessary */ \
- if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
- if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 1) / 512 + 1) * 512) != 0) { \
+ if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
+ if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 1) / 512 + 1) * 512) != 0) { \
ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
ast_free(sql); \
ast_free(sql2); \
} \
} while (0)
-#define LENGTHEN_BUF2(size) \
- do { \
- if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) { \
- if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) { \
- ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", tableptr->connection, tableptr->table); \
- ast_free(sql); \
- ast_free(sql2); \
- AST_RWLIST_UNLOCK(&odbc_tables); \
- return; \
- } \
- } \
- } while (0)
+#define LENGTHEN_BUF1(size) \
+ LENGTHEN_BUF(size, sql);
+
+#define LENGTHEN_BUF2(size) \
+ LENGTHEN_BUF(size, sql2);
static void odbc_log(struct ast_event *event)
{
static AST_RWLIST_HEAD_STATIC(psql_columns, columns);
-#define LENGTHEN_BUF1(size) \
+#define LENGTHEN_BUF(size, var_sql) \
do { \
/* Lengthen buffer, if necessary */ \
- if (ast_str_strlen(sql) + size + 1 > ast_str_size(sql)) { \
- if (ast_str_make_space(&sql, ((ast_str_size(sql) + size + 3) / 512 + 1) * 512) != 0) { \
+ if (ast_str_strlen(var_sql) + size + 1 > ast_str_size(var_sql)) { \
+ if (ast_str_make_space(&var_sql, ((ast_str_size(var_sql) + size + 3) / 512 + 1) * 512) != 0) { \
ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", pghostname, table); \
ast_free(sql); \
ast_free(sql2); \
} \
} while (0)
+#define LENGTHEN_BUF1(size) \
+ LENGTHEN_BUF(size, sql);
#define LENGTHEN_BUF2(size) \
- do { \
- if (ast_str_strlen(sql2) + size + 1 > ast_str_size(sql2)) { \
- if (ast_str_make_space(&sql2, ((ast_str_size(sql2) + size + 3) / 512 + 1) * 512) != 0) { \
- ast_log(LOG_ERROR, "Unable to allocate sufficient memory. Insert CEL '%s:%s' failed.\n", pghostname, table); \
- ast_free(sql); \
- ast_free(sql2); \
- AST_RWLIST_UNLOCK(&psql_columns); \
- return; \
- } \
- } \
- } while (0)
+ LENGTHEN_BUF(size, sql2);
static void pgsql_reconnect(void)
{
static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
+ struct chan_pjsip_pvt *pvt;
struct ast_sip_endpoint *endpoint;
- if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_AUDIO]->rtp) {
+ if (!channel || !channel->session || !(pvt = channel->pvt) || !pvt->media[SIP_MEDIA_AUDIO]->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
int cid_has_name = 1;
int cid_has_num = 1;
struct ast_party_id connected_id;
+ int ret;
if (ast_test_flag(&p->flags[0], SIP_USEREQPHONE)) {
const char *s = p->username; /* being a string field, cannot be NULL */
ast_copy_string(tmp_l, l, sizeof(tmp_l));
if (sip_cfg.pedanticsipchecking) {
- ast_escape_quoted(n, tmp_n, sizeof(tmp_n));
- n = tmp_n;
ast_uri_encode(l, tmp_l, sizeof(tmp_l), ast_uri_sip_user);
}
ourport = (p->fromdomainport && (p->fromdomainport != STANDARD_SIP_PORT)) ? p->fromdomainport : ast_sockaddr_port(&p->ourip);
- /* If a caller id name was specified, add a display name. */
- if (cid_has_name || !cid_has_num) {
- snprintf(from, sizeof(from), "\"%s\" ", n);
+ if (!sip_standard_port(p->socket.type, ourport)) {
+ ret = snprintf(from, sizeof(from), "<sip:%s@%s:%d>;tag=%s", tmp_l, d, ourport, p->tag);
} else {
- from[0] = '\0';
+ ret = snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", tmp_l, d, p->tag);
+ }
+ if (ret < 0 || ret >= sizeof(from)) { /* a return value of size or more means that the output was truncated */
+ /* We don't have an escape path from here... */
+ ast_log(LOG_ERROR, "The From header was truncated in call '%s'. This call setup will fail.\n", p->callid);
}
- if (!sip_standard_port(p->socket.type, ourport)) {
- size_t offset = strlen(from);
- snprintf(&from[offset], sizeof(from) - offset, "<sip:%s@%s:%d>;tag=%s", tmp_l, d, ourport, p->tag);
- } else {
- size_t offset = strlen(from);
- snprintf(&from[offset], sizeof(from) - offset, "<sip:%s@%s>;tag=%s", tmp_l, d, p->tag);
+ /* If a caller id name was specified, prefix a display name, if there is enough room. */
+ if (cid_has_name || !cid_has_num) {
+ size_t written = strlen(from);
+ ssize_t left = sizeof(from) - written - 4; /* '"" \0' */
+ if (left > 0) {
+ size_t name_len;
+ if (sip_cfg.pedanticsipchecking) {
+ ast_escape_quoted(n, tmp_n, MIN(left + 1, sizeof(tmp_n)));
+ n = tmp_n;
+ }
+ name_len = strlen(n);
+ if (left < name_len) {
+ name_len = left;
+ }
+ memmove(from + name_len + 3, from, written + 1);
+ from[0] = '"';
+ memcpy(from + 1, n, name_len);
+ from[name_len + 1] = '"';
+ from[name_len + 2] = ' ';
+ }
}
if (!ast_strlen_zero(explicit_uri)) {
/*! \todo Need to add back the VXML URL here at some point, possibly use build_string for all this junk */
if (!strchr(p->todnid, '@')) {
/* We have no domain in the dnid */
- snprintf(to, sizeof(to), "<sip:%s@%s>%s%s", p->todnid, p->tohost, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag);
+ ret = snprintf(to, sizeof(to), "<sip:%s@%s>%s%s", p->todnid, p->tohost, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag);
} else {
- snprintf(to, sizeof(to), "<sip:%s>%s%s", p->todnid, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag);
+ ret = snprintf(to, sizeof(to), "<sip:%s>%s%s", p->todnid, ast_strlen_zero(p->theirtag) ? "" : ";tag=", p->theirtag);
}
} else {
if (sipmethod == SIP_NOTIFY && !ast_strlen_zero(p->theirtag)) {
/* If this is a NOTIFY, use the From: tag in the subscribe (RFC 3265) */
- snprintf(to, sizeof(to), "<%s%s>;tag=%s", (strncasecmp(p->uri, "sip:", 4) ? "sip:" : ""), p->uri, p->theirtag);
+ ret = snprintf(to, sizeof(to), "<%s%s>;tag=%s", (strncasecmp(p->uri, "sip:", 4) ? "sip:" : ""), p->uri, p->theirtag);
} else if (p->options && p->options->vxml_url) {
/* If there is a VXML URL append it to the SIP URL */
- snprintf(to, sizeof(to), "<%s>;%s", p->uri, p->options->vxml_url);
+ ret = snprintf(to, sizeof(to), "<%s>;%s", p->uri, p->options->vxml_url);
} else {
- snprintf(to, sizeof(to), "<%s>", p->uri);
+ ret = snprintf(to, sizeof(to), "<%s>", p->uri);
}
}
+ if (ret < 0 || ret >= sizeof(to)) { /* a return value of size or more means that the output was truncated */
+ /* We don't have an escape path from here... */
+ ast_log(LOG_ERROR, "The To header was truncated in call '%s'. This call setup will fail.\n", p->callid);
+ }
init_req(req, sipmethod, p->uri);
/* now tmp_n is available so reuse it to build the CSeq */
/*! \brief Part of Asterisk module interface */
static int reload(void)
{
- if (sip_reload(0, 0, NULL)) {
- return 0;
- }
- return 1;
+ sip_reload(0, 0, NULL);
+ return AST_MODULE_LOAD_SUCCESS;
}
/*! \brief Return the first entry from ast_sockaddr_resolve filtered by address family
<literal>type</literal> parameter must be provided. It specifies
which signalling parameter to read.</para>
<enumlist>
+ <enum name="call-id">
+ <para>The SIP call-id.</para>
+ </enum>
<enum name="secure">
<para>Whether or not the signalling uses a secure transport.</para>
<enumlist>
if (ast_strlen_zero(type)) {
ast_log(LOG_WARNING, "You must supply a type field for 'pjsip' information\n");
return -1;
+ } else if (!strcmp(type, "call-id")) {
+ snprintf(buf, buflen, "%.*s", (int) pj_strlen(&dlg->call_id->id), pj_strbuf(&dlg->call_id->id));
} else if (!strcmp(type, "secure")) {
#ifdef HAVE_PJSIP_GET_DEST_INFO
pjsip_host_info dest;
--- /dev/null
+"""add default_from_user
+
+Revision ID: 154177371065
+Revises: 26f10cadc157
+Create Date: 2015-09-04 14:13:59.195013
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '154177371065'
+down_revision = '26f10cadc157'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_globals', sa.Column('default_from_user', sa.String(80)))
+
+
+def downgrade():
+ op.drop_column('ps_globals', 'default_from_user')
*/
char *ast_sip_get_endpoint_identifier_order(void);
+/*!
+ * \brief Retrieve the global default from user.
+ *
+ * This is the value placed in outbound requests' From header if there
+ * is no better option (such as an endpoint-configured from_user or
+ * caller ID number).
+ *
+ * \param[out] from_user The default from user
+ * \param size The buffer size of from_user
+ * \return nothing
+ */
+void ast_sip_get_default_from_user(char *from_user, size_t size);
+
/*! \brief Determines whether the res_pjsip module is loaded */
#define CHECK_PJSIP_MODULE_LOADED() \
do { \
int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data);
/*!
+ * \brief Register a new Stasis application that receives all Asterisk events.
+ *
+ * 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_all(const char *app_name, stasis_app_cb handler, void *data);
+
+/*!
* \brief Unregister a Stasis application.
* \param app_name Name of the application to unregister.
*/
#include "asterisk/lock.h"
#include "asterisk/strings.h"
#include "asterisk/unaligned.h"
+#include "asterisk/localtime.h"
+#include "asterisk/time.h"
static struct fdleaks {
const char *callname;
char file[40];
char function[25];
char callargs[60];
+ struct timeval now;
} fdleaks[1024] = { { "", }, };
/* COPY does ast_copy_string(dst, src, sizeof(dst)), except:
#define STORE_COMMON(offset, name, ...) \
do { \
struct fdleaks *tmp = &fdleaks[offset]; \
+ tmp->now = ast_tvnow(); \
COPY(tmp->file, file); \
tmp->line = line; \
COPY(tmp->function, func); \
case CLI_GENERATE:
return NULL;
}
- getrlimit(RLIMIT_FSIZE, &rl);
+ getrlimit(RLIMIT_NOFILE, &rl);
if (rl.rlim_cur == RLIM_INFINITY || rl.rlim_max == RLIM_INFINITY) {
ast_copy_string(line, "unlimited", sizeof(line));
} else {
ast_cli(a->fd, "Current maxfiles: %s\n", line);
for (i = 0; i < ARRAY_LEN(fdleaks); i++) {
if (fdleaks[i].isopen) {
+ struct ast_tm tm = {0};
+ char datestring[256];
+
+ ast_localtime(&fdleaks[i].now, &tm, NULL);
+ ast_strftime(datestring, sizeof(datestring), "%F %T", &tm);
snprintf(line, sizeof(line), "%d", fdleaks[i].line);
- ast_cli(a->fd, "%5d %15s:%-7.7s (%-25s): %s(%s)\n", i, fdleaks[i].file, line, fdleaks[i].function, fdleaks[i].callname, fdleaks[i].callargs);
+ ast_cli(a->fd, "%5d [%s] %22s:%-7.7s (%-25s): %s(%s)\n", i, datestring, fdleaks[i].file, line, fdleaks[i].function, fdleaks[i].callname, fdleaks[i].callargs);
}
}
return CLI_SUCCESS;
/* Don't bother if the channel is about to go away, anyway. */
if ((ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
- || ast_check_hangup(chan))
+ || (ast_check_hangup(chan) && !ast_channel_is_leaving_bridge(chan)))
&& condition != AST_CONTROL_MASQUERADE_NOTIFY) {
res = -1;
goto indicate_cleanup;
struct aco_option *opt;
struct ao2_iterator iter;
+ if (!type->internal) {
+ return -1;
+ }
+
iter = ao2_iterator_init(type->internal->opts, 0);
while ((opt = ao2_iterator_next(&iter))) {
ast_channel_unlock(channel->owner);
if (!ast_strlen_zero(predial_string)) {
- const char *predial_callee = ast_app_expand_sub_args(chan, predial_string);
- if (!predial_callee) {
- ast_log(LOG_ERROR, "Could not expand subroutine arguments in predial request '%s'\n", predial_string);
+ if (chan) {
+ ast_autoservice_start(chan);
+ }
+ ast_pre_call(channel->owner, predial_string);
+ if (chan) {
+ ast_autoservice_stop(chan);
}
- ast_autoservice_start(chan);
- ast_pre_call(channel->owner, predial_callee);
- ast_autoservice_stop(chan);
- ast_free((char *) predial_callee);
}
return 0;
int res = -1;
char *predial_string = dial->options[AST_DIAL_OPTION_PREDIAL];
- if (!ast_strlen_zero(predial_string)) {
- ast_replace_subargument_delimiter(predial_string);
- }
-
AST_LIST_LOCK(&dial->channels);
AST_LIST_TRAVERSE(&dial->channels, channel, list) {
if ((res = begin_dial_prerun(channel, chan, cap, predial_string))) {
int success = 0;
char *predial_string = dial->options[AST_DIAL_OPTION_PREDIAL];
- if (!ast_strlen_zero(predial_string)) {
- ast_replace_subargument_delimiter(predial_string);
- }
-
/* Iterate through channel list, requesting and calling each one */
AST_LIST_LOCK(&dial->channels);
AST_LIST_TRAVERSE(&dial->channels, channel, list) {
char *tech = "Local", *device = tmp, *stuff;
char *predial_string = dial->options[AST_DIAL_OPTION_PREDIAL];
- if (!ast_strlen_zero(predial_string)) {
- ast_replace_subargument_delimiter(predial_string);
- }
-
/* If call forwarding is disabled just drop the original channel and don't attempt to dial the new one */
if (FIND_RELATIVE_OPTION(dial, channel, AST_DIAL_OPTION_DISABLE_CALL_FORWARDING)) {
ast_hangup(original);
struct ast_endpoint_snapshot *ast_endpoint_snapshot_create(
struct ast_endpoint *endpoint)
{
- RAII_VAR(struct ast_endpoint_snapshot *, snapshot, NULL, ao2_cleanup);
+ struct ast_endpoint_snapshot *snapshot;
int channel_count;
struct ao2_iterator i;
void *obj;
}
ao2_iterator_destroy(&i);
- ao2_ref(snapshot, +1);
return snapshot;
}
chan->logmask = logmask;
}
+/*!
+ * \brief create the filename that will be used for a logger channel.
+ *
+ * \param channel The name of the logger channel
+ * \param[out] filename The filename for the logger channel
+ * \param size The size of the filename buffer
+ */
+static void make_filename(const char *channel, char *filename, size_t size)
+{
+ const char *log_dir_prefix = "";
+ const char *log_dir_separator = "";
+
+ *filename = '\0';
+
+ if (!strcasecmp(channel, "console")) {
+ return;
+ }
+
+ if (!strncasecmp(channel, "syslog", 6)) {
+ ast_copy_string(filename, channel, size);
+ return;
+ }
+
+ /* It's a filename */
+
+ if (channel[0] != '/') {
+ log_dir_prefix = ast_config_AST_LOG_DIR;
+ log_dir_separator = "/";
+ }
+
+ if (!ast_strlen_zero(hostname)) {
+ snprintf(filename, size, "%s%s%s.%s",
+ log_dir_prefix, log_dir_separator, channel, hostname);
+ } else {
+ snprintf(filename, size, "%s%s%s",
+ log_dir_prefix, log_dir_separator, channel);
+ }
+}
+
+/*!
+ * \brief Find a particular logger channel by name
+ *
+ * \pre logchannels list is locked
+ *
+ * \param channel The name of the logger channel to find
+ * \retval non-NULL The corresponding logger channel
+ * \retval NULL Unable to find a logger channel with that particular name
+ */
+static struct logchannel *find_logchannel(const char *channel)
+{
+ char filename[PATH_MAX];
+ struct logchannel *chan;
+
+ make_filename(channel, filename, sizeof(filename));
+
+ AST_RWLIST_TRAVERSE(&logchannels, chan, list) {
+ if (!strcmp(chan->filename, filename)) {
+ return chan;
+ }
+ }
+
+ return NULL;
+}
+
static struct logchannel *make_logchannel(const char *channel, const char *components, int lineno, int dynamic)
{
struct logchannel *chan;
chan->lineno = lineno;
chan->dynamic = dynamic;
+ make_filename(channel, chan->filename, sizeof(chan->filename));
+
if (!strcasecmp(channel, "console")) {
chan->type = LOGTYPE_CONSOLE;
} else if (!strncasecmp(channel, "syslog", 6)) {
}
chan->type = LOGTYPE_SYSLOG;
- ast_copy_string(chan->filename, channel, sizeof(chan->filename));
- openlog("asterisk", LOG_PID, chan->facility);
} else {
- const char *log_dir_prefix = "";
- const char *log_dir_separator = "";
-
- if (channel[0] != '/') {
- log_dir_prefix = ast_config_AST_LOG_DIR;
- log_dir_separator = "/";
- }
-
- if (!ast_strlen_zero(hostname)) {
- snprintf(chan->filename, sizeof(chan->filename), "%s%s%s.%s",
- log_dir_prefix, log_dir_separator, channel, hostname);
- } else {
- snprintf(chan->filename, sizeof(chan->filename), "%s%s%s",
- log_dir_prefix, log_dir_separator, channel);
- }
-
if (!(chan->fileptr = fopen(chan->filename, "a"))) {
/* Can't do real logging here since we're called with a lock
* so log to any attached consoles */
{
struct logchannel *f;
int success = AST_LOGGER_FAILURE;
+ char filename[PATH_MAX];
- struct ast_str *filename = ast_str_create(64);
- if (!filename) {
- return AST_LOGGER_ALLOC_ERROR;
- }
-
- ast_str_append(&filename, 0, "%s/%s", ast_config_AST_LOG_DIR, log_channel);
+ make_filename(log_channel, filename, sizeof(filename));
AST_RWLIST_WRLOCK(&logchannels);
if (f->fileptr && (f->fileptr != stdout) && (f->fileptr != stderr)) {
fclose(f->fileptr); /* Close file */
f->fileptr = NULL;
- if (strcmp(ast_str_buffer(filename), f->filename) == 0) {
+ if (strcmp(filename, f->filename) == 0) {
rotate_file(f->filename);
success = AST_LOGGER_SUCCESS;
}
init_logger_chain(1 /* locked */, NULL);
AST_RWLIST_UNLOCK(&logchannels);
- ast_free(filename);
return success;
}
int ast_logger_create_channel(const char *log_channel, const char *components)
{
struct logchannel *chan;
- struct ast_str *filename = ast_str_create(64);
- int chan_exists = AST_LOGGER_SUCCESS;
if (ast_strlen_zero(components)) {
return AST_LOGGER_DECLINE;
}
- if (!filename) {
- return AST_LOGGER_ALLOC_ERROR;
- }
-
- ast_str_append(&filename, 0, "%s/%s", ast_config_AST_LOG_DIR, log_channel);
-
AST_RWLIST_WRLOCK(&logchannels);
- AST_RWLIST_TRAVERSE(&logchannels, chan, list) {
- if (!strcmp(ast_str_buffer(filename), chan->filename)) {
- chan_exists = AST_LOGGER_FAILURE;
- break;
- }
+ chan = find_logchannel(log_channel);
+ if (chan) {
+ AST_RWLIST_UNLOCK(&logchannels);
+ return AST_LOGGER_FAILURE;
}
- if (!chan_exists) {
- chan = make_logchannel(log_channel, components, 0, 1);
- if (chan) {
- AST_RWLIST_INSERT_HEAD(&logchannels, chan, list);
- global_logmask |= chan->logmask;
- chan_exists = AST_LOGGER_SUCCESS;
- }
+ chan = make_logchannel(log_channel, components, 0, 1);
+ if (!chan) {
+ AST_RWLIST_UNLOCK(&logchannels);
+ return AST_LOGGER_ALLOC_ERROR;
}
+
+ AST_RWLIST_INSERT_HEAD(&logchannels, chan, list);
+ global_logmask |= chan->logmask;
+
AST_RWLIST_UNLOCK(&logchannels);
- return chan_exists;
+ return AST_LOGGER_SUCCESS;
}
static char *handle_logger_add_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- struct logchannel *chan;
-
switch (cmd) {
case CLI_INIT:
e->command = "logger add channel";
return CLI_SHOWUSAGE;
}
- AST_RWLIST_WRLOCK(&logchannels);
- AST_RWLIST_TRAVERSE(&logchannels, chan, list) {
- if (!strcmp(chan->filename, a->argv[3])) {
- break;
- }
- }
-
- if (chan) {
- AST_RWLIST_UNLOCK(&logchannels);
- ast_cli(a->fd, "Logger channel '%s' already exists\n", a->argv[3]);
+ switch (ast_logger_create_channel(a->argv[3], a->argv[4])) {
+ case AST_LOGGER_SUCCESS:
return CLI_SUCCESS;
- }
-
- chan = make_logchannel(a->argv[3], a->argv[4], 0, 1);
- if (chan) {
- AST_RWLIST_INSERT_HEAD(&logchannels, chan, list);
- global_logmask |= chan->logmask;
- AST_RWLIST_UNLOCK(&logchannels);
+ case AST_LOGGER_FAILURE:
+ ast_cli(a->fd, "Logger channel '%s' already exists\n", a->argv[3]);
return CLI_SUCCESS;
+ case AST_LOGGER_DECLINE:
+ case AST_LOGGER_ALLOC_ERROR:
+ default:
+ ast_cli(a->fd, "ERROR: Unable to create log channel '%s'\n", a->argv[3]);
+ return CLI_FAILURE;
}
-
- AST_RWLIST_UNLOCK(&logchannels);
- ast_cli(a->fd, "ERROR: Unable to create log channel '%s'\n", a->argv[3]);
-
- return CLI_FAILURE;
}
int ast_logger_remove_channel(const char *log_channel)
{
struct logchannel *chan;
- struct ast_str *filename = ast_str_create(64);
-
- if (!filename) {
- return AST_LOGGER_ALLOC_ERROR;
- }
-
- ast_str_append(&filename, 0, "%s/%s", ast_config_AST_LOG_DIR, log_channel);
AST_RWLIST_WRLOCK(&logchannels);
- AST_RWLIST_TRAVERSE_SAFE_BEGIN(&logchannels, chan, list) {
- if (chan->dynamic && !strcmp(chan->filename, ast_str_buffer(filename))) {
- AST_RWLIST_REMOVE_CURRENT(list);
- break;
- }
- }
- AST_RWLIST_TRAVERSE_SAFE_END;
- AST_RWLIST_UNLOCK(&logchannels);
- if (!chan) {
+ chan = find_logchannel(log_channel);
+ if (chan && chan->dynamic) {
+ AST_RWLIST_REMOVE(&logchannels, chan, list);
+ } else {
+ AST_RWLIST_UNLOCK(&logchannels);
return AST_LOGGER_FAILURE;
}
+ AST_RWLIST_UNLOCK(&logchannels);
if (chan->fileptr) {
fclose(chan->fileptr);
return CLI_SHOWUSAGE;
}
- AST_RWLIST_WRLOCK(&logchannels);
- AST_RWLIST_TRAVERSE_SAFE_BEGIN(&logchannels, chan, list) {
- if (chan->dynamic && !strcmp(chan->filename, a->argv[3])) {
- AST_RWLIST_REMOVE_CURRENT(list);
- break;
- }
- }
- AST_RWLIST_TRAVERSE_SAFE_END;
- AST_RWLIST_UNLOCK(&logchannels);
-
- if (!chan) {
+ switch (ast_logger_remove_channel(a->argv[3])) {
+ case AST_LOGGER_SUCCESS:
+ ast_cli(a->fd, "Removed dynamic logger channel '%s'\n", a->argv[3]);
+ return CLI_SUCCESS;
+ case AST_LOGGER_FAILURE:
ast_cli(a->fd, "Unable to find dynamic logger channel '%s'\n", a->argv[3]);
return CLI_SUCCESS;
+ default:
+ ast_cli(a->fd, "Internal failure attempting to delete dynamic logger channel '%s'\n", a->argv[3]);
+ return CLI_FAILURE;
}
-
- ast_cli(a->fd, "Removed dynamic logger channel '%s'\n", chan->filename);
- if (chan->fileptr) {
- fclose(chan->fileptr);
- chan->fileptr = NULL;
- }
- ast_free(chan);
- chan = NULL;
-
- return CLI_SUCCESS;
}
struct verb {
.sa_flags = SA_RESTART,
};
-static void ast_log_vsyslog(struct logmsg *msg)
+static void ast_log_vsyslog(struct logmsg *msg, int facility)
{
char buf[BUFSIZ];
int syslog_level = ast_syslog_priority_from_loglevel(msg->level);
return;
}
+ syslog_level = LOG_MAKEPRI(facility, syslog_level);
+
snprintf(buf, sizeof(buf), "%s[%d]%s: %s:%d in %s: %s",
levels[msg->level], msg->lwp, call_identifier_str, msg->file, msg->line, msg->function, msg->message);
/* Check syslog channels */
if (chan->type == LOGTYPE_SYSLOG && (chan->logmask & (1 << logmsg->level))) {
- ast_log_vsyslog(logmsg);
+ ast_log_vsyslog(logmsg, chan->facility);
/* Console channels */
} else if (chan->type == LOGTYPE_CONSOLE && (chan->logmask & (1 << logmsg->level))) {
char linestr[128];
/*! \brief Change hint for an extension */
static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
{
+ struct ast_str *hint_app;
struct ast_hint *hint;
+ int previous_device_state;
+ char *previous_message = NULL;
+ char *message = NULL;
+ char *previous_subtype = NULL;
+ char *subtype = NULL;
+ int previous_presence_state;
+ int presence_state;
+ int presence_state_changed = 0;
if (!oe || !ne) {
return -1;
}
+ hint_app = ast_str_create(1024);
+ if (!hint_app) {
+ return -1;
+ }
+
+ ast_mutex_lock(&context_merge_lock); /* Hold off ast_merge_contexts_and_delete and state changes */
+
ao2_lock(hints);/* Locked to hold off others while we move the hint around. */
/*
hint = ao2_find(hints, oe, OBJ_UNLINK);
if (!hint) {
ao2_unlock(hints);
+ ast_mutex_unlock(&context_merge_lock);
+ ast_free(hint_app);
return -1;
}
/* Update the hint and put it back in the hints container. */
ao2_lock(hint);
hint->exten = ne;
+
+ /* Store the previous states so we know whether we need to notify state callbacks */
+ previous_device_state = hint->laststate;
+ previous_presence_state = hint->last_presence_state;
+ previous_message = hint->last_presence_message;
+ previous_subtype = hint->last_presence_subtype;
+
+ /* Update the saved device and presence state with the new extension */
+ hint->laststate = ast_extension_state2(ne, NULL);
+ hint->last_presence_state = AST_PRESENCE_INVALID;
+ hint->last_presence_subtype = NULL;
+ hint->last_presence_message = NULL;
+
+ presence_state = extension_presence_state_helper(ne, &subtype, &message);
+ if (presence_state > 0) {
+ hint->last_presence_state = presence_state;
+ hint->last_presence_subtype = subtype;
+ hint->last_presence_message = message;
+ }
+
ao2_unlock(hint);
+
ao2_link(hints, hint);
if (add_hintdevice(hint, ast_get_extension_app(ne))) {
ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n",
}
ao2_unlock(hints);
+
+ /* Locking for state callbacks is respected here and only the context_merge_lock lock is
+ * held during the state callback invocation. This will stop the normal state callback
+ * thread from being able to handle incoming state changes if they occur.
+ */
+
+ /* Determine if presence state has changed due to the change of the hint extension */
+ if ((hint->last_presence_state != previous_presence_state) ||
+ strcmp(S_OR(hint->last_presence_subtype, ""), S_OR(previous_subtype, "")) ||
+ strcmp(S_OR(hint->last_presence_message, ""), S_OR(previous_message, ""))) {
+ presence_state_changed = 1;
+ }
+
+ /* Notify any existing state callbacks if the device or presence state has changed */
+ if ((hint->laststate != previous_device_state) || presence_state_changed) {
+ struct ao2_iterator cb_iter;
+ struct ast_state_cb *state_cb;
+ struct ao2_container *device_state_info;
+ int first_extended_cb_call = 1;
+
+ /* For general callbacks */
+ cb_iter = ao2_iterator_init(statecbs, 0);
+ for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+ /* Unlike the normal state callbacks since something has explicitly provided us this extension
+ * it will remain valid and unchanged for the lifetime of this function invocation.
+ */
+ if (hint->laststate != previous_device_state) {
+ execute_state_callback(state_cb->change_cb,
+ ast_get_context_name(ast_get_extension_context(ne)),
+ ast_get_extension_name(ne),
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint,
+ NULL);
+ }
+ if (presence_state_changed) {
+ execute_state_callback(state_cb->change_cb,
+ ast_get_context_name(ast_get_extension_context(ne)),
+ ast_get_extension_name(ne),
+ state_cb->data,
+ AST_HINT_UPDATE_PRESENCE,
+ hint,
+ NULL);
+ }
+ }
+ ao2_iterator_destroy(&cb_iter);
+
+ ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(ne));
+
+ device_state_info = alloc_device_state_info();
+ ast_extension_state3(hint_app, device_state_info);
+
+ /* For extension callbacks */
+ cb_iter = ao2_iterator_init(hint->callbacks, 0);
+ for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+ if (hint->laststate != previous_device_state) {
+ if (state_cb->extended && first_extended_cb_call) {
+ /* Fill detailed device_state_info now that we know it is used by extd. callback */
+ first_extended_cb_call = 0;
+ get_device_state_causing_channels(device_state_info);
+ }
+ execute_state_callback(state_cb->change_cb,
+ ast_get_context_name(ast_get_extension_context(ne)),
+ ast_get_extension_name(ne),
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint,
+ state_cb->extended ? device_state_info : NULL);
+ }
+ if (presence_state_changed) {
+ execute_state_callback(state_cb->change_cb,
+ ast_get_context_name(ast_get_extension_context(ne)),
+ ast_get_extension_name(ne),
+ state_cb->data,
+ AST_HINT_UPDATE_PRESENCE,
+ hint,
+ NULL);
+ }
+ }
+ ao2_iterator_destroy(&cb_iter);
+
+ ao2_cleanup(device_state_info);
+ }
+
ao2_ref(hint, -1);
+ ast_mutex_unlock(&context_merge_lock);
+
+ ast_free(hint_app);
+ ast_free(previous_message);
+ ast_free(previous_subtype);
+
return 0;
}
AST_THREADSTORAGE(last_del_id);
+/*!
+ * \brief Scheduler ID holder
+ *
+ * These form a queue on a scheduler context. When a new
+ * scheduled item is created, a sched_id is popped off the
+ * queue and its id is assigned to the new scheduled item.
+ * When the scheduled task is complete, the sched_id on that
+ * task is then pushed to the back of the queue to be re-used
+ * on some future scheduled item.
+ */
+struct sched_id {
+ /*! Immutable ID number that is copied onto the scheduled task */
+ int id;
+ AST_LIST_ENTRY(sched_id) list;
+};
+
struct sched {
AST_LIST_ENTRY(sched) list;
- int id; /*!< ID number of event */
+ /*! The ID that has been popped off the scheduler context's queue */
+ struct sched_id *sched_id;
struct timeval when; /*!< Absolute time event should take place */
int resched; /*!< When to reschedule */
int variable; /*!< Use return value from callback to reschedule */
AST_LIST_HEAD_NOLOCK(, sched) schedc; /*!< Cache of unused schedule structures and how many */
unsigned int schedccnt;
#endif
+ /*! Queue of scheduler task IDs to assign */
+ AST_LIST_HEAD_NOLOCK(, sched_id) id_queue;
+ /*! The number of IDs in the id_queue */
+ int id_queue_size;
};
static void *sched_run(void *data)
ast_mutex_init(&tmp->lock);
tmp->eventcnt = 1;
+ AST_LIST_HEAD_INIT_NOLOCK(&tmp->id_queue);
+
if (!(tmp->sched_heap = ast_heap_create(8, sched_time_cmp,
offsetof(struct sched, __heap_index)))) {
ast_sched_context_destroy(tmp);
static void sched_free(struct sched *task)
{
+ /* task->sched_id will be NULL most of the time, but when the
+ * scheduler context shuts down, it will free all scheduled
+ * tasks, and in that case, the task->sched_id will be non-NULL
+ */
+ ast_free(task->sched_id);
ast_cond_destroy(&task->cond);
ast_free(task);
}
void ast_sched_context_destroy(struct ast_sched_context *con)
{
struct sched *s;
+ struct sched_id *sid;
sched_thread_destroy(con);
con->sched_thread = NULL;
con->sched_heap = NULL;
}
+ while ((sid = AST_LIST_REMOVE_HEAD(&con->id_queue, list))) {
+ ast_free(sid);
+ }
+
ast_mutex_unlock(&con->lock);
ast_mutex_destroy(&con->lock);
ast_free(con);
}
-static struct sched *sched_alloc(struct ast_sched_context *con)
-{
- struct sched *tmp;
+#define ID_QUEUE_INCREMENT 16
- /*
- * We keep a small cache of schedule entries
- * to minimize the number of necessary malloc()'s
+/*!
+ * \brief Add new scheduler IDs to the queue.
+ *
+ * \retval The number of IDs added to the queue
+ */
+static int add_ids(struct ast_sched_context *con)
+{
+ int new_size;
+ int original_size;
+ int i;
+
+ original_size = con->id_queue_size;
+ /* So we don't go overboard with the mallocs here, we'll just up
+ * the size of the list by a fixed amount each time instead of
+ * multiplying the size by any particular factor
*/
-#ifdef SCHED_MAX_CACHE
- if ((tmp = AST_LIST_REMOVE_HEAD(&con->schedc, list))) {
- con->schedccnt--;
- } else
-#endif
- {
- tmp = ast_calloc(1, sizeof(*tmp));
- ast_cond_init(&tmp->cond, NULL);
+ new_size = original_size + ID_QUEUE_INCREMENT;
+ if (new_size < 0) {
+ /* Overflow. Cap it at INT_MAX. */
+ new_size = INT_MAX;
}
+ for (i = original_size; i < new_size; ++i) {
+ struct sched_id *new_id;
- return tmp;
+ new_id = ast_calloc(1, sizeof(*new_id));
+ if (!new_id) {
+ break;
+ }
+ new_id->id = i;
+ AST_LIST_INSERT_TAIL(&con->id_queue, new_id, list);
+ ++con->id_queue_size;
+ }
+
+ return con->id_queue_size - original_size;
+}
+
+static int set_sched_id(struct ast_sched_context *con, struct sched *new_sched)
+{
+ if (AST_LIST_EMPTY(&con->id_queue) && (add_ids(con) == 0)) {
+ return -1;
+ }
+
+ new_sched->sched_id = AST_LIST_REMOVE_HEAD(&con->id_queue, list);
+ return 0;
}
static void sched_release(struct ast_sched_context *con, struct sched *tmp)
{
+ if (tmp->sched_id) {
+ AST_LIST_INSERT_TAIL(&con->id_queue, tmp->sched_id, list);
+ tmp->sched_id = NULL;
+ }
+
/*
* Add to the cache, or just free() if we
* already have too many cache entries
*/
-
#ifdef SCHED_MAX_CACHE
if (con->schedccnt < SCHED_MAX_CACHE) {
AST_LIST_INSERT_HEAD(&con->schedc, tmp, list);
sched_free(tmp);
}
+static struct sched *sched_alloc(struct ast_sched_context *con)
+{
+ struct sched *tmp;
+
+ /*
+ * We keep a small cache of schedule entries
+ * to minimize the number of necessary malloc()'s
+ */
+#ifdef SCHED_MAX_CACHE
+ if ((tmp = AST_LIST_REMOVE_HEAD(&con->schedc, list))) {
+ con->schedccnt--;
+ } else
+#endif
+ {
+ tmp = ast_calloc(1, sizeof(*tmp));
+ if (!tmp) {
+ return NULL;
+ }
+ ast_cond_init(&tmp->cond, NULL);
+ }
+
+ if (set_sched_id(con, tmp)) {
+ sched_release(con, tmp);
+ return NULL;
+ }
+
+ return tmp;
+}
+
void ast_sched_clean_by_callback(struct ast_sched_context *con, ast_sched_cb match, ast_sched_cb cleanup_cb)
{
int i = 1;
ast_mutex_lock(&con->lock);
if ((tmp = sched_alloc(con))) {
- tmp->id = con->eventcnt++;
+ con->eventcnt++;
tmp->callback = callback;
tmp->data = data;
tmp->resched = when;
sched_release(con, tmp);
} else {
schedule(con, tmp);
- res = tmp->id;
+ res = tmp->sched_id->id;
}
}
#ifdef DUMP_SCHEDULER
for (x = 1; x <= heap_size; x++) {
struct sched *cur = ast_heap_peek(con->sched_heap, x);
- if (cur->id == id) {
+ if (cur->sched_id->id == id) {
return cur;
}
}
s = sched_find(con, id);
if (s) {
if (!ast_heap_remove(con->sched_heap, s)) {
- ast_log(LOG_WARNING,"sched entry %d not in the sched heap?\n", s->id);
+ ast_log(LOG_WARNING,"sched entry %d not in the sched heap?\n", s->sched_id->id);
}
sched_release(con, s);
- } else if (con->currently_executing && (id == con->currently_executing->id)) {
+ } else if (con->currently_executing && (id == con->currently_executing->sched_id->id)) {
s = con->currently_executing;
s->deleted = 1;
/* Wait for executing task to complete so that caller of ast_sched_del() does not
* free memory out from under the task.
*/
- while (con->currently_executing && (id == con->currently_executing->id)) {
+ while (con->currently_executing && (id == con->currently_executing->sched_id->id)) {
ast_cond_wait(&s->cond, &con->lock);
}
/* Do not sched_release() here because ast_sched_runq() will do it */
q = ast_heap_peek(con->sched_heap, x);
delta = ast_tvsub(q->when, when);
ast_debug(1, "|%.4d | %-15p | %-15p | %.6ld : %.6ld |\n",
- q->id,
+ q->sched_id->id,
q->callback,
q->data,
(long)delta.tv_sec,
return stasis_cp_all_topic_cached(endpoint_cache_all);
}
-static struct ast_manager_event_blob *peerstatus_to_ami(struct stasis_message *msg);
-
STASIS_MESSAGE_TYPE_DEFN(ast_endpoint_snapshot_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_endpoint_state_type,
- .to_ami = peerstatus_to_ami,
-);
static struct ast_manager_event_blob *peerstatus_to_ami(struct stasis_message *msg)
{
ast_str_buffer(peerstatus_event_string));
}
-static struct ast_manager_event_blob *contactstatus_to_ami(struct stasis_message *msg);
+static struct ast_json *peerstatus_to_json(struct stasis_message *msg, const struct stasis_message_sanitizer *sanitize)
+{
+ struct ast_endpoint_blob *obj = stasis_message_data(msg);
+ struct ast_json *json_endpoint;
+ struct ast_json *json_peer;
+ struct ast_json *json_final;
+ const struct timeval *tv = stasis_message_timestamp(msg);
-STASIS_MESSAGE_TYPE_DEFN(ast_endpoint_contact_state_type,
- .to_ami = contactstatus_to_ami,
+ json_endpoint = ast_endpoint_snapshot_to_json(obj->snapshot, NULL);
+ if (!json_endpoint) {
+ return NULL;
+ }
+
+ json_peer = ast_json_object_create();
+ if (!json_peer) {
+ ast_json_unref(json_endpoint);
+ return NULL;
+ }
+
+ /* Copy all fields from the blob */
+ ast_json_object_update(json_peer, obj->blob);
+
+ json_final = ast_json_pack("{s: s, s: o, s: o, s: o }",
+ "type", "PeerStatusChange",
+ "timestamp", ast_json_timeval(*tv, NULL),
+ "endpoint", json_endpoint,
+ "peer", json_peer);
+ if (!json_final) {
+ ast_json_unref(json_endpoint);
+ ast_json_unref(json_peer);
+ }
+
+ return json_final;
+}
+
+STASIS_MESSAGE_TYPE_DEFN(ast_endpoint_state_type,
+ .to_ami = peerstatus_to_ami,
+ .to_json = peerstatus_to_json,
);
static struct ast_manager_event_blob *contactstatus_to_ami(struct stasis_message *msg)
"%s", ast_str_buffer(contactstatus_event_string));
}
+static struct ast_json *contactstatus_to_json(struct stasis_message *msg, const struct stasis_message_sanitizer *sanitize)
+{
+ struct ast_endpoint_blob *obj = stasis_message_data(msg);
+ struct ast_json *json_endpoint;
+ struct ast_json *json_final;
+ const struct timeval *tv = stasis_message_timestamp(msg);
+
+ json_endpoint = ast_endpoint_snapshot_to_json(obj->snapshot, NULL);
+ if (!json_endpoint) {
+ return NULL;
+ }
+
+ json_final = ast_json_pack("{s: s, s: o, s: o, s: { s: s, s: s, s: s } } ",
+ "type", "ContactStatusChange",
+ "timestamp", ast_json_timeval(*tv, NULL),
+ "endpoint", json_endpoint,
+ "contact_info",
+ "uri", ast_json_string_get(ast_json_object_get(obj->blob, "uri")),
+ "contact_status", ast_json_string_get(ast_json_object_get(obj->blob, "contact_status")),
+ "aor", ast_json_string_get(ast_json_object_get(obj->blob, "aor")),
+ "roundtrip_usec", ast_json_string_get(ast_json_object_get(obj->blob, "roundtrip_usec")));
+ if (!json_final) {
+ ast_json_unref(json_endpoint);
+ }
+
+ return json_final;
+}
+
+STASIS_MESSAGE_TYPE_DEFN(ast_endpoint_contact_state_type,
+ .to_ami = contactstatus_to_ami,
+ .to_json = contactstatus_to_json
+);
+
static void endpoint_blob_dtor(void *obj)
{
struct ast_endpoint_blob *event = obj;
pthread_attr_init(attr);
}
-#ifdef __linux__
- /* On Linux, pthread_attr_init() defaults to PTHREAD_EXPLICIT_SCHED,
+#if defined(__linux__) || defined(__FreeBSD__)
+ /* On Linux and FreeBSD , pthread_attr_init() defaults to PTHREAD_EXPLICIT_SCHED,
which is kind of useless. Change this here to
PTHREAD_INHERIT_SCHED; that way the -p option to set realtime
priority will propagate down to new threads by default.
return ast_ari_validate_channel_varset;
}
+int ast_ari_validate_contact_info(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_aor = 0;
+ int has_contact_status = 0;
+ int has_uri = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("aor", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_aor = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactInfo field aor failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("contact_status", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_contact_status = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactInfo field contact_status failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("roundtrip_usec", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactInfo field roundtrip_usec failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("uri", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_uri = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactInfo field uri failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI ContactInfo has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_aor) {
+ ast_log(LOG_ERROR, "ARI ContactInfo missing required field aor\n");
+ res = 0;
+ }
+
+ if (!has_contact_status) {
+ ast_log(LOG_ERROR, "ARI ContactInfo missing required field contact_status\n");
+ res = 0;
+ }
+
+ if (!has_uri) {
+ ast_log(LOG_ERROR, "ARI ContactInfo missing required field uri\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_contact_info_fn(void)
+{
+ return ast_ari_validate_contact_info;
+}
+
+int ast_ari_validate_contact_status_change(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_type = 0;
+ int has_application = 0;
+ int has_contact_info = 0;
+ int has_endpoint = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_type = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange field type failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_application = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange field application failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_date(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange field timestamp failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("contact_info", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_contact_info = 1;
+ prop_is_valid = ast_ari_validate_contact_info(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange field contact_info failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("endpoint", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_endpoint = 1;
+ prop_is_valid = ast_ari_validate_endpoint(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange field endpoint failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI ContactStatusChange has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_type) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange missing required field type\n");
+ res = 0;
+ }
+
+ if (!has_application) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange missing required field application\n");
+ res = 0;
+ }
+
+ if (!has_contact_info) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange missing required field contact_info\n");
+ res = 0;
+ }
+
+ if (!has_endpoint) {
+ ast_log(LOG_ERROR, "ARI ContactStatusChange missing required field endpoint\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_contact_status_change_fn(void)
+{
+ return ast_ari_validate_contact_status_change;
+}
+
int ast_ari_validate_device_state_changed(struct ast_json *json)
{
int res = 1;
discriminator = ast_json_string_get(ast_json_object_get(json, "type"));
if (!discriminator) {
- ast_log(LOG_ERROR, "ARI Event missing required field type");
+ ast_log(LOG_ERROR, "ARI Event missing required field type\n");
return 0;
}
if (strcmp("ChannelVarset", discriminator) == 0) {
return ast_ari_validate_channel_varset(json);
} else
+ if (strcmp("ContactStatusChange", discriminator) == 0) {
+ return ast_ari_validate_contact_status_change(json);
+ } else
if (strcmp("DeviceStateChanged", discriminator) == 0) {
return ast_ari_validate_device_state_changed(json);
} else
if (strcmp("EndpointStateChange", discriminator) == 0) {
return ast_ari_validate_endpoint_state_change(json);
} else
+ if (strcmp("PeerStatusChange", discriminator) == 0) {
+ return ast_ari_validate_peer_status_change(json);
+ } else
if (strcmp("PlaybackFinished", discriminator) == 0) {
return ast_ari_validate_playback_finished(json);
} else
discriminator = ast_json_string_get(ast_json_object_get(json, "type"));
if (!discriminator) {
- ast_log(LOG_ERROR, "ARI Message missing required field type");
+ ast_log(LOG_ERROR, "ARI Message missing required field type\n");
return 0;
}
if (strcmp("ChannelVarset", discriminator) == 0) {
return ast_ari_validate_channel_varset(json);
} else
+ if (strcmp("ContactStatusChange", discriminator) == 0) {
+ return ast_ari_validate_contact_status_change(json);
+ } else
if (strcmp("DeviceStateChanged", discriminator) == 0) {
return ast_ari_validate_device_state_changed(json);
} else
if (strcmp("MissingParams", discriminator) == 0) {
return ast_ari_validate_missing_params(json);
} else
+ if (strcmp("PeerStatusChange", discriminator) == 0) {
+ return ast_ari_validate_peer_status_change(json);
+ } else
if (strcmp("PlaybackFinished", discriminator) == 0) {
return ast_ari_validate_playback_finished(json);
} else
return ast_ari_validate_missing_params;
}
+int ast_ari_validate_peer(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_peer_status = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("address", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Peer field address failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("cause", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Peer field cause failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("peer_status", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_peer_status = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Peer field peer_status failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("port", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Peer field port failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("time", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Peer field time failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI Peer has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_peer_status) {
+ ast_log(LOG_ERROR, "ARI Peer missing required field peer_status\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_peer_fn(void)
+{
+ return ast_ari_validate_peer;
+}
+
+int ast_ari_validate_peer_status_change(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_type = 0;
+ int has_application = 0;
+ int has_endpoint = 0;
+ int has_peer = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_type = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange field type failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_application = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange field application failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_date(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange field timestamp failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("endpoint", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_endpoint = 1;
+ prop_is_valid = ast_ari_validate_endpoint(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange field endpoint failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("peer", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_peer = 1;
+ prop_is_valid = ast_ari_validate_peer(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange field peer failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI PeerStatusChange has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_type) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange missing required field type\n");
+ res = 0;
+ }
+
+ if (!has_application) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange missing required field application\n");
+ res = 0;
+ }
+
+ if (!has_endpoint) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange missing required field endpoint\n");
+ res = 0;
+ }
+
+ if (!has_peer) {
+ ast_log(LOG_ERROR, "ARI PeerStatusChange missing required field peer\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_peer_status_change_fn(void)
+{
+ return ast_ari_validate_peer_status_change;
+}
+
int ast_ari_validate_playback_finished(struct ast_json *json)
{
int res = 1;
ari_validator ast_ari_validate_channel_varset_fn(void);
/*!
+ * \brief Validator for ContactInfo.
+ *
+ * Detailed information about a contact on an endpoint.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_contact_info(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_contact_info().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_contact_info_fn(void);
+
+/*!
+ * \brief Validator for ContactStatusChange.
+ *
+ * The state of a contact on an endpoint has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_contact_status_change(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_contact_status_change().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_contact_status_change_fn(void);
+
+/*!
* \brief Validator for DeviceStateChanged.
*
* Notification that a device state has changed.
ari_validator ast_ari_validate_missing_params_fn(void);
/*!
+ * \brief Validator for Peer.
+ *
+ * Detailed information about a remote peer that communicates with Asterisk.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_peer(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_peer().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_peer_fn(void);
+
+/*!
+ * \brief Validator for PeerStatusChange.
+ *
+ * The state of a peer associated with an endpoint has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_peer_status_change(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_peer_status_change().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_peer_status_change_fn(void);
+
+/*!
* \brief Validator for PlaybackFinished.
*
* Event showing the completion of a media playback operation.
* - channel: Channel
* - value: string (required)
* - variable: string (required)
+ * ContactInfo
+ * - aor: string (required)
+ * - contact_status: string (required)
+ * - roundtrip_usec: string
+ * - uri: string (required)
+ * ContactStatusChange
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - contact_info: ContactInfo (required)
+ * - endpoint: Endpoint (required)
* DeviceStateChanged
* - type: string (required)
* - application: string (required)
* MissingParams
* - type: string (required)
* - params: List[string] (required)
+ * Peer
+ * - address: string
+ * - cause: string
+ * - peer_status: string (required)
+ * - port: string
+ * - time: string
+ * PeerStatusChange
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - endpoint: Endpoint (required)
+ * - peer: Peer (required)
* PlaybackFinished
* - type: string (required)
* - application: string (required)
}
event_session_shutdown(session);
- ao2_unlink(event_session_registry, session);
+ if (event_session_registry) {
+ ao2_unlink(event_session_registry, session);
+ }
}
/*!
struct ast_ari_events_event_websocket_args *args, const char *session_id)
{
RAII_VAR(struct event_session *, session, NULL, ao2_cleanup);
+ int (* register_handler)(const char *, stasis_app_cb handler, void *data);
size_t size, i;
/* The request must have at least one [app] parameter */
}
/* Register the apps with Stasis */
+ if (args->subscribe_all) {
+ register_handler = &stasis_app_register_all;
+ } else {
+ register_handler = &stasis_app_register;
+ }
+
for (i = 0; i < args->app_count; ++i) {
const char *app = args->app[i];
return event_session_allocation_error_handler(session, ERROR_TYPE_OOM, ser);
}
- if (stasis_app_register(app, stasis_app_message_handler, session)) {
+ if (register_handler(app, stasis_app_message_handler, session)) {
ast_log(LOG_WARNING, "Stasis registration failed for application: '%s'\n", app);
return event_session_allocation_error_handler(
- session, ERROR_TYPE_STASIS_REGISTRATION, ser);
+ session, ERROR_TYPE_STASIS_REGISTRATION, ser);
}
}
return 0;
}
+static int event_session_shutdown_cb(void *session, void *arg, int flags)
+{
+ event_session_cleanup(session);
+
+ return 0;
+}
+
void ast_ari_websocket_events_event_websocket_dtor(void)
{
+ ao2_callback(event_session_registry, OBJ_MULTIPLE | OBJ_NODATA, event_session_shutdown_cb, NULL);
+
ao2_cleanup(event_session_registry);
event_session_registry = NULL;
}
struct ast_ari_websocket_session *ws_session, struct ast_variable *headers,
struct ast_ari_events_event_websocket_args *args)
{
- RAII_VAR(struct event_session *, session, NULL, event_session_cleanup);
+ struct event_session *session;
+
struct ast_json *msg;
const char *session_id;
/* Find the event_session and update its websocket */
session = ao2_find(event_session_registry, session_id, OBJ_SEARCH_KEY);
-
if (session) {
ao2_unlink(event_session_registry, session);
event_session_update_websocket(session, ws_session);
while ((msg = ast_ari_websocket_session_read(ws_session))) {
ast_json_unref(msg);
}
+
+ event_session_cleanup(session);
+ ao2_ref(session, -1);
}
void ast_ari_events_user_event(struct ast_variable *headers,
size_t app_count;
/*! Parsing context for app. */
char *app_parse;
+ /*! Subscribe to all Asterisk events. If provided, the applications listed will be subscribed to all events, effectively disabling the application specific subscriptions. Default is 'false'. */
+ int subscribe_all;
};
/*!
return pa_data;
}
+/*! \internal
+ * \brief Gathers inheritable channel variables from a channel by name.
+ *
+ * \param oh outgoing helper struct we are bestowing inheritable variables to
+ * \param channel_id name or uniqueID of the channel to inherit variables from
+ *
+ * \return Nothing
+ */
+static void inherit_channel_vars_from_id(struct outgoing_helper *oh, const char *channel_id)
+{
+ struct ast_channel *chan = ast_channel_get_by_name(channel_id);
+ struct ast_var_t *current;
+ struct ast_variable *newvar;
+ const char *varname;
+ int vartype;
+
+
+ if (!chan) {
+ /* Already gone */
+ return;
+ }
+
+ ast_channel_lock(chan);
+
+ AST_LIST_TRAVERSE(ast_channel_varshead((struct ast_channel *) chan), current, entries) {
+ varname = ast_var_full_name(current);
+ if (!varname) {
+ continue;
+ }
+
+ vartype = 0;
+ if (varname[0] == '_') {
+ vartype = 1;
+ if (varname[1] == '_') {
+ vartype = 2;
+ }
+ }
+
+ switch (vartype) {
+ case 1:
+ newvar = ast_variable_new(&varname[1], ast_var_value(current), "");
+ break;
+ case 2:
+ newvar = ast_variable_new(varname, ast_var_value(current), "");
+ break;
+ default:
+ continue;
+ }
+ if (newvar) {
+ ast_debug(1, "Inheriting variable %s from %s.\n",
+ newvar->name, ast_channel_name(chan));
+ if (oh->vars) {
+ newvar->next = oh->vars;
+ oh->vars = newvar;
+ }
+ }
+ }
+
+ ast_channel_unlock(chan);
+ ast_channel_cleanup(chan);
+}
+
static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
{
struct ast_channel *dchan;
snprintf(buf, sizeof(buf), "%d", parkingspace);
oh.vars = ast_variable_new("_PARKEDAT", buf, "");
+
+ inherit_channel_vars_from_id(&oh, parkee_snapshot->uniqueid);
+
dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, NULL, dial_string, 30000,
&outstate,
parkee_snapshot->caller_number,
args.app[j] = (vals[j]);
}
} else
+ if (strcmp(i->name, "subscribeAll") == 0) {
+ args.subscribe_all = ast_true(i->value);
+ } else
{}
}
args.app[j] = (vals[j]);
}
} else
+ if (strcmp(i->name, "subscribeAll") == 0) {
+ args.subscribe_all = ast_true(i->value);
+ } else
{}
}
struct ast_str *sql1 = ast_str_create(160), *sql2 = ast_str_create(16);
int first = 1;
+ if (!sql1 || !sql2) {
+ ast_free(sql1);
+ ast_free(sql2);
+ return -1;
+ }
+
if (!tbl) {
ast_log(LOG_WARNING, "No such table: %s\n", cdr_table);
+ ast_free(sql1);
+ ast_free(sql2);
return -1;
}
Identifier names are usually derived from and can be found in the endpoint
identifier module itself (res_pjsip_endpoint_identifier_*)</synopsis>
</configOption>
+ <configOption name="default_from_user" default="asterisk">
+ <synopsis>When Asterisk generates an outgoing SIP request, the From header username will be
+ set to this value if there is no better option (such as CallerID) to be
+ used.</synopsis>
+ </configOption>
</configObject>
</configFile>
</configInfo>
pjsip_sip_uri *sip_uri;
pjsip_transport_type_e type = PJSIP_TRANSPORT_UNSPECIFIED;
int local_port;
- char uuid_str[AST_UUID_STR_LEN];
+ char default_user[PJSIP_MAX_URL_SIZE];
if (ast_strlen_zero(user)) {
- user = ast_uuid_generate_str(uuid_str, sizeof(uuid_str));
+ ast_sip_get_default_from_user(default_user, sizeof(default_user));
+ user = default_user;
}
/* Parse the provided target URI so we can determine what transport it will end up using */
#define DEFAULT_DEBUG "no"
#define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
+#define DEFAULT_FROM_USER "asterisk"
static char default_useragent[256];
AST_STRING_FIELD(debug);
/*! Order by which endpoint identifiers are checked (comma separated list) */
AST_STRING_FIELD(endpoint_identifier_order);
+ /*! User name to place in From header if there is no better option */
+ AST_STRING_FIELD(default_from_user);
);
/* Value to put in Max-Forwards header */
unsigned int max_forwards;
return time;
}
+void ast_sip_get_default_from_user(char *from_user, size_t size)
+{
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ ast_copy_string(from_user, DEFAULT_FROM_USER, size);
+ } else {
+ ast_copy_string(from_user, cfg->default_from_user, size);
+ ao2_ref(cfg, -1);
+ }
+}
+
/*!
* \internal
* \brief Observer to set default global object if none exist.
ast_sorcery_object_field_register(sorcery, "global", "max_initial_qualify_time",
__stringify(DEFAULT_MAX_INITIAL_QUALIFY_TIME),
OPT_UINT_T, 0, FLDSET(struct global_config, max_initial_qualify_time));
+ ast_sorcery_object_field_register(sorcery, "global", "default_from_user", DEFAULT_FROM_USER,
+ OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, default_from_user));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
return aor;
}
+/*! \brief Internal callback function which destroys the specified contact */
+static int destroy_contact(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+
+ ast_sip_location_delete_contact(contact);
+
+ return CMP_MATCH;
+}
+
+static void aor_deleted_observer(const void *object)
+{
+ const char *aor_id = ast_sorcery_object_get_id(object);
+ /* Give enough space for ^ at the beginning and ;@ at the end, since that is our object naming scheme */
+ char regex[strlen(aor_id) + 4];
+ struct ao2_container *contacts;
+
+ snprintf(regex, sizeof(regex), "^%s;@", aor_id);
+
+ if (!(contacts = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "contact", regex))) {
+ return;
+ }
+
+ /* Destroy any contacts that may still exist that were made for this AoR */
+ ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, destroy_contact, NULL);
+
+ ao2_ref(contacts, -1);
+}
+
+/*! \brief Observer for contacts so state can be updated on respective endpoints */
+static const struct ast_sorcery_observer aor_observer = {
+ .deleted = aor_deleted_observer,
+};
+
+
/*! \brief Destructor for contact */
static void contact_destroy(void *obj)
{
return 0;
}
-/*! \brief Simple callback function which returns immediately, used to grab the first contact of an AOR */
-static int contact_find_first(void *obj, void *arg, int flags)
-{
- return CMP_MATCH | CMP_STOP;
-}
-
struct ast_sip_contact *ast_sip_location_retrieve_first_aor_contact(const struct ast_sip_aor *aor)
{
RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
return NULL;
}
- contact = ao2_callback(contacts, 0, contact_find_first, NULL);
+ /* Get the first AOR contact in the container. */
+ contact = ao2_callback(contacts, 0, NULL, NULL);
return contact;
}
{
char name[MAX_OBJECT_FIELD * 2 + 3];
RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+ char hash[33];
- snprintf(name, sizeof(name), "%s;@%s", ast_sorcery_object_get_id(aor), uri);
+ ast_md5_hash(hash, uri);
+ snprintf(name, sizeof(name), "%s;@%s", ast_sorcery_object_get_id(aor), hash);
if (!(contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name))) {
return -1;
return -1;
}
+ ast_sorcery_observer_add(sorcery, "aor", &aor_observer);
+
ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri));
ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
int ast_sip_destroy_sorcery_location(void)
{
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor", &aor_observer);
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
ast_sip_unregister_cli_formatter(contact_formatter);
ast_sip_unregister_cli_formatter(aor_formatter);
{
ast_free(*dst);
*dst = ast_malloc(pj_strlen(src) + 1);
- ast_copy_pj_str(*dst, src, pj_strlen(src) + 1);
+ if (*dst) {
+ ast_copy_pj_str(*dst, src, pj_strlen(src) + 1);
+ }
}
static void set_redirecting_id(pjsip_name_addr *name_addr, struct ast_party_id *data,
break;
}
}
- } else {
+ } else if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method)) {
rr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_RECORD_ROUTE, NULL);
}
tree->root = tree_node_alloc(resource, &visited, list->full_state);
if (!tree->root) {
+ AST_VECTOR_FREE(&visited);
return 500;
}
return;
}
+ /* We notify subscription shutdown only on the tree leaves. */
if (sub->handler->subscription_shutdown) {
sub->handler->subscription_shutdown(sub);
}
sub_tree = allocate_subscription_tree(endpoint);
if (!sub_tree) {
- pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+ *dlg_status = PJ_ENOMEM;
return NULL;
}
sub_tree->role = AST_SIP_NOTIFIER;
return sub_tree;
}
-static int generate_initial_notify(struct ast_sip_subscription *sub);
+static int initial_notify_task(void *obj);
static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state);
/*! \brief Callback function to perform the actual recreation of a subscription */
request_uri = pjsip_uri_get_uri(rdata.msg_info.msg->line.req.uri);
resource_size = pj_strlen(&request_uri->user) + 1;
- resource = alloca(resource_size);
+ resource = ast_alloca(resource_size);
ast_copy_pj_str(resource, &request_uri->user, resource_size);
/* Update the expiration header with the new expiration */
}
sub_tree->persistence = ao2_bump(persistence);
subscription_persistence_update(sub_tree, &rdata);
- if (generate_initial_notify(sub_tree->root)) {
+ if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ao2_bump(sub_tree))) {
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
- } else {
- send_notify(sub_tree, 1);
+ ao2_ref(sub_tree, -1);
}
} else {
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
}
+/* XXX This function is not used. */
struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
struct ast_sip_endpoint *endpoint, const char *resource)
{
return 0;
}
+ /* We notify subscription establishment only on the tree leaves. */
if (sub->handler->notifier->subscription_established(sub)) {
return -1;
}
return res;
}
+static int initial_notify_task(void * obj)
+{
+ struct sip_subscription_tree *sub_tree;
+
+ sub_tree = obj;
+ if (generate_initial_notify(sub_tree->root)) {
+ pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ } else {
+ send_notify(sub_tree, 1);
+ ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
+ "Resource: %s",
+ sub_tree->root->resource);
+ }
+
+ ao2_ref(sub_tree, -1);
+ return 0;
+}
+
static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
{
pjsip_expires_hdr *expires_header;
request_uri_sip = pjsip_uri_get_uri(request_uri);
resource_size = pj_strlen(&request_uri_sip->user) + 1;
- resource = alloca(resource_size);
+ resource = ast_alloca(resource_size);
ast_copy_pj_str(resource, &request_uri_sip->user, resource_size);
expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, rdata->msg_info.msg->hdr.next);
sub_tree->persistence = subscription_persistence_create(sub_tree);
subscription_persistence_update(sub_tree, rdata);
sip_subscription_accept(sub_tree, rdata, resp);
- if (generate_initial_notify(sub_tree->root)) {
+ if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ao2_bump(sub_tree))) {
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
- } else {
- send_notify(sub_tree, 1);
- ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
- "Resource: %s",
- sub_tree->root->resource);
+ ao2_ref(sub_tree, -1);
}
}
request_uri_sip = pjsip_uri_get_uri(request_uri);
resource_size = pj_strlen(&request_uri_sip->user) + 1;
- resource_name = alloca(resource_size);
+ resource_name = ast_alloca(resource_size);
ast_copy_pj_str(resource_name, &request_uri_sip->user, resource_size);
resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name);
AST_LIST_INSERT_HEAD(&body_generators, generator, list);
AST_RWLIST_UNLOCK(&body_generators);
- /* Lengths of type and subtype plus space for a slash. pj_str_t is not
- * null-terminated, so there is no need to allocate for the extra null
- * byte
- */
+ /* Lengths of type and subtype plus a slash. */
accept_len = strlen(generator->type) + strlen(generator->subtype) + 1;
- accept.ptr = alloca(accept_len);
- accept.slen = accept_len;
- /* Safe use of sprintf */
- sprintf(accept.ptr, "%s/%s", generator->type, generator->subtype);
+ /* Add room for null terminator that sprintf() will set. */
+ pj_strset(&accept, ast_alloca(accept_len + 1), accept_len);
+ sprintf((char *) pj_strbuf(&accept), "%s/%s", generator->type, generator->subtype);/* Safe */
+
pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module,
PJSIP_H_ACCEPT, NULL, 1, &accept);
if (apply_list_configuration(sorcery)) {
ast_sip_unregister_service(&pubsub_module);
ast_sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
}
ast_sorcery_apply_default(sorcery, "inbound-publication", "config", "pjsip.conf,criteria=type=inbound-publication");
session->contact = ao2_bump(contact);
session->inv_session = inv_session;
session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!session->req_caps) {
+ /* Release the ref held by session->inv_session */
+ ao2_ref(session, -1);
+ return NULL;
+ }
if ((endpoint->dtmf == AST_SIP_DTMF_INBAND) || (endpoint->dtmf == AST_SIP_DTMF_AUTO)) {
dsp_features |= DSP_FEATURE_DIGIT_DETECT;
CHECK_PJSIP_MODULE_LOADED();
pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WS", 5060, &transport_type_ws);
- pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WSS", 5060, &transport_type_wss);
+ pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE, "WSS", 5060, &transport_type_wss);
if (ast_sip_register_service(&websocket_module) != PJ_SUCCESS) {
return AST_MODULE_LOAD_DECLINE;
pj_ice_sess_role role = rtp->ice->role;
int res;
+ ast_debug(3, "Resetting ICE for RTP instance '%p'\n", instance);
if (!rtp->ice->is_nominating && !rtp->ice->is_complete) {
+ ast_debug(3, "Nevermind. ICE isn't ready for a reset\n");
return 0;
}
+ ast_debug(3, "Stopping ICE for RTP instance '%p'\n", instance);
ast_rtp_ice_stop(instance);
+ ast_debug(3, "Recreating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port, instance);
res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1);
if (!res) {
/* Preserve the role that the old ICE session used */
/* Check for equivalence in the lists */
if (rtp->ice_active_remote_candidates &&
!ice_candidates_compare(rtp->ice_proposed_remote_candidates, rtp->ice_active_remote_candidates)) {
+ ast_debug(3, "Proposed == active candidates for RTP instance '%p'\n", instance);
ao2_cleanup(rtp->ice_proposed_remote_candidates);
rtp->ice_proposed_remote_candidates = NULL;
return;
}
if (candidate->id == AST_RTP_ICE_COMPONENT_RTP && rtp->turn_rtp) {
+ ast_debug(3, "RTP candidate %s (%p)\n", ast_sockaddr_stringify(&candidate->address), instance);
pj_turn_sock_set_perm(rtp->turn_rtp, 1, &candidates[cand_cnt].addr, 1);
} else if (candidate->id == AST_RTP_ICE_COMPONENT_RTCP && rtp->turn_rtcp) {
+ ast_debug(3, "RTCP candidate %s (%p)\n", ast_sockaddr_stringify(&candidate->address), instance);
pj_turn_sock_set_perm(rtp->turn_rtcp, 1, &candidates[cand_cnt].addr, 1);
}
ao2_iterator_destroy(&i);
- if (has_rtp && has_rtcp &&
- pj_ice_sess_create_check_list(rtp->ice, &ufrag, &passwd, ao2_container_count(
- rtp->ice_active_remote_candidates), &candidates[0]) == PJ_SUCCESS) {
- ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: SUCCESS");
- pj_ice_sess_start_check(rtp->ice);
- pj_timer_heap_poll(timer_heap, NULL);
- rtp->strict_rtp_state = STRICT_RTP_OPEN;
- return;
+ if (cand_cnt < ao2_container_count(rtp->ice_active_remote_candidates)) {
+ ast_log(LOG_WARNING, "Lost %d ICE candidates. Consider increasing PJ_ICE_MAX_CAND in PJSIP (%p)\n",
+ ao2_container_count(rtp->ice_active_remote_candidates) - cand_cnt, instance);
+ }
+
+ if (!has_rtp) {
+ ast_log(LOG_WARNING, "No RTP candidates; skipping ICE checklist (%p)\n", instance);
+ }
+
+ if (!has_rtcp) {
+ ast_log(LOG_WARNING, "No RTCP candidates; skipping ICE checklist (%p)\n", instance);
+ }
+
+ if (has_rtp && has_rtcp) {
+ pj_status_t res = pj_ice_sess_create_check_list(rtp->ice, &ufrag, &passwd, cand_cnt, &candidates[0]);
+ char reason[80];
+
+ if (res == PJ_SUCCESS) {
+ ast_debug(3, "Successfully created ICE checklist (%p)\n", instance);
+ ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: SUCCESS");
+ pj_ice_sess_start_check(rtp->ice);
+ pj_timer_heap_poll(timer_heap, NULL);
+ rtp->strict_rtp_state = STRICT_RTP_OPEN;
+ return;
+ }
+
+ pj_strerror(res, reason, sizeof(reason));
+ ast_log(LOG_WARNING, "Failed to create ICE session check list: %s (%p)\n", reason, instance);
}
ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: FAILURE");
/* even though create check list failed don't stop ice as
it might still work */
- ast_debug(1, "Failed to create ICE session check list\n");
/* however we do need to reset remote candidates since
this function may be re-entered */
ao2_ref(rtp->ice_active_remote_candidates, -1);
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+ ast_debug(3, "Set role to %s (%p)\n",
+ role == AST_RTP_ICE_ROLE_CONTROLLED ? "CONTROLLED" : "CONTROLLING", instance);
+
if (!rtp->ice) {
+ ast_log(LOG_WARNING, "Set role failed; no ice instance (%p)\n", instance);
return;
}
create_new_socket("RTP",
ast_sockaddr_is_ipv4(addr) ? AF_INET :
ast_sockaddr_is_ipv6(addr) ? AF_INET6 : -1)) < 0) {
- ast_debug(1, "Failed to create a new socket for RTP instance '%p'\n", instance);
+ ast_log(LOG_WARNING, "Failed to create a new socket for RTP instance '%p'\n", instance);
ast_free(rtp);
return -1;
}
#ifdef HAVE_PJPROJECT
/* Create an ICE session for ICE negotiation */
if (icesupport) {
+ ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(addr), x, instance);
if (ice_create(instance, addr, x, 0)) {
ast_log(LOG_NOTICE, "Failed to start ICE session\n");
} else {
struct ao2_container *app_bridges_playback;
+/*!
+ * \internal \brief List of registered event sources.
+ */
+AST_RWLIST_HEAD_STATIC(event_sources, stasis_app_event_source);
+
static struct ast_json *stasis_end_to_json(struct stasis_message *message,
const struct stasis_message_sanitizer *sanitize)
{
return ao2_bump(apps);
}
-int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
+static int __stasis_app_register(const char *app_name, stasis_app_cb handler, void *data, int all_events)
{
RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
if (app) {
app_update(app, handler, data);
} else {
- app = app_create(app_name, handler, data);
+ app = app_create(app_name, handler, data, all_events ? STASIS_APP_SUBSCRIBE_ALL : STASIS_APP_SUBSCRIBE_MANUAL);
if (app) {
+ if (all_events) {
+ struct stasis_app_event_source *source;
+ SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+
+ AST_LIST_TRAVERSE(&event_sources, source, next) {
+ if (!source->subscribe) {
+ continue;
+ }
+
+ source->subscribe(app, NULL);
+ }
+ }
ao2_link_flags(apps_registry, app, OBJ_NOLOCK);
} else {
ao2_unlock(apps_registry);
return 0;
}
+int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
+{
+ return __stasis_app_register(app_name, handler, data, 0);
+}
+
+int stasis_app_register_all(const char *app_name, stasis_app_cb handler, void *data)
+{
+ return __stasis_app_register(app_name, handler, data, 1);
+}
+
void stasis_app_unregister(const char *app_name)
{
RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
cleanup();
}
-/*!
- * \internal \brief List of registered event sources.
- */
-AST_RWLIST_HEAD_STATIC(event_sources, stasis_app_event_source);
-
void stasis_app_register_event_source(struct stasis_app_event_source *obj)
{
SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
ast_debug(3, "%s: Checking %s\n", app_name, uri);
- if (!event_source->find ||
- (!(obj = event_source->find(app, uri + strlen(event_source->scheme))))) {
+ if (!ast_strlen_zero(uri + strlen(event_source->scheme)) &&
+ (!event_source->find || (!(obj = event_source->find(app, uri + strlen(event_source->scheme)))))) {
ast_log(LOG_WARNING, "Event source not found: %s\n", uri);
return STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
}
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application support",
+ .load_pri = AST_MODPRI_APP_DEPEND,
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
/*! Number of hash buckets for device state subscriptions */
#define DEVICE_STATE_BUCKETS 37
+/*! The key used for tracking a subscription to all device states */
+#define DEVICE_STATE_ALL "__AST_DEVICE_STATE_ALL_TOPIC"
+
/*! Container for subscribed device states */
static struct ao2_container *device_state_subscriptions;
static struct device_state_subscription *device_state_subscription_create(
const struct stasis_app *app, const char *device_name)
{
- struct device_state_subscription *sub = ao2_alloc(
- sizeof(*sub), device_state_subscription_destroy);
+ struct device_state_subscription *sub;
const char *app_name = stasis_app_name(app);
- size_t size = strlen(device_name) + strlen(app_name) + 2;
+ size_t size;
+
+ if (ast_strlen_zero(device_name)) {
+ device_name = DEVICE_STATE_ALL;
+ }
+ size = strlen(device_name) + strlen(app_name) + 2;
+
+ sub = ao2_alloc(sizeof(*sub), device_state_subscription_destroy);
if (!sub) {
return NULL;
}
static int is_subscribed_device_state(struct stasis_app *app, const char *name)
{
- RAII_VAR(struct device_state_subscription *, sub,
- find_device_state_subscription(app, name), ao2_cleanup);
- return sub != NULL;
+ struct device_state_subscription *sub;
+
+ sub = find_device_state_subscription(app, DEVICE_STATE_ALL);
+ if (sub) {
+ ao2_ref(sub, -1);
+ return 1;
+ }
+
+ sub = find_device_state_subscription(app, name);
+ if (sub) {
+ ao2_ref(sub, -1);
+ return 1;
+ }
+
+ return 0;
}
static int subscribe_device_state(struct stasis_app *app, void *obj)
{
struct device_state_subscription *sub = obj;
+ struct stasis_topic *topic;
+
+ if (!sub) {
+ sub = device_state_subscription_create(app, NULL);
+ if (!sub) {
+ return -1;
+ }
+ }
- ast_debug(3, "Subscribing to device %s", sub->device_name);
+ if (strcmp(sub->device_name, DEVICE_STATE_ALL)) {
+ topic = ast_device_state_topic(sub->device_name);
+ } else {
+ topic = ast_device_state_topic_all();
+ }
if (is_subscribed_device_state(app, sub->device_name)) {
ast_debug(3, "App %s is already subscribed to %s\n", stasis_app_name(app), sub->device_name);
return 0;
}
- if (!(sub->sub = stasis_subscribe_pool(
- ast_device_state_topic(sub->device_name),
- device_state_cb, sub))) {
+ ast_debug(3, "Subscribing to device %s\n", sub->device_name);
+
+ sub->sub = stasis_subscribe_pool(topic, device_state_cb, sub);
+ if (!sub->sub) {
ast_log(LOG_ERROR, "Unable to subscribe to device %s\n",
sub->device_name);
return -1;
#include "asterisk/stasis_endpoints.h"
#include "asterisk/stasis_message_router.h"
+#define BRIDGE_ALL "__AST_BRIDGE_ALL_TOPIC"
+#define CHANNEL_ALL "__AST_CHANNEL_ALL_TOPIC"
+#define ENDPOINT_ALL "__AST_ENDPOINT_ALL_TOPIC"
+
static int unsubscribe(struct stasis_app *app, const char *kind, const char *id, int terminate);
struct stasis_app {
struct stasis_message_router *router;
/*! Router for handling messages to the bridge all \a topic. */
struct stasis_message_router *bridge_router;
+ /*! Optional router for handling endpoint messages in 'all' subscriptions */
+ struct stasis_message_router *endpoint_router;
/*! 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;
+ /*! Subscription model for the application */
+ enum stasis_app_subscription_model subscription_model;
/*! Name of the Stasis application */
char name[];
};
static struct app_forwards *forwards_create_channel(struct stasis_app *app,
struct ast_channel *chan)
{
- RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
+ struct app_forwards *forwards;
- if (!app || !chan) {
+ if (!app) {
return NULL;
}
- forwards = forwards_create(app, ast_channel_uniqueid(chan));
+ forwards = forwards_create(app, chan ? ast_channel_uniqueid(chan) : CHANNEL_ALL);
if (!forwards) {
return NULL;
}
forwards->forward_type = FORWARD_CHANNEL;
- forwards->topic_forward = stasis_forward_all(ast_channel_topic(chan),
- app->topic);
- if (!forwards->topic_forward) {
- return NULL;
+ if (chan) {
+ forwards->topic_forward = stasis_forward_all(ast_channel_topic(chan),
+ app->topic);
}
-
forwards->topic_cached_forward = stasis_forward_all(
- ast_channel_topic_cached(chan), app->topic);
- if (!forwards->topic_cached_forward) {
+ chan ? ast_channel_topic_cached(chan) : ast_channel_topic_all_cached(),
+ app->topic);
+
+ if ((!forwards->topic_forward && chan) || !forwards->topic_cached_forward) {
/* Half-subscribed is a bad thing */
- stasis_forward_cancel(forwards->topic_forward);
- forwards->topic_forward = NULL;
+ forwards_unsubscribe(forwards);
+ ao2_ref(forwards, -1);
return NULL;
}
- ao2_ref(forwards, +1);
return forwards;
}
static struct app_forwards *forwards_create_bridge(struct stasis_app *app,
struct ast_bridge *bridge)
{
- RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
+ struct app_forwards *forwards;
- if (!app || !bridge) {
+ if (!app) {
return NULL;
}
- forwards = forwards_create(app, bridge->uniqueid);
+ forwards = forwards_create(app, bridge ? bridge->uniqueid : BRIDGE_ALL);
if (!forwards) {
return NULL;
}
forwards->forward_type = FORWARD_BRIDGE;
- forwards->topic_forward = stasis_forward_all(ast_bridge_topic(bridge),
- app->topic);
- if (!forwards->topic_forward) {
- return NULL;
+ if (bridge) {
+ forwards->topic_forward = stasis_forward_all(ast_bridge_topic(bridge),
+ app->topic);
}
-
forwards->topic_cached_forward = stasis_forward_all(
- ast_bridge_topic_cached(bridge), app->topic);
- if (!forwards->topic_cached_forward) {
+ bridge ? ast_bridge_topic_cached(bridge) : ast_bridge_topic_all_cached(),
+ app->topic);
+
+ if ((!forwards->topic_forward && bridge) || !forwards->topic_cached_forward) {
/* Half-subscribed is a bad thing */
- stasis_forward_cancel(forwards->topic_forward);
- forwards->topic_forward = NULL;
+ forwards_unsubscribe(forwards);
+ ao2_ref(forwards, -1);
return NULL;
}
- ao2_ref(forwards, +1);
return forwards;
}
+static void endpoint_state_cb(void *data, struct stasis_subscription *sub,
+ struct stasis_message *message)
+{
+ struct stasis_app *app = data;
+
+ stasis_publish(app->topic, message);
+}
+
/*! Forward a endpoint's topics to an app */
static struct app_forwards *forwards_create_endpoint(struct stasis_app *app,
struct ast_endpoint *endpoint)
{
- RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
+ struct app_forwards *forwards;
+ int ret = 0;
- if (!app || !endpoint) {
+ if (!app) {
return NULL;
}
- forwards = forwards_create(app, ast_endpoint_get_id(endpoint));
+ forwards = forwards_create(app, endpoint ? ast_endpoint_get_id(endpoint) : ENDPOINT_ALL);
if (!forwards) {
return NULL;
}
forwards->forward_type = FORWARD_ENDPOINT;
- forwards->topic_forward = stasis_forward_all(ast_endpoint_topic(endpoint),
- app->topic);
- if (!forwards->topic_forward) {
- return NULL;
- }
+ if (endpoint) {
+ forwards->topic_forward = stasis_forward_all(ast_endpoint_topic(endpoint),
+ app->topic);
+ forwards->topic_cached_forward = stasis_forward_all(
+ ast_endpoint_topic_cached(endpoint), app->topic);
+
+ if (!forwards->topic_forward || !forwards->topic_cached_forward) {
+ /* Half-subscribed is a bad thing */
+ forwards_unsubscribe(forwards);
+ ao2_ref(forwards, -1);
+ return NULL;
+ }
+ } else {
+ /* Since endpoint subscriptions also subscribe to channels, in the case
+ * of all endpoint subscriptions, we only want messages for the endpoints.
+ * As such, we route those particular messages and then re-publish them
+ * on the app's topic.
+ */
+ ast_assert(app->endpoint_router == NULL);
+ app->endpoint_router = stasis_message_router_create(ast_endpoint_topic_all_cached());
+ if (!app->endpoint_router) {
+ forwards_unsubscribe(forwards);
+ ao2_ref(forwards, -1);
+ return NULL;
+ }
- forwards->topic_cached_forward = stasis_forward_all(
- ast_endpoint_topic_cached(endpoint), app->topic);
- if (!forwards->topic_cached_forward) {
- /* Half-subscribed is a bad thing */
- stasis_forward_cancel(forwards->topic_forward);
- forwards->topic_forward = NULL;
- return NULL;
+ ret |= stasis_message_router_add(app->endpoint_router,
+ ast_endpoint_state_type(), endpoint_state_cb, app);
+ ret |= stasis_message_router_add(app->endpoint_router,
+ ast_endpoint_contact_state_type(), endpoint_state_cb, app);
+
+ if (ret) {
+ ao2_ref(app->endpoint_router, -1);
+ app->endpoint_router = NULL;
+ ao2_ref(forwards, -1);
+ return NULL;
+ }
}
- ao2_ref(forwards, +1);
return forwards;
}
ast_assert(app->router == NULL);
ast_assert(app->bridge_router == NULL);
+ ast_assert(app->endpoint_router == NULL);
ao2_cleanup(app->topic);
app->topic = NULL;
}
}
-struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data)
+struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data, enum stasis_app_subscription_model subscription_model)
{
RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
size_t size;
size = sizeof(*app) + strlen(name) + 1;
app = ao2_alloc_options(size, app_dtor, AO2_ALLOC_OPT_LOCK_MUTEX);
-
if (!app) {
return NULL;
}
+ app->subscription_model = subscription_model;
app->forwards = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
AO2_CONTAINER_ALLOC_OPT_DUPS_OBJ_REJECT,
return app;
}
-struct stasis_topic *ast_app_get_topic(struct stasis_app *app) {
+struct stasis_topic *ast_app_get_topic(struct stasis_app *app)
+{
return app->topic;
}
app->router = NULL;
stasis_message_router_unsubscribe(app->bridge_router);
app->bridge_router = NULL;
+ stasis_message_router_unsubscribe(app->endpoint_router);
+ app->endpoint_router = NULL;
}
int app_is_active(struct stasis_app *app)
int app_subscribe_channel(struct stasis_app *app, struct ast_channel *chan)
{
+ struct app_forwards *forwards;
+ SCOPED_AO2LOCK(lock, app->forwards);
int res;
- if (!app || !chan) {
+ if (!app) {
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;
- }
+ /* If subscribed to all, don't subscribe again */
+ forwards = ao2_find(app->forwards, CHANNEL_ALL, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (forwards) {
+ ao2_ref(forwards, -1);
+ return 0;
+ }
- res = ao2_link_flags(app->forwards, forwards,
- OBJ_NOLOCK);
- if (!res) {
- return -1;
- }
+ forwards = ao2_find(app->forwards,
+ chan ? ast_channel_uniqueid(chan) : CHANNEL_ALL,
+ OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!forwards) {
+ /* Forwards not found, create one */
+ forwards = forwards_create_channel(app, chan);
+ if (!forwards) {
+ return -1;
}
- ++forwards->interested;
- ast_debug(3, "Channel '%s' is %d interested in %s\n", ast_channel_uniqueid(chan), forwards->interested, app->name);
- return 0;
+ res = ao2_link_flags(app->forwards, forwards,
+ OBJ_NOLOCK);
+ if (!res) {
+ ao2_ref(forwards, -1);
+ return -1;
+ }
}
+
+ ++forwards->interested;
+ ast_debug(3, "Channel '%s' is %d interested in %s\n",
+ chan ? ast_channel_uniqueid(chan) : "ALL",
+ forwards->interested,
+ app->name);
+
+ ao2_ref(forwards, -1);
+ return 0;
}
static int subscribe_channel(struct stasis_app *app, void *obj)
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
SCOPED_AO2LOCK(lock, app->forwards);
+ if (!id) {
+ if (!strcmp(kind, "bridge")) {
+ id = BRIDGE_ALL;
+ } else if (!strcmp(kind, "channel")) {
+ id = CHANNEL_ALL;
+ } else if (!strcmp(kind, "endpoint")) {
+ id = ENDPOINT_ALL;
+ } else {
+ ast_log(LOG_WARNING, "Unknown subscription kind '%s'\n", kind);
+ return -1;
+ }
+ }
+
forwards = ao2_find(app->forwards, id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!forwards) {
ast_debug(3, "App '%s' not subscribed to %s '%s'\n", app->name, kind, id);
int app_unsubscribe_channel(struct stasis_app *app, struct ast_channel *chan)
{
- if (!app || !chan) {
+ if (!app) {
return -1;
}
- return app_unsubscribe_channel_id(app, ast_channel_uniqueid(chan));
+ return app_unsubscribe_channel_id(app, chan ? ast_channel_uniqueid(chan) : CHANNEL_ALL);
}
int app_unsubscribe_channel_id(struct stasis_app *app, const char *channel_id)
{
- if (!app || !channel_id) {
+ if (!app) {
return -1;
}
int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(channel_id)) {
+ channel_id = CHANNEL_ALL;
+ }
forwards = ao2_find(app->forwards, channel_id, OBJ_SEARCH_KEY);
return forwards != NULL;
}
int app_subscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge)
{
- if (!app || !bridge) {
+ struct app_forwards *forwards;
+ SCOPED_AO2LOCK(lock, app->forwards);
+
+ if (!app) {
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 subscribed to all, don't subscribe again */
+ forwards = ao2_find(app->forwards, BRIDGE_ALL, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (forwards) {
+ ao2_ref(forwards, -1);
+ return 0;
+ }
+ forwards = ao2_find(app->forwards, bridge ? bridge->uniqueid : BRIDGE_ALL,
+ OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!forwards) {
+ /* Forwards not found, create one */
+ forwards = forwards_create_bridge(app, bridge);
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);
+ return -1;
}
-
- ++forwards->interested;
- ast_debug(3, "Bridge '%s' is %d interested in %s\n", bridge->uniqueid, forwards->interested, app->name);
- return 0;
+ ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK);
}
+
+ ++forwards->interested;
+ ast_debug(3, "Bridge '%s' is %d interested in %s\n",
+ bridge ? bridge->uniqueid : "ALL",
+ forwards->interested,
+ app->name);
+
+ ao2_ref(forwards, -1);
+ return 0;
}
static int subscribe_bridge(struct stasis_app *app, void *obj)
int app_unsubscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge)
{
- if (!app || !bridge) {
+ if (!app) {
return -1;
}
- return app_unsubscribe_bridge_id(app, bridge->uniqueid);
+ return app_unsubscribe_bridge_id(app, bridge ? bridge->uniqueid : BRIDGE_ALL);
}
int app_unsubscribe_bridge_id(struct stasis_app *app, const char *bridge_id)
{
- if (!app || !bridge_id) {
+ if (!app) {
return -1;
}
int app_is_subscribed_bridge_id(struct stasis_app *app, const char *bridge_id)
{
- RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
- forwards = ao2_find(app->forwards, bridge_id, OBJ_SEARCH_KEY);
- return forwards != NULL;
+ struct app_forwards *forwards;
+ SCOPED_AO2LOCK(lock, app->forwards);
+
+ forwards = ao2_find(app->forwards, BRIDGE_ALL, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (forwards) {
+ ao2_ref(forwards, -1);
+ return 1;
+ }
+
+ if (ast_strlen_zero(bridge_id)) {
+ bridge_id = BRIDGE_ALL;
+ }
+
+ forwards = ao2_find(app->forwards, bridge_id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (forwards) {
+ ao2_ref(forwards, -1);
+ return 1;
+ }
+
+ return 0;
}
static void *bridge_find(const struct stasis_app *app, const char *id)
int app_subscribe_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint)
{
- if (!app || !endpoint) {
+ struct app_forwards *forwards;
+ SCOPED_AO2LOCK(lock, app->forwards);
+
+ if (!app) {
return -1;
- } else {
- RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
- SCOPED_AO2LOCK(lock, app->forwards);
+ }
- forwards = ao2_find(app->forwards, ast_endpoint_get_id(endpoint),
- OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ /* If subscribed to all, don't subscribe again */
+ forwards = ao2_find(app->forwards, ENDPOINT_ALL, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (forwards) {
+ ao2_ref(forwards, -1);
+ return 0;
+ }
+ forwards = ao2_find(app->forwards,
+ endpoint ? ast_endpoint_get_id(endpoint) : ENDPOINT_ALL,
+ OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!forwards) {
+ /* Forwards not found, create one */
+ forwards = forwards_create_endpoint(app, endpoint);
if (!forwards) {
- /* Forwards not found, create one */
- forwards = forwards_create_endpoint(app, endpoint);
- if (!forwards) {
- return -1;
- }
- ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK);
-
- /* Subscribe for messages */
- messaging_app_subscribe_endpoint(app->name, endpoint, &message_received_handler, app);
+ return -1;
}
+ ao2_link_flags(app->forwards, forwards, OBJ_NOLOCK);
- ++forwards->interested;
- ast_debug(3, "Endpoint '%s' is %d interested in %s\n", ast_endpoint_get_id(endpoint), forwards->interested, app->name);
- return 0;
+ /* Subscribe for messages */
+ messaging_app_subscribe_endpoint(app->name, endpoint, &message_received_handler, app);
}
+
+ ++forwards->interested;
+ ast_debug(3, "Endpoint '%s' is %d interested in %s\n",
+ endpoint ? ast_endpoint_get_id(endpoint) : "ALL",
+ forwards->interested,
+ app->name);
+
+ ao2_ref(forwards, -1);
+ return 0;
}
static int subscribe_endpoint(struct stasis_app *app, void *obj)
int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id)
{
- if (!app || !endpoint_id) {
+ if (!app) {
return -1;
}
int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(endpoint_id)) {
+ endpoint_id = ENDPOINT_ALL;
+ }
forwards = ao2_find(app->forwards, endpoint_id, OBJ_SEARCH_KEY);
return forwards != NULL;
}
*/
struct stasis_app;
+enum stasis_app_subscription_model {
+ /*
+ * \brief An application must manually subscribe to each
+ * resource that it cares about. This is the default approach.
+ */
+ STASIS_APP_SUBSCRIBE_MANUAL,
+ /*
+ * \brief An application is automatically subscribed to all
+ * resources in Asterisk, even if it does not control them.
+ */
+ STASIS_APP_SUBSCRIBE_ALL
+};
+
/*!
* \brief Create a res_stasis application.
*
* \return New \c res_stasis application.
* \return \c NULL on error.
*/
-struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data);
+struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data, enum stasis_app_subscription_model subscription_model);
/*!
* \brief Tears down an application.
#include "messaging.h"
/*!
+ * \brief Subscription to all technologies
+ */
+#define TECH_WILDCARD "__AST_ALL_TECH"
+
+/*!
* \brief Number of buckets for the \ref endpoint_subscriptions container
*/
#define ENDPOINTS_NUM_BUCKETS 127
for (i = 0; i < AST_VECTOR_SIZE(&tech_subscriptions); i++) {
sub = AST_VECTOR_GET(&tech_subscriptions, i);
- if (sub && (!strncasecmp(sub->token, buf, strlen(sub->token))
- || !strncasecmp(sub->token, buf, strlen(sub->token)))) {
+ if (!sub) {
+ continue;
+ }
+
+ if (!strcmp(sub->token, TECH_WILDCARD)
+ || !strncasecmp(sub->token, buf, strlen(sub->token))
+ || !strncasecmp(sub->token, buf, strlen(sub->token))) {
ast_rwlock_unlock(&tech_subscriptions_lock);
- sub = NULL; /* No ref bump! */
goto match;
}
sub = ao2_find(endpoint_subscriptions, buf, OBJ_SEARCH_KEY);
if (sub) {
+ ao2_ref(sub, -1);
goto match;
}
return 0;
match:
- ao2_cleanup(sub);
return 1;
}
continue;
}
- if (!strncasecmp(sub->token, buf, strlen(sub->token))) {
+ if (!strcmp(sub->token, TECH_WILDCARD)
+ || !strncasecmp(sub->token, buf, strlen(sub->token))) {
ast_rwlock_unlock(&tech_subscriptions_lock);
ao2_bump(sub);
endpoint_name = buf;
{
struct message_subscription *sub = NULL;
- if (!ast_strlen_zero(ast_endpoint_get_resource(endpoint))) {
+ if (endpoint && !ast_strlen_zero(ast_endpoint_get_resource(endpoint))) {
sub = ao2_find(endpoint_subscriptions, endpoint, OBJ_SEARCH_KEY);
} else {
int i;
for (i = 0; i < AST_VECTOR_SIZE(&tech_subscriptions); i++) {
sub = AST_VECTOR_GET(&tech_subscriptions, i);
- if (sub && !strcmp(sub->token, ast_endpoint_get_tech(endpoint))) {
+ if (sub && !strcmp(sub->token, endpoint ? ast_endpoint_get_tech(endpoint) : TECH_WILDCARD)) {
ao2_bump(sub);
break;
}
RAII_VAR(struct ast_endpoint *, endpoint, NULL, ao2_cleanup);
endpoint = ast_endpoint_find_by_id(endpoint_id);
- if (!endpoint) {
- return;
- }
-
sub = get_subscription(endpoint);
if (!sub) {
return;
AST_VECTOR_REMOVE_CMP_UNORDERED(&sub->applications, app_name, application_tuple_cmp, ao2_cleanup);
if (AST_VECTOR_SIZE(&sub->applications) == 0) {
- if (!ast_strlen_zero(ast_endpoint_get_resource(endpoint))) {
+ if (endpoint && !ast_strlen_zero(ast_endpoint_get_resource(endpoint))) {
ao2_unlink(endpoint_subscriptions, sub);
} else {
ast_rwlock_wrlock(&tech_subscriptions_lock);
- AST_VECTOR_REMOVE_CMP_UNORDERED(&tech_subscriptions, ast_endpoint_get_id(endpoint),
+ AST_VECTOR_REMOVE_CMP_UNORDERED(&tech_subscriptions, endpoint ? ast_endpoint_get_id(endpoint) : TECH_WILDCARD,
messaging_subscription_cmp, AST_VECTOR_ELEM_CLEANUP_NOOP);
ast_rwlock_unlock(&tech_subscriptions_lock);
}
ao2_unlock(sub);
ao2_ref(sub, -1);
- ast_debug(3, "App '%s' unsubscribed to messages from endpoint '%s'\n", app_name, ast_endpoint_get_id(endpoint));
+ ast_debug(3, "App '%s' unsubscribed to messages from endpoint '%s'\n", app_name, endpoint ? ast_endpoint_get_id(endpoint) : "-- ALL --");
ast_test_suite_event_notify("StasisMessagingSubscription", "SubState: Unsubscribed\r\nAppName: %s\r\nToken: %s\r\n",
- app_name, ast_endpoint_get_id(endpoint));
+ app_name, endpoint ? ast_endpoint_get_id(endpoint) : "ALL");
}
static struct message_subscription *get_or_create_subscription(struct ast_endpoint *endpoint)
return sub;
}
- sub = message_subscription_alloc(ast_endpoint_get_id(endpoint));
+ sub = message_subscription_alloc(endpoint ? ast_endpoint_get_id(endpoint) : TECH_WILDCARD);
if (!sub) {
return NULL;
}
- if (!ast_strlen_zero(ast_endpoint_get_resource(endpoint))) {
+ if (endpoint && !ast_strlen_zero(ast_endpoint_get_resource(endpoint))) {
ao2_link(endpoint_subscriptions, sub);
} else {
ast_rwlock_wrlock(&tech_subscriptions_lock);
AST_VECTOR_APPEND(&sub->applications, tuple);
ao2_unlock(sub);
- ast_debug(3, "App '%s' subscribed to messages from endpoint '%s'\n", app_name, ast_endpoint_get_id(endpoint));
+ ast_debug(3, "App '%s' subscribed to messages from endpoint '%s'\n", app_name, endpoint ? ast_endpoint_get_id(endpoint) : "-- ALL --");
ast_test_suite_event_notify("StasisMessagingSubscription", "SubState: Subscribed\r\nAppName: %s\r\nToken: %s\r\n",
- app_name, ast_endpoint_get_id(endpoint));
+ app_name, endpoint ? ast_endpoint_get_id(endpoint) : "ALL");
return 0;
}
discriminator = ast_json_string_get(ast_json_object_get(json, "{{discriminator.name}}"));
if (!discriminator) {
- ast_log(LOG_ERROR, "ARI {{id}} missing required field {{discriminator.name}}");
+ ast_log(LOG_ERROR, "ARI {{id}} missing required field {{discriminator.name}}\n");
return 0;
}
"required": true,
"allowMultiple": true,
"dataType": "string"
+ },
+ {
+ "name": "subscribeAll",
+ "description": "Subscribe to all Asterisk events. If provided, the applications listed will be subscribed to all events, effectively disabling the application specific subscriptions. Default is 'false'.",
+ "paramType": "query",
+ "required": false,
+ "allowMultiple": false,
+ "dataType": "boolean"
}
]
}
"ChannelTalkingFinished",
"ChannelHold",
"ChannelUnhold",
+ "ContactStatusChange",
"EndpointStateChange",
"Dial",
"StasisEnd",
"StasisStart",
"TextMessageReceived",
- "ChannelConnectedLine"
+ "ChannelConnectedLine",
+ "PeerStatusChange"
]
},
+ "ContactInfo": {
+ "id": "ContactInfo",
+ "description": "Detailed information about a contact on an endpoint.",
+ "properties": {
+ "uri": {
+ "type": "string",
+ "description": "The location of the contact.",
+ "required": true
+ },
+ "contact_status": {
+ "type": "string",
+ "description": "The current status of the contact.",
+ "required": true,
+ "allowableValues": {
+ "valueType": "LIST",
+ "values": [
+ "Unreachable",
+ "Reachable",
+ "Unknown",
+ "Created",
+ "Removed"
+ ]
+ }
+ },
+ "aor": {
+ "type": "string",
+ "description": "The Address of Record this contact belongs to.",
+ "required": true
+ },
+ "roundtrip_usec": {
+ "type": "string",
+ "description": "Current round trip time, in microseconds, for the contact.",
+ "required": false
+ }
+ }
+ },
+ "Peer": {
+ "id": "Peer",
+ "description": "Detailed information about a remote peer that communicates with Asterisk.",
+ "properties": {
+ "peer_status": {
+ "type": "string",
+ "description": "The current state of the peer. Note that the values of the status are dependent on the underlying peer technology.",
+ "required": true
+ },
+ "cause": {
+ "type": "string",
+ "description": "An optional reason associated with the change in peer_status.",
+ "required": false
+ },
+ "address": {
+ "type": "string",
+ "description": "The IP address of the peer.",
+ "required": false
+ },
+ "port": {
+ "type": "string",
+ "description": "The port of the peer.",
+ "required": false
+ },
+ "time": {
+ "type": "string",
+ "description": "The last known time the peer was contacted.",
+ "required": false
+ }
+ }
+ },
"DeviceStateChanged": {
"id": "DeviceStateChanged",
"description": "Notification that a device state has changed.",
}
}
},
+ "ContactStatusChange": {
+ "id": "ContactStatusChange",
+ "description": "The state of a contact on an endpoint has changed.",
+ "properties": {
+ "endpoint": {
+ "required": true,
+ "type": "Endpoint"
+ },
+ "contact_info": {
+ "required": true,
+ "type": "ContactInfo"
+ }
+ }
+ },
+ "PeerStatusChange": {
+ "id": "PeerStatusChange",
+ "description": "The state of a peer associated with an endpoint has changed.",
+ "properties": {
+ "endpoint": {
+ "required": true,
+ "type": "Endpoint"
+ },
+ "peer": {
+ "required": true,
+ "type": "Peer"
+ }
+ }
+ },
"EndpointStateChange": {
"id": "EndpointStateChange",
"description": "Endpoint state changed.",