summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES3
-rw-r--r--apps/app_echo.c17
-rw-r--r--channels/chan_pjsip.c12
-rw-r--r--channels/chan_sip.c2
-rw-r--r--channels/pjsip/cli_commands.c467
-rw-r--r--channels/pjsip/include/cli_functions.h45
-rw-r--r--configs/samples/pjsip.conf.sample30
-rw-r--r--contrib/ast-db-manage/config/versions/1c688d9a003c_pjsip_voicemail_extension.py31
-rw-r--r--contrib/ast-db-manage/config/versions/5813202e92be_add_contact_expiration_check_interval_.py21
-rwxr-xr-xcontrib/scripts/install_prereq9
-rw-r--r--include/asterisk/config.h81
-rw-r--r--include/asterisk/res_pjsip.h25
-rw-r--r--include/asterisk/res_pjsip_body_generator_types.h2
-rw-r--r--include/asterisk/res_pjsip_pubsub.h20
-rw-r--r--include/asterisk/rtp_engine.h8
-rw-r--r--include/asterisk/sorcery.h24
-rw-r--r--include/asterisk/stasis_app.h10
-rw-r--r--include/asterisk/strings.h30
-rw-r--r--main/Makefile20
-rw-r--r--main/config.c90
-rw-r--r--main/core_unreal.c2
-rw-r--r--main/strings.c127
-rw-r--r--main/utils.c2
-rw-r--r--res/ari/resource_bridges.c9
-rw-r--r--res/res_pjsip.c25
-rw-r--r--res/res_pjsip/config_global.c63
-rw-r--r--res/res_pjsip/location.c20
-rw-r--r--res/res_pjsip/pjsip_cli.c2
-rw-r--r--res/res_pjsip/pjsip_configuration.c281
-rw-r--r--res/res_pjsip/pjsip_options.c128
-rw-r--r--res/res_pjsip_mwi.c196
-rw-r--r--res/res_pjsip_mwi_body_generator.c5
-rw-r--r--res/res_pjsip_pubsub.c11
-rw-r--r--res/res_pjsip_pubsub.exports.in40
-rw-r--r--res/res_pjsip_registrar_expire.c277
-rw-r--r--res/res_rtp_asterisk.c22
-rw-r--r--res/res_sorcery_astdb.c91
-rw-r--r--res/res_sorcery_config.c9
-rw-r--r--res/res_sorcery_memory.c4
-rw-r--r--res/res_sorcery_memory_cache.c3
-rw-r--r--res/res_sorcery_realtime.c111
-rw-r--r--res/res_stasis.c22
-rw-r--r--res/res_stasis_playback.c2
-rw-r--r--res/res_stasis_recording.c20
-rw-r--r--res/stasis/control.c75
-rw-r--r--res/stasis/control.h18
-rw-r--r--tests/test_config.c62
-rw-r--r--tests/test_sorcery_astdb.c4
-rw-r--r--tests/test_sorcery_realtime.c212
-rw-r--r--tests/test_strings.c64
50 files changed, 1947 insertions, 907 deletions
diff --git a/CHANGES b/CHANGES
index 4b607b7a2..f44df3284 100644
--- a/CHANGES
+++ b/CHANGES
@@ -263,6 +263,9 @@ res_parking:
for these variables. The indefinite inheritance is also recommended
for the PARKINGEXTEN variable.
+chan_pjsip
+------------------
+ * Added 'pjsip show channelstats' CLI command.
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.7.0 to Asterisk 13.8.0 ------------
diff --git a/apps/app_echo.c b/apps/app_echo.c
index 2ec9d709b..972e59f0a 100644
--- a/apps/app_echo.c
+++ b/apps/app_echo.c
@@ -58,6 +58,7 @@ static const char app[] = "Echo";
static int echo_exec(struct ast_channel *chan, const char *data)
{
int res = -1;
+ int fir_sent = 0;
while (ast_waitfor(chan, -1) > -1) {
struct ast_frame *f = ast_read(chan);
@@ -66,6 +67,22 @@ static int echo_exec(struct ast_channel *chan, const char *data)
}
f->delivery.tv_sec = 0;
f->delivery.tv_usec = 0;
+ if (f->frametype == AST_FRAME_CONTROL
+ && f->subclass.integer == AST_CONTROL_VIDUPDATE) {
+ if (ast_write(chan, f) < 0) {
+ ast_frfree(f);
+ goto end;
+ }
+ fir_sent = 1;
+ }
+ if (!fir_sent && f->frametype == AST_FRAME_VIDEO) {
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = AST_CONTROL_VIDUPDATE,
+ };
+ ast_write(chan, &frame);
+ fir_sent = 1;
+ }
if (f->frametype != AST_FRAME_CONTROL
&& f->frametype != AST_FRAME_MODEM
&& f->frametype != AST_FRAME_NULL
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index cd55400c3..370075d2b 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -69,6 +69,7 @@ ASTERISK_REGISTER_FILE()
#include "pjsip/include/chan_pjsip.h"
#include "pjsip/include/dialplan_functions.h"
+#include "pjsip/include/cli_functions.h"
AST_THREADSTORAGE(uniqueid_threadbuf);
#define UNIQUEID_BUFSIZE 256
@@ -2468,6 +2469,15 @@ static int load_module(void)
goto end;
}
+ if (pjsip_channel_cli_register()) {
+ ast_log(LOG_ERROR, "Unable to register PJSIP Channel CLI\n");
+ ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
+ ast_sip_session_unregister_supplement(&pbx_start_supplement);
+ ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
+ ast_sip_session_unregister_supplement(&call_pickup_supplement);
+ goto end;
+ }
+
/* since endpoints are loaded before the channel driver their device
states get set to 'invalid', so they need to be updated */
if ((endpoints = ast_sip_get_endpoints())) {
@@ -2494,6 +2504,8 @@ static int unload_module(void)
ao2_cleanup(pjsip_uids_onhold);
pjsip_uids_onhold = NULL;
+ pjsip_channel_cli_unregister();
+
ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
ast_sip_session_unregister_supplement(&pbx_start_supplement);
ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 09ab1a196..ffc2084a1 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -13531,7 +13531,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
}
/* Finally our remaining audio/video codecs */
- for (x = 0; ast_test_flag(&p->flags[0], SIP_OUTGOING) && x < ast_format_cap_count(p->caps); x++) {
+ for (x = 0; p->outgoing_call && x < ast_format_cap_count(p->caps); x++) {
tmp_fmt = ast_format_cap_get_format(p->caps, x);
if (ast_format_cap_iscompatible_format(alreadysent, tmp_fmt) != AST_FORMAT_CMP_NOT_EQUAL) {
diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c
new file mode 100644
index 000000000..8d99379ff
--- /dev/null
+++ b/channels/pjsip/cli_commands.c
@@ -0,0 +1,467 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \author \verbatim George Joseph <george.joseph@fairview5.com> \endverbatim
+ *
+ * \ingroup functions
+ *
+ * \brief PJSIP channel CLI functions
+ */
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include <pjsip.h>
+#include <pjlib.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/astobj2.h"
+#include "asterisk/channel.h"
+#include "asterisk/format.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_session.h"
+#include "asterisk/res_pjsip_cli.h"
+#include "asterisk/stasis.h"
+#include "asterisk/time.h"
+#include "include/chan_pjsip.h"
+#include "include/cli_functions.h"
+
+
+static int cli_channel_iterate(void *endpoint, ao2_callback_fn callback, void *arg)
+{
+ return ast_sip_for_each_channel(endpoint, callback, arg);
+}
+
+static int cli_channelstats_iterate(void *endpoint, ao2_callback_fn callback, void *arg)
+{
+ return ast_sip_for_each_channel(endpoint, callback, arg);
+}
+
+static int cli_channel_sort(const void *obj, const void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = right_obj->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(left_obj->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(left_obj->name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_channelstats_sort(const void *obj, const void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ cmp = strcmp(left_obj->bridgeid, right_obj->bridgeid);
+ if (cmp) {
+ return cmp;
+ }
+ right_key = right_obj->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(left_obj->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(left_obj->name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_channel_compare(void *obj, void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = right_obj->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ if (strcmp(left_obj->name, right_key) == 0) {
+ cmp = CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_channelstats_compare(void *obj, void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ if (strcmp(left_obj->bridgeid, right_obj->bridgeid) == 0
+ && strcmp(left_obj->name, right_obj->name) == 0) {
+ return CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_KEY:
+ if (strcmp(left_obj->name, right_key) == 0) {
+ cmp = CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_message_to_snapshot(void *obj, void *arg, int flags)
+{
+ struct stasis_message *message = obj;
+ struct ao2_container *snapshots = arg;
+ struct ast_channel_snapshot *snapshot = stasis_message_data(message);
+
+ if (!strcmp(snapshot->type, "PJSIP")) {
+ ao2_link(snapshots, snapshot);
+ return CMP_MATCH;
+ }
+
+ return 0;
+}
+
+static int cli_filter_channels(void *obj, void *arg, int flags)
+{
+ struct ast_channel_snapshot *channel = obj;
+ regex_t *regexbuf = arg;
+
+ if (!regexec(regexbuf, channel->name, 0, NULL, 0)
+ || !regexec(regexbuf, channel->appl, 0, NULL, 0)) {
+ return 0;
+ }
+
+ return CMP_MATCH;
+}
+
+static struct ao2_container *get_container(const char *regex, ao2_sort_fn sort_fn, ao2_callback_fn compare_fn)
+{
+ struct ao2_container *child_container;
+ regex_t regexbuf;
+ RAII_VAR(struct ao2_container *, parent_container,
+ stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type()), ao2_cleanup);
+
+ if (!parent_container) {
+ return NULL;
+ }
+
+ child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, sort_fn, compare_fn);
+ if (!child_container) {
+ return NULL;
+ }
+
+ ao2_callback(parent_container, OBJ_MULTIPLE | OBJ_NODATA, cli_message_to_snapshot, child_container);
+
+ if (!ast_strlen_zero(regex)) {
+ if (regcomp(&regexbuf, regex, REG_EXTENDED | REG_NOSUB)) {
+ ao2_ref(child_container, -1);
+ return NULL;
+ }
+ ao2_callback(child_container, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, cli_filter_channels, &regexbuf);
+ regfree(&regexbuf);
+ }
+
+ return child_container;
+}
+
+static struct ao2_container *cli_channel_get_container(const char *regex)
+{
+ return get_container(regex, cli_channel_sort, cli_channel_compare);
+}
+
+static struct ao2_container *cli_channelstats_get_container(const char *regex)
+{
+ return get_container(regex, cli_channelstats_sort, cli_channelstats_compare);
+}
+
+static const char *cli_channel_get_id(const void *obj)
+{
+ const struct ast_channel_snapshot *snapshot = obj;
+
+ return snapshot->name;
+}
+
+static void *cli_channel_retrieve_by_id(const char *id)
+{
+ return ast_channel_snapshot_get_latest_by_name(id);
+}
+
+static int cli_channel_print_header(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+ int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ int filler = CLI_LAST_TABSTOP - indent - 13;
+
+ ast_assert(context->output_buffer != NULL);
+
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: <ChannelId%*.*s> <State.....> <Time.....>\n",
+ indent, "Channel", filler, filler, CLI_HEADER_FILLER);
+ if (context->recurse) {
+ context->indent_level++;
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ filler = CLI_LAST_TABSTOP - indent - 38;
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: <DialedExten%*.*s> CLCID: <ConnectedLineCID.......>\n",
+ indent, "Exten", filler, filler, CLI_HEADER_FILLER);
+ context->indent_level--;
+ }
+
+ return 0;
+}
+
+static int cli_channel_print_body(void *obj, void *arg, int flags)
+{
+ const struct ast_channel_snapshot *snapshot = obj;
+ struct ast_sip_cli_context *context = arg;
+ char *print_name = NULL;
+ int print_name_len;
+ int indent;
+ int flexwidth;
+ char *print_time = alloca(32);
+
+ ast_assert(context->output_buffer != NULL);
+
+ print_name_len = strlen(snapshot->name) + strlen(snapshot->appl) + 2;
+ print_name = alloca(print_name_len);
+
+ /* Append the application */
+ snprintf(print_name, print_name_len, "%s/%s", snapshot->name, snapshot->appl);
+
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ flexwidth = CLI_LAST_TABSTOP - indent;
+
+ ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
+
+ ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %-12.12s %-11.11s\n",
+ CLI_INDENT_TO_SPACES(context->indent_level), "Channel",
+ flexwidth, flexwidth,
+ print_name,
+ ast_state2str(snapshot->state),
+ print_time);
+
+ if (context->recurse) {
+ context->indent_level++;
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ flexwidth = CLI_LAST_TABSTOP - indent - 25;
+
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: %-*.*s CLCID: \"%s\" <%s>\n",
+ indent, "Exten",
+ flexwidth, flexwidth,
+ snapshot->exten,
+ snapshot->connected_name,
+ snapshot->connected_number
+ );
+ context->indent_level--;
+ if (context->indent_level == 0) {
+ ast_str_append(&context->output_buffer, 0, "\n");
+ }
+ }
+
+ return 0;
+}
+
+static int cli_channelstats_print_header(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+
+ ast_assert(context->output_buffer != NULL);
+
+ ast_str_append(&context->output_buffer, 0,
+ " ...........Receive......... .........Transmit..........\n"
+ " BridgeId ChannelId ........ UpTime.. Codec. Count Lost Pct Jitter Count Lost Pct Jitter RTT....\n"
+ " =================");
+
+ return 0;
+}
+
+static int cli_channelstats_print_body(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+ const struct ast_channel_snapshot *snapshot = obj;
+ struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);
+ struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;
+ struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL;
+ struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL;
+ struct ast_rtp_codecs *codecs = media && media->rtp ? ast_rtp_instance_get_codecs(media->rtp) : NULL;
+ struct ast_format *format = codecs ? ast_rtp_codecs_get_payload_format(codecs, 0) : NULL;
+ struct ast_rtp_instance_stats stats;
+ char *print_name = NULL;
+ char *print_time = alloca(32);
+
+ ast_assert(context->output_buffer != NULL);
+
+ if (!media || !media->rtp) {
+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ ao2_cleanup(channel);
+ return -1;
+ }
+
+ print_name = ast_strdupa(snapshot->name);
+ /* Skip the PJSIP/. We know what channel type it is and we need the space. */
+ print_name += 6;
+
+ ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
+
+ if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
+ ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);
+ } else {
+ ast_str_append(&context->output_buffer, 0,
+ " %8.8s %-18.18s %-8.8s %-6.6s %6u%s %6u%s %3u %7.3f %6u%s %6u%s %3u %7.3f %7.3f\n",
+ snapshot->bridgeid,
+ print_name,
+ print_time,
+ format ? ast_format_get_name(format) : "",
+ stats.rxcount > 100000 ? stats.rxcount / 1000 : stats.rxcount,
+ stats.rxcount > 100000 ? "K": " ",
+ stats.rxploss > 100000 ? stats.rxploss / 1000 : stats.rxploss,
+ stats.rxploss > 100000 ? "K": " ",
+ stats.rxcount ? (stats.rxploss * 100) / stats.rxcount : 0,
+ MIN(stats.rxjitter, 999.999),
+ stats.txcount > 100000 ? stats.txcount / 1000 : stats.txcount,
+ stats.txcount > 100000 ? "K": " ",
+ stats.txploss > 100000 ? stats.txploss / 1000 : stats.txploss,
+ stats.txploss > 100000 ? "K": " ",
+ stats.txcount ? (stats.txploss * 100) / stats.txcount : 0,
+ MIN(stats.txjitter, 999.999),
+ MIN(stats.normdevrtt, 999.999)
+ );
+ }
+
+ ao2_cleanup(format);
+ ao2_cleanup(channel);
+
+ return 0;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Channels",
+ .command = "pjsip list channels",
+ .usage = "Usage: pjsip list channels [ like <pattern> ]\n"
+ " List the active PJSIP channels\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channels",
+ .command = "pjsip show channels",
+ .usage = "Usage: pjsip show channels [ like <pattern> ]\n"
+ " List(detailed) the active PJSIP channels\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channel",
+ .command = "pjsip show channel",
+ .usage = "Usage: pjsip show channel\n"
+ " List(detailed) the active PJSIP channel\n"),
+
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channel Stats",
+ .command = "pjsip show channelstats",
+ .usage = "Usage: pjsip show channelstats [ like <pattern> ]\n"
+ " List(detailed) the active PJSIP channel stats\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
+};
+
+struct ast_sip_cli_formatter_entry *channelstats_formatter;
+struct ast_sip_cli_formatter_entry *channel_formatter;
+
+int pjsip_channel_cli_register(void)
+{
+ channel_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
+ if (!channel_formatter) {
+ ast_log(LOG_ERROR, "Unable to allocate memory for channel_formatter\n");
+ return -1;
+ }
+ channel_formatter->name = "channel";
+ channel_formatter->print_header = cli_channel_print_header;
+ channel_formatter->print_body = cli_channel_print_body;
+ channel_formatter->get_container = cli_channel_get_container;
+ channel_formatter->iterate = cli_channel_iterate;
+ channel_formatter->retrieve_by_id = cli_channel_retrieve_by_id;
+ channel_formatter->get_id = cli_channel_get_id;
+
+ channelstats_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
+ if (!channelstats_formatter) {
+ ao2_ref(channel_formatter, -1);
+ ast_log(LOG_ERROR, "Unable to allocate memory for channelstats_formatter\n");
+ return -1;
+ }
+ channelstats_formatter->name = "channelstat";
+ channelstats_formatter->print_header = cli_channelstats_print_header;
+ channelstats_formatter->print_body = cli_channelstats_print_body;
+ channelstats_formatter->get_container = cli_channelstats_get_container;
+ channelstats_formatter->iterate = cli_channelstats_iterate;
+ channelstats_formatter->retrieve_by_id = cli_channel_retrieve_by_id;
+ channelstats_formatter->get_id = cli_channel_get_id;
+
+ ast_sip_register_cli_formatter(channel_formatter);
+ ast_sip_register_cli_formatter(channelstats_formatter);
+ ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
+ return 0;
+}
+
+void pjsip_channel_cli_unregister(void)
+{
+ ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+ ast_sip_unregister_cli_formatter(channel_formatter);
+ ast_sip_unregister_cli_formatter(channelstats_formatter);
+}
diff --git a/channels/pjsip/include/cli_functions.h b/channels/pjsip/include/cli_functions.h
new file mode 100644
index 000000000..87200774e
--- /dev/null
+++ b/channels/pjsip/include/cli_functions.h
@@ -0,0 +1,45 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \author \verbatim George Joseph <george.joseph@fairview5.com> \endverbatim
+ *
+ * \brief PJSIP CLI functions header file
+ */
+
+#ifndef _PJSIP_CLI_FUNCTIONS
+#define _PJSIP_CLI_FUNCTIONS
+
+
+/*!
+ * \brief Registers the channel cli commands
+ * \since 13.9.0
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int pjsip_channel_cli_register(void);
+
+/*!
+ * \brief Unregisters the channel cli commands
+ * \since 13.9.0
+ */
+void pjsip_channel_cli_unregister(void);
+
+
+#endif /* _PJSIP_CLI_FUNCTIONS */
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 2d127a1dc..94d179208 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -624,7 +624,14 @@
; "username")
;redirect_method=user ; How redirects received from an endpoint are handled
; (default: "user")
-;mailboxes= ; Mailbox es to be associated with (default: "")
+;mailboxes= ; NOTIFY the endpoint when state changes for any of the specified mailboxes.
+ ; Asterisk will send unsolicited MWI NOTIFY messages to the endpoint when state
+ ; changes happen for any of the specified mailboxes. (default: "")
+;voicemail_extension= ; The voicemail extension to send in the NOTIFY Message-Account header
+ ; (default: global/default_voicemail_extension)
+;mwi_subscribe_replaces_unsolicited=no
+ ; An MWI subscribe will replace unsoliticed NOTIFYs
+ ; (default: "no")
;moh_suggest=default ; Default Music On Hold class (default: "default")
;moh_passthrough=yes ; Pass Music On Hold through using SIP re-invites with sendonly
; when placing on hold and sendrecv when taking off hold
@@ -832,7 +839,11 @@
;default_expiration=3600 ; Default expiration time in seconds for
; contacts that are dynamically bound to an AoR
; (default: "3600")
-;mailboxes= ; Mailbox es to be associated with (default: "")
+;mailboxes= ; Allow subscriptions for the specified mailbox(es)
+ ; This option applies when an external entity subscribes to an AoR
+ ; for Message Waiting Indications. (default: "")
+;voicemail_extension= ; The voicemail extension to send in the NOTIFY Message-Account header
+ ; (default: global/default_voicemail_extension)
;maximum_expiration=7200 ; Maximum time to keep an AoR (default: "7200")
;max_contacts=0 ; Maximum number of contacts that can bind to an AoR (default:
; "0")
@@ -894,6 +905,8 @@
;keep_alive_interval=20 ; The interval (in seconds) at which to send keepalive
; messages on all active connection-oriented transports
; (default: "0")
+;contact_expiration_check_interval=30
+ ; The interval (in seconds) to check for expired contacts.
;endpoint_identifier_order=ip,username,anonymous
; The order by which endpoint identifiers are given priority.
; Identifier names are derived from res_pjsip_endpoint_identifier_*
@@ -902,10 +915,15 @@
; startup that qualifies should be attempted on all
; contacts. If greater than the qualify_frequency
; for an aor, qualify_frequency will be used instead.
-; If regcontext is specified, Asterisk will dynamically create and destroy a
-; NoOp priority 1 extension for a given endpoint who registers or unregisters
-; with us. The extension added is the name of the endpoint.
-;regcontext=sipregistrations
+;regcontext=sipregistrations ; If regcontext is specified, Asterisk will dynamically
+ ; create and destroy a NoOp priority 1 extension for a
+ ; given endpoint who registers or unregisters with us.
+ ; The extension added is the name of the endpoint.
+;default_voicemail_extension=asterisk
+ ; The voicemail extension to send in the NOTIFY Message-Account header
+ ; if not set on endpoint or aor.
+ ; (default: "")
+
; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl
;==========================ACL SECTION OPTIONS=========================
diff --git a/contrib/ast-db-manage/config/versions/1c688d9a003c_pjsip_voicemail_extension.py b/contrib/ast-db-manage/config/versions/1c688d9a003c_pjsip_voicemail_extension.py
new file mode 100644
index 000000000..781dca703
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/1c688d9a003c_pjsip_voicemail_extension.py
@@ -0,0 +1,31 @@
+"""pjsip voicemail extension
+
+Revision ID: 1c688d9a003c
+Revises: 5813202e92be
+Create Date: 2016-03-24 22:31:45.537895
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '1c688d9a003c'
+down_revision = '5813202e92be'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_globals', sa.Column('default_voicemail_extension', sa.String(40)))
+ op.add_column('ps_aors', sa.Column('voicemail_extension', sa.String(40)))
+ op.add_column('ps_endpoints', sa.Column('voicemail_extension', sa.String(40)))
+ op.add_column('ps_endpoints', sa.Column('mwi_subscribe_replaces_unsolicited', sa.Integer))
+
+
+def downgrade():
+ with op.batch_alter_table('ps_globals') as batch_op:
+ batch_op.drop_column('default_voicemail_extension')
+ with op.batch_alter_table('ps_aors') as batch_op:
+ batch_op.drop_column('voicemail_extension')
+ with op.batch_alter_table('ps_endpoints') as batch_op:
+ batch_op.drop_column('voicemail_extension')
+ batch_op.drop_column('mwi_subscribe_replaces_unsolicited')
diff --git a/contrib/ast-db-manage/config/versions/5813202e92be_add_contact_expiration_check_interval_.py b/contrib/ast-db-manage/config/versions/5813202e92be_add_contact_expiration_check_interval_.py
new file mode 100644
index 000000000..2c61f2b9d
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/5813202e92be_add_contact_expiration_check_interval_.py
@@ -0,0 +1,21 @@
+"""Add contact_expiration_check_interval to ps_globals
+
+Revision ID: 5813202e92be
+Revises: 3bcc0b5bc2c9
+Create Date: 2016-03-08 21:52:21.372310
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '5813202e92be'
+down_revision = '3bcc0b5bc2c9'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+ op.add_column('ps_globals', sa.Column('contact_expiration_check_interval', sa.Integer))
+
+def downgrade():
+ with op.batch_alter_table('ps_globals') as batch_op:
+ batch_op.drop_column('contact_expiration_check_interval')
diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq
index afad1f719..1682558ac 100755
--- a/contrib/scripts/install_prereq
+++ b/contrib/scripts/install_prereq
@@ -66,13 +66,12 @@ in_test_mode() {
}
check_installed_debs() {
- for pack in "$@"
- do
- tocheck="${tocheck} ^${pack}$"
+ for pack in "$@" ; do
+ tocheck="${tocheck} ^${pack}$ ~P^${pack}$"
done
pkgs=$(aptitude -F '%c %p' search ${tocheck} 2>/dev/null | awk '/^p/{print $2}')
- if ! [ ${#pkgs} -eq 0 ]; then
- echo $pkgs | grep -v ':i386$'
+ if [ ${#pkgs} -ne 0 ]; then
+ echo $pkgs | sed -r -e "s/ ?[^ :]+:i386//g"
fi
}
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index 9a899d8d0..287635a8e 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -307,7 +307,7 @@ const char *ast_variable_retrieve(struct ast_config *config,
const char *category, const char *variable);
/*!
- * \brief Gets a variable from a specific category structure
+ * \brief Gets a variable value from a specific category structure by name
*
* \param category category structure under which the variable lies
* \param variable which variable you wish to get the data for
@@ -321,7 +321,7 @@ const char *ast_variable_retrieve(struct ast_config *config,
const char *ast_variable_find(const struct ast_category *category, const char *variable);
/*!
- * \brief Gets a variable from a variable list
+ * \brief Gets the value of a variable from a variable list by name
*
* \param list variable list to search
* \param variable which variable you wish to get the data for
@@ -335,7 +335,7 @@ const char *ast_variable_find(const struct ast_category *category, const char *v
const char *ast_variable_find_in_list(const struct ast_variable *list, const char *variable);
/*!
- * \brief Gets the LAST occurrence of a variable from a variable list
+ * \brief Gets the value of the LAST occurrence of a variable from a variable list
*
* \param list The ast_variable list to search
* \param variable The name of the ast_variable you wish to fetch data for
@@ -352,6 +352,21 @@ const char *ast_variable_find_in_list(const struct ast_variable *list, const cha
const char *ast_variable_find_last_in_list(const struct ast_variable *list, const char *variable);
/*!
+ * \brief Gets a variable from a variable list by name
+ * \since 13.9.0
+ *
+ * \param list variable list to search
+ * \param variable name you wish to get the data for
+ *
+ * \details
+ * Goes through a given variable list and searches for the given variable
+ *
+ * \retval The variable (not the value) on success
+ * \retval NULL if unable to find it.
+ */
+const struct ast_variable *ast_variable_find_variable_in_list(const struct ast_variable *list, const char *variable_name);
+
+/*!
* \brief Retrieve a category if it exists
*
* \param config which config to use
@@ -1217,6 +1232,66 @@ char *ast_realtime_decode_chunk(char *chunk);
*/
char *ast_realtime_encode_chunk(struct ast_str **dest, ssize_t maxlen, const char *chunk);
+/*!
+ * \brief Tests 2 variable values to see if they match
+ * \since 13.9.0
+ *
+ * \param left Variable to test
+ * \param right Variable to match against with an optional realtime-style operator in the name
+ *
+ * \retval 1 matches
+ * \retval 0 doesn't match
+ *
+ * \details
+ *
+ * The values of the variables are passed to ast_strings_match.
+ * If right->name is suffixed with a space and an operator, that operator
+ * is also passed to ast_strings_match.
+ *
+ * Examples:
+ *
+ * left->name = "id" (ignored)
+ * left->value = "abc"
+ * right->name = "id regex" (id is ignored)
+ * right->value = "a[bdef]c"
+ *
+ * will result in ast_strings_match("abc", "regex", "a[bdef]c") which will return 1.
+ *
+ * left->name = "id" (ignored)
+ * left->value = "abc"
+ * right->name = "id" (ignored)
+ * right->value = "abc"
+ *
+ * will result in ast_strings_match("abc", NULL, "abc") which will return 1.
+ *
+ * See the documentation for ast_strings_match for the valid operators.
+ */
+int ast_variables_match(const struct ast_variable *left, const struct ast_variable *right);
+
+/*!
+ * \brief Tests 2 variable lists to see if they match
+ * \since 13.9.0
+ *
+ * \param left Variable list to test
+ * \param right Variable list with an optional realtime-style operator in the names
+ * \param exact_match If true, all variables in left must match all variables in right
+ * and vice versa. This does exact value matches only. Operators aren't supported.
+ * Except for order, the left and right lists must be equal.
+ *
+ * If false, every variable in the right list must match some variable in the left list
+ * using the operators supplied. Variables in the left list that aren't in the right
+ * list are ignored for matching purposes.
+ *
+ * \retval 1 matches
+ * \retval 0 doesn't match
+ *
+ * \details
+ * Iterates over the variable lists calling ast_variables_match. If any match fails
+ * or a variable in the right list isn't in the left list, 0 is returned.
+ */
+int ast_variable_lists_match(const struct ast_variable *left, const struct ast_variable *right,
+ int exact_match);
+
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 66370186a..2c26c25da 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -312,6 +312,8 @@ struct ast_sip_aor {
unsigned int support_path;
/*! Qualify timeout. 0 is diabled. */
double qualify_timeout;
+ /* Voicemail extension to set in Message-Account */
+ char *voicemail_extension;
};
/*!
@@ -466,6 +468,10 @@ struct ast_sip_mwi_configuration {
);
/* Should mailbox states be combined into a single notification? */
unsigned int aggregate;
+ /* Should a subscribe replace unsolicited notifies? */
+ unsigned int subscribe_replaces_unsolicited;
+ /* Voicemail extension to set in Message-Account */
+ char *voicemail_extension;
};
/*!
@@ -2118,6 +2124,16 @@ char *ast_sip_get_regcontext(void);
char *ast_sip_get_endpoint_identifier_order(void);
/*!
+ * \brief Retrieve the default voicemail extension.
+ * \since 13.9.0
+ *
+ * \note returned string needs to be de-allocated by caller.
+ *
+ * \retval the default voicemail extension
+ */
+char *ast_sip_get_default_voicemail_extension(void);
+
+/*!
* \brief Retrieve the global default from user.
*
* This is the value placed in outbound requests' From header if there
@@ -2147,6 +2163,14 @@ void ast_sip_get_default_from_user(char *from_user, size_t size);
unsigned int ast_sip_get_keep_alive_interval(void);
/*!
+ * \brief Retrieve the system contact expiration check interval setting.
+ *
+ * \retval the contact expiration check interval.
+ */
+unsigned int ast_sip_get_contact_expiration_check_interval(void);
+
+
+/*!
* \brief Retrieve the system max initial qualify time.
*
* \retval the maximum initial qualify time.
@@ -2257,4 +2281,5 @@ int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip
void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
const struct ast_party_id *id);
+
#endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_body_generator_types.h b/include/asterisk/res_pjsip_body_generator_types.h
index a2cc04313..aab147238 100644
--- a/include/asterisk/res_pjsip_body_generator_types.h
+++ b/include/asterisk/res_pjsip_body_generator_types.h
@@ -65,6 +65,8 @@ struct ast_sip_message_accumulator {
int old_msgs;
/*! Number of new messages */
int new_msgs;
+ /*! Message-Account */
+ char message_account[PJSIP_MAX_URL_SIZE];
};
#endif /* _RES_PJSIP_BODY_GENERATOR_TYPES_H */
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index c9b66dce3..84d86fb9e 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -339,6 +339,14 @@ struct ast_sip_subscription_handler {
struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
struct ast_sip_endpoint *endpoint, const char *resource);
+/*!
+ * \brief Get the pjsip dialog that is associated with this subscription
+ * \since 13.9.0
+ *
+ * \retval NULL Could not get dialog
+ * \retval non-NULL The dialog
+ */
+pjsip_dialog *ast_sip_subscription_get_dialog(struct ast_sip_subscription *sub);
/*!
* \brief Get the endpoint that is associated with this subscription
@@ -379,6 +387,18 @@ struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_sub
int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip_body_data *notify_data, int terminate);
/*!
+ * \brief Retrieve the local sip uri for this subscription
+ * \since 13.9.0
+ *
+ * This is the local sip URI of the subscribed resource.
+ *
+ * \param sub The subscription
+ * \retval NULL Could not get uri
+ * \retval non-NULL The local pjsip_sip_uri
+ */
+pjsip_sip_uri *ast_sip_subscription_get_sip_uri(struct ast_sip_subscription *sub);
+
+/*!
* \brief Retrieve the local URI for this subscription
*
* This is the local URI of the subscribed resource.
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 34fb17dbd..a40472e9d 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -228,6 +228,10 @@ enum ast_rtp_instance_stat {
AST_RTP_INSTANCE_STAT_REMOTE_SSRC,
/*! Retrieve channel unique ID */
AST_RTP_INSTANCE_STAT_CHANNEL_UNIQUEID,
+ /*! Retrieve number of octets transmitted */
+ AST_RTP_INSTANCE_STAT_TXOCTETCOUNT,
+ /*! Retrieve number of octets received */
+ AST_RTP_INSTANCE_STAT_RXOCTETCOUNT,
};
/* Codes for RTP-specific data - not defined by our AST_FORMAT codes */
@@ -359,6 +363,10 @@ struct ast_rtp_instance_stats {
unsigned int remote_ssrc;
/*! The Asterisk channel's unique ID that owns this instance */
char channel_uniqueid[MAX_CHANNEL_ID];
+ /*! Number of octets transmitted */
+ unsigned int txoctetcount;
+ /*! Number of octets received */
+ unsigned int rxoctetcount;
};
#define AST_RTP_STAT_SET(current_stat, combined, placement, value) \
diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h
index d681ebb5a..42ecc133f 100644
--- a/include/asterisk/sorcery.h
+++ b/include/asterisk/sorcery.h
@@ -1151,6 +1151,7 @@ void *ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *
/*!
* \brief Retrieve an object or multiple objects using specific fields
+ * \since 13.9.0
*
* \param sorcery Pointer to a sorcery structure
* \param type Type of object to retrieve
@@ -1165,6 +1166,29 @@ void *ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *
*
* \note If the AST_RETRIEVE_FLAG_ALL flag is used you may omit fields to retrieve all objects
* of the given type.
+ *
+ * \note The fields parameter can contain realtime-style expressions in variable->name.
+ * All operators defined for ast_strings_match can be used except for regex as
+ * there's no common support for regex in the realtime backends at this time.
+ * If multiple variables are in the fields list, all must match for an object to
+ * be returned. See ast_strings_match for more information.
+ *
+ * Example:
+ *
+ * The following code can be significantly faster when a realtime backend is in use
+ * because the expression "qualify_frequency > 0" is passed to the database to limit
+ * the number of rows returned.
+ *
+ * struct ast_variable *var = ast_variable_new("qualify_frequency >", "0", "");
+ * struct ao2_container *aors;
+ *
+ * if (!var) {
+ * return;
+ * }
+ *
+ * aors = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),
+ * "aor", AST_RETRIEVE_FLAG_MULTIPLE, var);
+ *
*/
void *ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields);
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index f2b07e0bf..90ef82ebf 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -440,6 +440,16 @@ int stasis_app_control_is_done(
struct stasis_app_control *control);
/*!
+ * \brief Flush the control command queue.
+ * \since 13.9.0
+ *
+ * \param control Control object to flush command queue.
+ *
+ * \return Nothing
+ */
+void stasis_app_control_flush_queue(struct stasis_app_control *control);
+
+/*!
* \brief Returns the uniqueid of the channel associated with this control
*
* \param control Control object.
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index 3701b5305..0e2f69ba8 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -1335,4 +1335,34 @@ void ast_str_container_remove(struct ao2_container *str_container, const char *r
* \return A pointer to buf
*/
char *ast_generate_random_string(char *buf, size_t size);
+
+/*!
+ * \brief Compares 2 strings using realtime-style operators
+ * \since 13.9.0
+ *
+ * \param left The left side of the equation
+ * \param op The operator to apply
+ * \param right The right side of the equation
+ *
+ * \retval 1 matches
+ * \retval 0 doesn't match
+ *
+ * \details
+ *
+ * Operators:
+ * "=", "!=", "<", "<=", ">", ">=":
+ * If both left and right can be converted to float, then they will be
+ * compared as such. Otherwise the result will be derived from strcmp(left, right).
+ * "regex":
+ * The right value will be compiled as a regular expression and matched against the left
+ * value.
+ * "like":
+ * Any '%' character in the right value will be converted to '.*' and the resulting
+ * string will be handled as a regex.
+ * NULL , "":
+ * If the right value starts and ends with a '/' then it will be processed as a regex.
+ * Otherwise, same as "=".
+ */
+int ast_strings_match(const char *left, const char *op, const char *right);
+
#endif /* _ASTERISK_STRINGS_H */
diff --git a/main/Makefile b/main/Makefile
index 50fdc5739..d52c3f0a7 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -225,11 +225,11 @@ endif
$(ASTSSL_LIB): $(ASTSSL_LIB).$(ASTSSL_SO_VERSION)
$(ECHO_PREFIX) echo " [LN] $< -> $@"
-ifneq ($(LDCONFIG),)
- $(CMD_PREFIX) $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null
-else
- $(CMD_PREFIX) $(LN) -sf $< $@
-endif
+ $(CMD_PREFIX) if [ -x "$(LDCONFIG)" ] ; then \
+ $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null ;\
+ else \
+ $(LN) -sf $< $@ ;\
+ fi
else # Darwin
ASTSSL_LIB:=libasteriskssl.dylib
@@ -305,11 +305,11 @@ $(ASTPJ_LIB).$(ASTPJ_SO_VERSION): libasteriskpj.o libasteriskpj.exports
$(ASTPJ_LIB): $(ASTPJ_LIB).$(ASTPJ_SO_VERSION)
$(ECHO_PREFIX) echo " [LN] $< -> $@"
-ifneq ($(LDCONFIG),)
- $(CMD_PREFIX) $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null
-else
- $(CMD_PREFIX) $(LN) -sf $< $@
-endif
+ $(CMD_PREFIX) if [ -x "$(LDCONFIG)" ] ; then \
+ $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null ;\
+ else \
+ $(LN) -sf $< $@ ;\
+ fi
else # Darwin
ASTPJ_LIB:=libasteriskpj.dylib
diff --git a/main/config.c b/main/config.c
index 04e9367b7..a9ea01a8b 100644
--- a/main/config.c
+++ b/main/config.c
@@ -723,6 +723,96 @@ const char *ast_variable_find(const struct ast_category *category, const char *v
return ast_variable_find_in_list(category->root, variable);
}
+const struct ast_variable *ast_variable_find_variable_in_list(const struct ast_variable *list, const char *variable_name)
+{
+ const struct ast_variable *v;
+
+ for (v = list; v; v = v->next) {
+ if (!strcasecmp(variable_name, v->name)) {
+ return v;
+ }
+ }
+ return NULL;
+}
+
+int ast_variables_match(const struct ast_variable *left, const struct ast_variable *right)
+{
+ char *op;
+
+ if (left == right) {
+ return 1;
+ }
+
+ if (!(left && right)) {
+ return 0;
+ }
+
+ op = strrchr(right->name, ' ');
+ if (op) {
+ op++;
+ }
+
+ return ast_strings_match(left->value, op ? ast_strdupa(op) : NULL, right->value);
+}
+
+int ast_variable_lists_match(const struct ast_variable *left, const struct ast_variable *right, int exact_match)
+{
+ const struct ast_variable *field;
+ int right_count = 0;
+ int left_count = 0;
+
+ if (left == right) {
+ return 1;
+ }
+
+ if (!(left && right)) {
+ return 0;
+ }
+
+ for (field = right; field; field = field->next) {
+ char *space = strrchr(field->name, ' ');
+ const struct ast_variable *old;
+ char * name = (char *)field->name;
+
+ if (space) {
+ name = ast_strdup(field->name);
+ if (!name) {
+ return 0;
+ }
+ name[space - field->name] = '\0';
+ }
+
+ old = ast_variable_find_variable_in_list(left, name);
+ if (name != field->name) {
+ ast_free(name);
+ }
+
+ if (exact_match) {
+ if (!old || strcmp(old->value, field->value)) {
+ return 0;
+ }
+ } else {
+ if (!ast_variables_match(old, field)) {
+ return 0;
+ }
+ }
+
+ right_count++;
+ }
+
+ if (exact_match) {
+ for (field = left; field; field = field->next) {
+ left_count++;
+ }
+
+ if (right_count != left_count) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
const char *ast_variable_find_in_list(const struct ast_variable *list, const char *variable)
{
const struct ast_variable *v;
diff --git a/main/core_unreal.c b/main/core_unreal.c
index da0bb43bb..1f5c202ba 100644
--- a/main/core_unreal.c
+++ b/main/core_unreal.c
@@ -805,9 +805,11 @@ int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge
return -1;
}
+ /* The bridge thread now controls the chan ref from the ast_unreal_pvt */
ao2_lock(p);
ast_set_flag(p, AST_UNREAL_CARETAKER_THREAD);
ao2_unlock(p);
+
ast_channel_unref(chan);
return 0;
diff --git a/main/strings.c b/main/strings.c
index 495011ec5..f828727ad 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -39,6 +39,7 @@
ASTERISK_REGISTER_FILE()
+#include <regex.h>
#include "asterisk/strings.h"
#include "asterisk/pbx.h"
@@ -228,3 +229,129 @@ char *ast_generate_random_string(char *buf, size_t size)
return buf;
}
+
+int ast_strings_match(const char *left, const char *op, const char *right)
+{
+ char *internal_op = (char *)op;
+ char *internal_right = (char *)right;
+ float left_num;
+ float right_num;
+ int scan_numeric = 0;
+
+ if (!(left && right)) {
+ return 0;
+ }
+
+ if (ast_strlen_zero(op)) {
+ if (ast_strlen_zero(left) && ast_strlen_zero(right)) {
+ return 1;
+ }
+
+ if (strlen(right) >= 2 && right[0] == '/' && right[strlen(right) - 1] == '/') {
+ internal_op = "regex";
+ internal_right = ast_strdupa(right);
+ /* strip the leading and trailing '/' */
+ internal_right++;
+ internal_right[strlen(internal_right) - 1] = '\0';
+ goto regex;
+ } else {
+ internal_op = "=";
+ goto equals;
+ }
+ }
+
+ if (!strcasecmp(op, "like")) {
+ char *tok;
+ struct ast_str *buffer = ast_str_alloca(128);
+
+ if (!strchr(right, '%')) {
+ return !strcmp(left, right);
+ } else {
+ internal_op = "regex";
+ internal_right = ast_strdupa(right);
+ tok = strsep(&internal_right, "%");
+ ast_str_set(&buffer, 0, "^%s", tok);
+
+ while ((tok = strsep(&internal_right, "%"))) {
+ ast_str_append(&buffer, 0, ".*%s", tok);
+ }
+ ast_str_append(&buffer, 0, "%s", "$");
+
+ internal_right = ast_str_buffer(buffer);
+ /* fall through to regex */
+ }
+ }
+
+regex:
+ if (!strcasecmp(internal_op, "regex")) {
+ regex_t expression;
+ int rc;
+
+ if (regcomp(&expression, internal_right, REG_EXTENDED | REG_NOSUB)) {
+ return 0;
+ }
+
+ rc = regexec(&expression, left, 0, NULL, 0);
+ regfree(&expression);
+ return !rc;
+ }
+
+equals:
+ scan_numeric = (sscanf(left, "%f", &left_num) && sscanf(internal_right, "%f", &right_num));
+
+ if (internal_op[0] == '=') {
+ if (ast_strlen_zero(left) && ast_strlen_zero(internal_right)) {
+ return 1;
+ }
+
+ if (scan_numeric) {
+ return (left_num == right_num);
+ } else {
+ return (!strcmp(left, internal_right));
+ }
+ }
+
+ if (internal_op[0] == '!' && internal_op[1] == '=') {
+ if (scan_numeric) {
+ return (left_num != right_num);
+ } else {
+ return !!strcmp(left, internal_right);
+ }
+ }
+
+ if (internal_op[0] == '<') {
+ if (scan_numeric) {
+ if (internal_op[1] == '=') {
+ return (left_num <= right_num);
+ } else {
+ return (left_num < right_num);
+ }
+ } else {
+ if (internal_op[1] == '=') {
+ return strcmp(left, internal_right) <= 0;
+ } else {
+ return strcmp(left, internal_right) < 0;
+ }
+ }
+ }
+
+ if (internal_op[0] == '>') {
+ if (scan_numeric) {
+ if (internal_op[1] == '=') {
+ return (left_num >= right_num);
+ } else {
+ return (left_num > right_num);
+ }
+ } else {
+ if (internal_op[1] == '=') {
+ return strcmp(left, internal_right) >= 0;
+ } else {
+ return strcmp(left, internal_right) > 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
diff --git a/main/utils.c b/main/utils.c
index 6a778b90c..e92f5c3d9 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1153,7 +1153,7 @@ static char *handle_show_locks(struct ast_cli_entry *e, int cmd, struct ast_cli_
"Usage: core show locks\n"
" This command is for lock debugging. It prints out which locks\n"
"are owned by each active thread.\n";
- ast_cli_allow_on_shutdown(e);
+ ast_cli_allow_at_shutdown(e);
return NULL;
case CLI_GENERATE:
diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c
index 7b9b94665..6018c43be 100644
--- a/res/ari/resource_bridges.c
+++ b/res/ari/resource_bridges.c
@@ -296,10 +296,11 @@ static void *bridge_channel_control_thread(void *data)
thread_data = NULL;
stasis_app_control_execute_until_exhausted(bridge_channel, control);
+ stasis_app_control_flush_queue(control);
- ast_hangup(bridge_channel);
- ao2_cleanup(control);
stasis_forward_cancel(forward);
+ ao2_cleanup(control);
+ ast_hangup(bridge_channel);
return NULL;
}
@@ -526,9 +527,7 @@ static enum play_found_result ari_bridges_play_found(const char *args_media,
control = stasis_app_control_find_by_channel(play_channel);
if (!control) {
- ast_ari_response_error(
- response, 500, "Internal Error", "Failed to get control snapshot");
- return PLAY_FOUND_FAILURE;
+ return PLAY_FOUND_CHANNEL_UNAVAILABLE;
}
ao2_lock(control);
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 170a19151..f4dc72549 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -302,6 +302,12 @@
configuration.
</para></description>
</configOption>
+ <configOption name="mwi_subscribe_replaces_unsolicited">
+ <synopsis>An MWI subscribe will replace sending unsolicited NOTIFYs</synopsis>
+ </configOption>
+ <configOption name="voicemail_extension">
+ <synopsis>The voicemail extension to send in the NOTIFY Message-Account header</synopsis>
+ </configOption>
<configOption name="moh_suggest" default="default">
<synopsis>Default Music On Hold class</synopsis>
</configOption>
@@ -1138,6 +1144,9 @@
endpoint configuration section to enable unsolicited MWI NOTIFYs to the endpoint.
</para></description>
</configOption>
+ <configOption name="voicemail_extension">
+ <synopsis>The voicemail extension to send in the NOTIFY Message-Account header</synopsis>
+ </configOption>
<configOption name="maximum_expiration" default="7200">
<synopsis>Maximum time to keep an AoR</synopsis>
<description><para>
@@ -1282,6 +1291,9 @@
<configOption name="keep_alive_interval" default="0">
<synopsis>The interval (in seconds) to send keepalives to active connection-oriented transports.</synopsis>
</configOption>
+ <configOption name="contact_expiration_check_interval" default="30">
+ <synopsis>The interval (in seconds) to check for expired contacts.</synopsis>
+ </configOption>
<configOption name="max_initial_qualify_time" default="0">
<synopsis>The maximum amount of time from startup that qualifies should be attempted on all contacts.
If greater than the qualify_frequency for an aor, qualify_frequency will be used instead.</synopsis>
@@ -1299,6 +1311,9 @@
<configOption name="default_outbound_endpoint" default="default_outbound_endpoint">
<synopsis>Endpoint to use when sending an outbound request to a URI without a specified endpoint.</synopsis>
</configOption>
+ <configOption name="default_voicemail_extension">
+ <synopsis>The voicemail extension to send in the NOTIFY Message-Account header if not specified on endpoint or aor</synopsis>
+ </configOption>
<configOption name="debug" default="no">
<synopsis>Enable/Disable SIP debug logging. Valid options include yes|no or
a host address</synopsis>
@@ -2859,6 +2874,7 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
pj_pool_t *pool;
pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
pjsip_uri *sip_uri;
+ const char *fromuser;
if (ast_strlen_zero(uri)) {
if (!endpoint && (!contact || ast_strlen_zero(contact->uri))) {
@@ -2905,7 +2921,8 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
return -1;
}
- if (sip_dialog_create_from(pool, &from, endpoint ? endpoint->fromuser : NULL,
+ fromuser = endpoint ? (!ast_strlen_zero(endpoint->fromuser) ? endpoint->fromuser : ast_sorcery_object_get_id(endpoint)) : NULL;
+ if (sip_dialog_create_from(pool, &from, fromuser,
endpoint ? endpoint->fromdomain : NULL, &remote_uri, &selector)) {
ast_log(LOG_ERROR, "Unable to create From header for %.*s request to endpoint %s\n",
(int) pj_strlen(&method->name), pj_strbuf(&method->name),
@@ -3200,6 +3217,7 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
struct send_request_wrapper *req_wrapper;
pj_status_t ret_val;
pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
+ pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
/* Create wrapper to detect if the callback was actually called on an error. */
req_wrapper = ao2_alloc(sizeof(*req_wrapper), send_request_wrapper_destructor);
@@ -3250,6 +3268,11 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
*/
ao2_ref(req_wrapper, +1);
+ if (endpoint) {
+ sip_get_tpselector_from_endpoint(endpoint, &selector);
+ pjsip_tx_data_set_transport(tdata, &selector);
+ }
+
ret_val = pjsip_endpt_send_request(endpt, tdata, -1, req_wrapper, endpt_send_request_cb);
if (ret_val != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index 3d88ffc2a..ad03379fd 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -36,6 +36,8 @@
#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
#define DEFAULT_FROM_USER "asterisk"
#define DEFAULT_REGCONTEXT ""
+#define DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL 30
+#define DEFAULT_VOICEMAIL_EXTENSION ""
static char default_useragent[256];
@@ -51,6 +53,8 @@ struct global_config {
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);
+ /*! Default voicemail extension */
+ AST_STRING_FIELD(default_voicemail_extension);
);
/* Value to put in Max-Forwards header */
unsigned int max_forwards;
@@ -58,6 +62,8 @@ struct global_config {
unsigned int keep_alive_interval;
/* The maximum time for all contacts to be qualified at startup */
unsigned int max_initial_qualify_time;
+ /* The interval at which to check for expired contacts */
+ unsigned int contact_expiration_check_interval;
};
static void global_destructor(void *obj)
@@ -141,20 +147,35 @@ char *ast_sip_get_debug(void)
char *ast_sip_get_regcontext(void)
{
- char *res;
- struct global_config *cfg;
+ char *res;
+ struct global_config *cfg;
- cfg = get_global_cfg();
- if (!cfg) {
- return ast_strdup(DEFAULT_REGCONTEXT);
- }
+ cfg = get_global_cfg();
+ if (!cfg) {
+ return ast_strdup(DEFAULT_REGCONTEXT);
+ }
- res = ast_strdup(cfg->regcontext);
- ao2_ref(cfg, -1);
+ res = ast_strdup(cfg->regcontext);
+ ao2_ref(cfg, -1);
- return res;
+ return res;
}
+char *ast_sip_get_default_voicemail_extension(void)
+{
+ char *res;
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ return ast_strdup(DEFAULT_VOICEMAIL_EXTENSION);
+ }
+
+ res = ast_strdup(cfg->default_voicemail_extension);
+ ao2_ref(cfg, -1);
+
+ return res;
+}
char *ast_sip_get_endpoint_identifier_order(void)
{
@@ -186,6 +207,21 @@ unsigned int ast_sip_get_keep_alive_interval(void)
return interval;
}
+unsigned int ast_sip_get_contact_expiration_check_interval(void)
+{
+ unsigned int interval;
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ return DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL;
+ }
+
+ interval = cfg->contact_expiration_check_interval;
+ ao2_ref(cfg, -1);
+ return interval;
+}
+
unsigned int ast_sip_get_max_initial_qualify_time(void)
{
unsigned int time;
@@ -329,9 +365,14 @@ int ast_sip_initialize_sorcery_global(void)
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));
+ ast_sorcery_object_field_register(sorcery, "global", "default_voicemail_extension",
+ DEFAULT_VOICEMAIL_EXTENSION, OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config,
+ default_voicemail_extension));
ast_sorcery_object_field_register(sorcery, "global", "regcontext", DEFAULT_REGCONTEXT,
- OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, regcontext));
-
+ OPT_UINT_T, 0, FLDSET(struct global_config, contact_expiration_check_interval));
+ ast_sorcery_object_field_register(sorcery, "global", "contact_expiration_check_interval",
+ __stringify(DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL),
+ OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, regcontext));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 4008abad1..3145daca0 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -35,6 +35,7 @@ static void aor_destroy(void *obj)
ao2_cleanup(aor->permanent_contacts);
ast_string_field_free_memory(aor);
+ ast_free(aor->voicemail_extension);
}
/*! \brief Allocator for AOR */
@@ -437,6 +438,24 @@ static int contacts_to_var_list(const void *obj, struct ast_variable **fields)
return 0;
}
+static int voicemail_extension_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_aor *aor = obj;
+
+ aor->voicemail_extension = ast_strdup(var->value);
+
+ return aor->voicemail_extension ? 0 : -1;
+}
+
+static int voicemail_extension_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_aor *aor = obj;
+
+ *buf = ast_strdup(aor->voicemail_extension);
+
+ return 0;
+}
+
int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
{
char *copy, *name;
@@ -987,6 +1006,7 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, contacts_to_str, contacts_to_var_list, 0, 0);
ast_sorcery_object_field_register(sorcery, "aor", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, mailboxes));
+ ast_sorcery_object_field_register_custom(sorcery, "aor", "voicemail_extension", "", voicemail_extension_handler, voicemail_extension_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sorcery, "aor", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, outbound_proxy));
ast_sorcery_object_field_register(sorcery, "aor", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, support_path));
diff --git a/res/res_pjsip/pjsip_cli.c b/res/res_pjsip/pjsip_cli.c
index bbd0ac4b8..e6433f435 100644
--- a/res/res_pjsip/pjsip_cli.c
+++ b/res/res_pjsip/pjsip_cli.c
@@ -197,7 +197,7 @@ char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_
ast_str_append(&context.output_buffer, 0, "\n");
formatter_entry->print_header(NULL, &context, 0);
ast_str_append(&context.output_buffer, 0,
- " =========================================================================================\n\n");
+ "==========================================================================================\n\n");
if (is_container || cmd == CLI_GENERATE) {
container = formatter_entry->get_container(regex);
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 371e4318b..baa5063e4 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1052,6 +1052,23 @@ static int set_var_to_vl(const void *obj, struct ast_variable **fields)
return 0;
}
+static int voicemail_extension_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+
+ endpoint->subscription.mwi.voicemail_extension = ast_strdup(var->value);
+
+ return endpoint->subscription.mwi.voicemail_extension ? 0 : -1;
+}
+
+static int voicemail_extension_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_endpoint *endpoint = obj;
+
+ *buf = ast_strdup(endpoint->subscription.mwi.voicemail_extension);
+
+ return 0;
+}
static void *sip_nat_hook_alloc(const char *name)
{
@@ -1432,235 +1449,6 @@ static struct ao2_container *cli_endpoint_get_container(const char *regex)
return s_container;
}
-static int cli_channel_populate_container(void *obj, void *arg, int flags)
-{
- struct ast_channel_snapshot *snapshot = obj;
-
- ao2_link(arg, snapshot);
-
- return 0;
-}
-
-static int cli_channel_iterate(void *container, ao2_callback_fn callback, void *args)
-{
- const struct ast_sip_endpoint *endpoint = container;
-
- ast_sip_for_each_channel(endpoint, callback, args);
-
- return 0;
-}
-
-static int cli_channel_sort(const void *obj, const void *arg, int flags)
-{
- const struct ast_channel_snapshot *left_obj = obj;
- const struct ast_channel_snapshot *right_obj = arg;
- const char *right_key = arg;
- int cmp;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_OBJECT:
- right_key = right_obj->name;
- /* Fall through */
- case OBJ_SEARCH_KEY:
- cmp = strcmp(left_obj->name, right_key);
- break;
- case OBJ_SEARCH_PARTIAL_KEY:
- cmp = strncmp(left_obj->name, right_key, strlen(right_key));
- break;
- default:
- cmp = 0;
- break;
- }
-
- return cmp;
-}
-
-static int cli_channel_compare(void *obj, void *arg, int flags)
-{
- const struct ast_channel_snapshot *left_obj = obj;
- const struct ast_channel_snapshot *right_obj = arg;
- const char *right_key = arg;
- int cmp = 0;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_OBJECT:
- right_key = right_obj->name;
- /* Fall through */
- case OBJ_SEARCH_KEY:
- if (strcmp(left_obj->name, right_key) == 0) {
- cmp = CMP_MATCH | CMP_STOP;
- }
- break;
- case OBJ_SEARCH_PARTIAL_KEY:
- if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) {
- cmp = CMP_MATCH;
- }
- break;
- default:
- cmp = 0;
- break;
- }
-
- return cmp;
-}
-
-static int cli_channel_hash(const void *obj, int flags)
-{
- const struct ast_channel_snapshot *snapshot = obj;
-
- if (flags & OBJ_SEARCH_OBJECT) {
- return ast_str_hash(snapshot->name);
- } else if (flags & OBJ_SEARCH_KEY) {
- return ast_str_hash(obj);
- }
-
- return -1;
-}
-
-static int cli_endpoint_gather_channels(void *obj, void *arg, int flags)
-{
- struct ast_sip_endpoint *endpoint = obj;
- struct ao2_container *channels = arg;
-
- ast_sip_for_each_channel(endpoint, cli_channel_populate_container, channels);
-
- return 0;
-}
-
-static int cli_filter_channels(void *obj, void *arg, int flags)
-{
- struct ast_channel_snapshot *channel = obj;
- regex_t *regexbuf = arg;
-
- if (!regexec(regexbuf, channel->name, 0, NULL, 0)
- || !regexec(regexbuf, channel->appl, 0, NULL, 0)) {
- return 0;
- }
-
- return CMP_MATCH;
-}
-
-static struct ao2_container *cli_channel_get_container(const char *regex)
-{
- RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
- struct ao2_container *child_container;
- regex_t regexbuf;
-
- parent_container = cli_endpoint_get_container("");
- if (!parent_container) {
- return NULL;
- }
- child_container = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, 17,
- cli_channel_hash, cli_channel_sort, cli_channel_compare);
- if (!child_container) {
- return NULL;
- }
-
- ao2_callback(parent_container, OBJ_NODATA, cli_endpoint_gather_channels, child_container);
-
- if (!ast_strlen_zero(regex)) {
- if (regcomp(&regexbuf, regex, REG_EXTENDED | REG_NOSUB)) {
- ao2_ref(child_container, -1);
- return NULL;
- }
- ao2_callback(child_container, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, cli_filter_channels, &regexbuf);
- regfree(&regexbuf);
- }
-
- return child_container;
-}
-
-static const char *cli_channel_get_id(const void *obj)
-{
- const struct ast_channel_snapshot *snapshot = obj;
-
- return snapshot->name;
-}
-
-static void *cli_channel_retrieve_by_id(const char *id)
-{
- RAII_VAR(struct ao2_container *, container, cli_channel_get_container(""), ao2_cleanup);
-
- return ao2_find(container, id, OBJ_KEY | OBJ_NOLOCK);
-}
-
-static int cli_channel_print_header(void *obj, void *arg, int flags)
-{
- struct ast_sip_cli_context *context = arg;
- int indent = CLI_INDENT_TO_SPACES(context->indent_level);
- int filler = CLI_LAST_TABSTOP - indent - 13;
-
- ast_assert(context->output_buffer != NULL);
-
- ast_str_append(&context->output_buffer, 0,
- "%*s: <ChannelId%*.*s> <State.....> <Time(sec)>\n",
- indent, "Channel", filler, filler, CLI_HEADER_FILLER);
- if (context->recurse) {
- context->indent_level++;
- indent = CLI_INDENT_TO_SPACES(context->indent_level);
- filler = CLI_LAST_TABSTOP - indent - 38;
- ast_str_append(&context->output_buffer, 0,
- "%*s: <DialedExten%*.*s> CLCID: <ConnectedLineCID.......>\n",
- indent, "Exten", filler, filler, CLI_HEADER_FILLER);
- context->indent_level--;
- }
-
- return 0;
-}
-
-static int cli_channel_print_body(void *obj, void *arg, int flags)
-{
- const struct ast_channel_snapshot *snapshot = obj;
- struct ast_sip_cli_context *context = arg;
- struct timeval current_time;
- char *print_name = NULL;
- int print_name_len;
- int indent;
- int flexwidth;
-
- ast_assert(context->output_buffer != NULL);
-
- gettimeofday(&current_time, NULL);
-
- print_name_len = strlen(snapshot->name) + strlen(snapshot->appl) + 2;
- if (!(print_name = alloca(print_name_len))) {
- return -1;
- }
-
- snprintf(print_name, print_name_len, "%s/%s", snapshot->name, snapshot->appl);
-
- indent = CLI_INDENT_TO_SPACES(context->indent_level);
- flexwidth = CLI_LAST_TABSTOP - indent;
-
- ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %-12.12s %11ld\n",
- CLI_INDENT_TO_SPACES(context->indent_level), "Channel",
- flexwidth, flexwidth,
- print_name,
- ast_state2str(snapshot->state),
- current_time.tv_sec - snapshot->creationtime.tv_sec);
-
- if (context->recurse) {
- context->indent_level++;
- indent = CLI_INDENT_TO_SPACES(context->indent_level);
- flexwidth = CLI_LAST_TABSTOP - indent - 25;
-
- ast_str_append(&context->output_buffer, 0,
- "%*s: %-*.*s CLCID: \"%s\" <%s>\n",
- indent, "Exten",
- flexwidth, flexwidth,
- snapshot->exten,
- snapshot->connected_name,
- snapshot->connected_number
- );
- context->indent_level--;
- if (context->indent_level == 0) {
- ast_str_append(&context->output_buffer, 0, "\n");
- }
- }
-
- return 0;
-}
-
static int cli_endpoint_iterate(void *obj, ao2_callback_fn callback, void *args)
{
ao2_callback(obj, OBJ_NODATA, callback, args);
@@ -1779,21 +1567,6 @@ static int cli_endpoint_print_body(void *obj, void *arg, int flags)
}
static struct ast_cli_entry cli_commands[] = {
- AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Channels",
- .command = "pjsip list channels",
- .usage = "Usage: pjsip list channels [ like <pattern> ]\n"
- " List the active PJSIP channels\n"
- " Optional regular expression pattern is used to filter the list.\n"),
- AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channels",
- .command = "pjsip show channels",
- .usage = "Usage: pjsip show channels [ like <pattern> ]\n"
- " List(detailed) the active PJSIP channels\n"
- " Optional regular expression pattern is used to filter the list.\n"),
- AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channel",
- .command = "pjsip show channel",
- .usage = "Usage: pjsip show channel\n"
- " List(detailed) the active PJSIP channel\n"),
-
AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Endpoints",
.command = "pjsip list endpoints",
.usage = "Usage: pjsip list endpoints [ like <pattern> ]\n"
@@ -1891,7 +1664,9 @@ int ast_res_pjsip_initialize_configuration(void)
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rpid_immediate", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.rpid_immediate));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_diversion));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.mailboxes));
+ ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "voicemail_extension", "", voicemail_extension_handler, voicemail_extension_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwi_subscribe_replaces_unsolicited", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.subscribe_replaces_unsolicited));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, media_encryption_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_avpf));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "force_avp", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.force_avp));
@@ -1987,21 +1762,6 @@ int ast_res_pjsip_initialize_configuration(void)
return -1;
}
- channel_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
- if (!channel_formatter) {
- ast_log(LOG_ERROR, "Unable to allocate memory for channel_formatter\n");
- ast_sorcery_unref(sip_sorcery);
- sip_sorcery = NULL;
- return -1;
- }
- channel_formatter->name = "channel";
- channel_formatter->print_header = cli_channel_print_header;
- channel_formatter->print_body = cli_channel_print_body;
- channel_formatter->get_container = cli_channel_get_container;
- channel_formatter->iterate = cli_channel_iterate;
- channel_formatter->retrieve_by_id = cli_channel_retrieve_by_id;
- channel_formatter->get_id = cli_channel_get_id;
-
endpoint_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
if (!endpoint_formatter) {
ast_log(LOG_ERROR, "Unable to allocate memory for endpoint_formatter\n");
@@ -2017,7 +1777,6 @@ int ast_res_pjsip_initialize_configuration(void)
endpoint_formatter->retrieve_by_id = cli_endpoint_retrieve_by_id;
endpoint_formatter->get_id = ast_sorcery_object_get_id;
- ast_sip_register_cli_formatter(channel_formatter);
ast_sip_register_cli_formatter(endpoint_formatter);
ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
@@ -2044,7 +1803,6 @@ void ast_res_pjsip_destroy_configuration(void)
ast_manager_unregister(AMI_SHOW_ENDPOINTS);
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
ast_sip_unregister_cli_formatter(endpoint_formatter);
- ast_sip_unregister_cli_formatter(channel_formatter);
ast_sip_destroy_cli();
ao2_cleanup(persistent_endpoints);
}
@@ -2060,6 +1818,7 @@ int ast_res_pjsip_reload_configuration(void)
static void subscription_configuration_destroy(struct ast_sip_endpoint_subscription_configuration *subscription)
{
ast_string_field_free_memory(&subscription->mwi);
+ ast_free(subscription->mwi.voicemail_extension);
}
static void info_configuration_destroy(struct ast_sip_endpoint_info_configuration *info)
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 4cce55836..7fd606d77 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -237,31 +237,12 @@ static void init_start_time(const struct ast_sip_contact *contact)
/*!
* \internal
- * \brief Match a container contact object with the contact sorcery id looking for.
- *
- * \param obj pointer to the (user-defined part) of an object.
- * \param arg callback argument from ao2_callback()
- * \param flags flags from ao2_callback()
- *
- * \return Values are a combination of enum _cb_results.
- */
-static int match_contact_id(void *obj, void *arg, int flags)
-{
- struct ast_sip_contact *contact = obj;
- const char *looking_for = arg;
-
- return strcmp(ast_sorcery_object_get_id(contact), looking_for) ? 0 : CMP_MATCH;
-}
-
-/*!
- * \internal
- * \brief For an endpoint try to match the given contact sorcery id.
+ * \brief For an endpoint try to match the given contact->aor.
*/
static int on_endpoint(void *obj, void *arg, int flags)
{
struct ast_sip_endpoint *endpoint = obj;
- struct ast_sip_contact *contact;
- char *looking_for = arg;
+ char *contact_aor = arg;
char *aor_name;
char *aors;
@@ -271,24 +252,7 @@ static int on_endpoint(void *obj, void *arg, int flags)
aors = ast_strdupa(endpoint->aors);
while ((aor_name = ast_strip(strsep(&aors, ",")))) {
- struct ast_sip_aor *aor;
- struct ao2_container *contacts;
-
- aor = ast_sip_location_retrieve_aor(aor_name);
- if (!aor) {
- continue;
- }
-
- contacts = ast_sip_location_retrieve_aor_contacts(aor);
- ao2_ref(aor, -1);
- if (!contacts) {
- continue;
- }
-
- contact = ao2_callback(contacts, 0, match_contact_id, looking_for);
- ao2_ref(contacts, -1);
- if (contact) {
- ao2_ref(contact, -1);
+ if (!strcmp(contact_aor, aor_name)) {
return CMP_MATCH;
}
}
@@ -302,12 +266,26 @@ static int on_endpoint(void *obj, void *arg, int flags)
*/
static struct ast_sip_endpoint *find_an_endpoint(struct ast_sip_contact *contact)
{
- char *looking_for = (char *) ast_sorcery_object_get_id(contact);
- struct ao2_container *endpoints = ast_sip_get_endpoints();
+ struct ao2_container *endpoints;
struct ast_sip_endpoint *endpoint;
+ struct ast_variable *var;
+ char *aor = ast_alloca(strlen(contact->aor) + 3);
+
+ sprintf(aor, "%%%s%%", contact->aor);
+ var = ast_variable_new("aors LIKE", aor, "");
+ endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),
+ "endpoint", AST_RETRIEVE_FLAG_MULTIPLE, var);
- endpoint = ao2_callback(endpoints, 0, on_endpoint, looking_for);
+ ast_variables_destroy(var);
+
+ /*
+ * Because aors are a string list, we have to use a pattern match but since a simple
+ * pattern match could return an endpoint that has an aor of "aaabccc" when searching
+ * for "abc", we still have to iterate over them to find an exact aor match.
+ */
+ endpoint = ao2_callback(endpoints, 0, on_endpoint, (char *)contact->aor);
ao2_ref(endpoints, -1);
+
return endpoint;
}
@@ -346,23 +324,18 @@ static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_con
pjsip_tx_data *tdata;
RAII_VAR(struct ast_sip_endpoint *, endpoint_local, NULL, ao2_cleanup);
- if (contact->authenticate_qualify) {
+ if (endpoint) {
endpoint_local = ao2_bump(endpoint);
+ } else {
+ endpoint_local = find_an_endpoint(contact);
if (!endpoint_local) {
- /*
- * Find the "first" endpoint to completely qualify the contact - any
- * endpoint that is associated with the contact should do.
- */
- endpoint_local = find_an_endpoint(contact);
- if (!endpoint_local) {
- ast_log(LOG_ERROR, "Unable to find an endpoint to qualify contact %s\n",
- contact->uri);
- return -1;
- }
+ ast_log(LOG_ERROR, "Unable to find an endpoint to qualify contact %s\n",
+ contact->uri);
+ return -1;
}
}
- if (ast_sip_create_request("OPTIONS", NULL, NULL, NULL, contact, &tdata)) {
+ if (ast_sip_create_request("OPTIONS", NULL, endpoint_local, NULL, contact, &tdata)) {
ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n",
contact->uri);
return -1;
@@ -1078,31 +1051,13 @@ static int qualify_and_schedule_cb(void *obj, void *arg, int flags)
*/
static int qualify_and_schedule_all_cb(void *obj, void *arg, int flags)
{
- struct ast_sip_endpoint *endpoint = obj;
- char *aors;
- char *aor_name;
-
- if (ast_strlen_zero(endpoint->aors)) {
- return 0;
- }
-
- aors = ast_strdupa(endpoint->aors);
- while ((aor_name = ast_strip(strsep(&aors, ",")))) {
- struct ast_sip_aor *aor;
- struct ao2_container *contacts;
-
- aor = ast_sip_location_retrieve_aor(aor_name);
- if (!aor) {
- continue;
- }
-
- contacts = ast_sip_location_retrieve_aor_contacts(aor);
- if (contacts) {
- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb, aor);
- ao2_ref(contacts, -1);
- }
+ struct ast_sip_aor *aor = obj;
+ struct ao2_container *contacts;
- ao2_ref(aor, -1);
+ contacts = ast_sip_location_retrieve_aor_contacts(aor);
+ if (contacts) {
+ ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb, aor);
+ ao2_ref(contacts, -1);
}
return 0;
@@ -1123,16 +1078,25 @@ static int unschedule_all_cb(void *obj, void *arg, int flags)
static void qualify_and_schedule_all(void)
{
- struct ao2_container *endpoints = ast_sip_get_endpoints();
+ struct ast_variable *var = ast_variable_new("qualify_frequency >", "0", "");
+ struct ao2_container *aors;
+
+ if (!var) {
+ return;
+ }
+ aors = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),
+ "aor", AST_RETRIEVE_FLAG_MULTIPLE, var);
+
+ ast_variables_destroy(var);
ao2_callback(sched_qualifies, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, unschedule_all_cb, NULL);
- if (!endpoints) {
+ if (!aors) {
return;
}
- ao2_callback(endpoints, OBJ_NODATA, qualify_and_schedule_all_cb, NULL);
- ao2_ref(endpoints, -1);
+ ao2_callback(aors, OBJ_NODATA, qualify_and_schedule_all_cb, NULL);
+ ao2_ref(aors, -1);
}
static int format_contact_status(void *obj, void *arg, int flags)
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index c9d1b743e..f9bfc1904 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -42,6 +42,8 @@
struct mwi_subscription;
static struct ao2_container *unsolicited_mwi;
+static char *default_voicemail_extension;
+
#define STASIS_BUCKETS 13
#define MWI_BUCKETS 53
@@ -326,11 +328,30 @@ static int get_message_count(void *obj, void *arg, int flags)
return 0;
}
+static void set_voicemail_extension(pj_pool_t *pool, pjsip_sip_uri *local_uri,
+ struct ast_sip_message_accumulator *counter, const char *voicemail_extension)
+{
+ pjsip_sip_uri *account_uri;
+ const char *vm_exten;
+
+ if (ast_strlen_zero(voicemail_extension)) {
+ vm_exten = default_voicemail_extension;
+ } else {
+ vm_exten = voicemail_extension;
+ }
+
+ if (!ast_strlen_zero(vm_exten)) {
+ account_uri = pjsip_uri_clone(pool, local_uri);
+ pj_strdup2(pool, &account_uri->user, vm_exten);
+ pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, account_uri, counter->message_account, sizeof(counter->message_account));
+ }
+}
+
struct unsolicited_mwi_data {
struct mwi_subscription *sub;
struct ast_sip_endpoint *endpoint;
pjsip_evsub_state state;
- const struct ast_sip_body *body;
+ struct ast_sip_message_accumulator *counter;
};
static int send_unsolicited_mwi_notify_to_contact(void *obj, void *arg, int flags)
@@ -339,27 +360,50 @@ static int send_unsolicited_mwi_notify_to_contact(void *obj, void *arg, int flag
struct mwi_subscription *sub = mwi_data->sub;
struct ast_sip_endpoint *endpoint = mwi_data->endpoint;
pjsip_evsub_state state = mwi_data->state;
- const struct ast_sip_body *body = mwi_data->body;
struct ast_sip_contact *contact = obj;
const char *state_name;
pjsip_tx_data *tdata;
pjsip_sub_state_hdr *sub_state;
pjsip_event_hdr *event;
+ pjsip_from_hdr *from;
+ pjsip_sip_uri *from_uri;
const pjsip_hdr *allow_events = pjsip_evsub_get_allow_events_hdr(NULL);
+ struct ast_sip_body body;
+ struct ast_str *body_text;
+ struct ast_sip_body_data body_data = {
+ .body_type = AST_SIP_MESSAGE_ACCUMULATOR,
+ .body_data = mwi_data->counter,
+ };
if (ast_sip_create_request("NOTIFY", NULL, endpoint, NULL, contact, &tdata)) {
ast_log(LOG_WARNING, "Unable to create unsolicited NOTIFY request to endpoint %s URI %s\n", sub->id, contact->uri);
return 0;
}
- if (!ast_strlen_zero(endpoint->subscription.mwi.fromuser)) {
- pjsip_fromto_hdr *from = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, NULL);
- pjsip_name_addr *from_name_addr = (pjsip_name_addr *) from->uri;
- pjsip_sip_uri *from_uri = pjsip_uri_get_uri(from_name_addr->uri);
+ body.type = MWI_TYPE;
+ body.subtype = MWI_SUBTYPE;
+ body_text = ast_str_create(64);
+ if (!body_text) {
+ return 0;
+ }
+
+ from = PJSIP_MSG_FROM_HDR(tdata->msg);
+ from_uri = pjsip_uri_get_uri(from->uri);
+ if (!ast_strlen_zero(endpoint->subscription.mwi.fromuser)) {
pj_strdup2(tdata->pool, &from_uri->user, endpoint->subscription.mwi.fromuser);
}
+ set_voicemail_extension(tdata->pool, from_uri, mwi_data->counter, endpoint->subscription.mwi.voicemail_extension);
+
+ if (ast_sip_pubsub_generate_body_content(body.type, body.subtype, &body_data, &body_text)) {
+ ast_log(LOG_WARNING, "Unable to generate SIP MWI NOTIFY body.\n");
+ ast_free(body_text);
+ return 0;
+ }
+
+ body.body_text = ast_str_buffer(body_text);
+
switch (state) {
case PJSIP_EVSUB_STATE_ACTIVE:
state_name = "active";
@@ -379,12 +423,54 @@ static int send_unsolicited_mwi_notify_to_contact(void *obj, void *arg, int flag
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) event);
pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, allow_events));
- ast_sip_add_body(tdata, body);
+ ast_sip_add_body(tdata, &body);
ast_sip_send_request(tdata, NULL, endpoint, NULL, NULL);
+ ast_free(body_text);
+
return 0;
}
+static struct ast_sip_aor *find_aor_for_resource(struct ast_sip_endpoint *endpoint, const char *resource)
+{
+ struct ast_sip_aor *aor;
+ char *aor_name;
+ char *aors_copy;
+
+ /* Direct match */
+ if ((aor = ast_sip_location_retrieve_aor(resource))) {
+ return aor;
+ }
+
+ if (!endpoint) {
+ return NULL;
+ }
+
+ /*
+ * This may be a subscribe to the voicemail_extension. If so,
+ * look for an aor belonging to this endpoint that has a matching
+ * voicemail_extension.
+ */
+ aors_copy = ast_strdupa(endpoint->aors);
+ while ((aor_name = ast_strip(strsep(&aors_copy, ",")))) {
+ struct ast_sip_aor *check_aor = ast_sip_location_retrieve_aor(aor_name);
+
+ if (!check_aor) {
+ continue;
+ }
+
+ if (!ast_strlen_zero(check_aor->voicemail_extension)
+ && !strcasecmp(check_aor->voicemail_extension, resource)) {
+ ast_debug(1, "Found an aor (%s) that matches voicemail_extension %s\n", aor_name, resource);
+ return check_aor;
+ }
+
+ ao2_ref(check_aor, -1);
+ }
+
+ return NULL;
+}
+
static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
struct ast_sip_message_accumulator *counter)
{
@@ -392,12 +478,6 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
"endpoint", sub->id), ao2_cleanup);
char *endpoint_aors;
char *aor_name;
- struct ast_sip_body body;
- struct ast_str *body_text;
- struct ast_sip_body_data body_data = {
- .body_type = AST_SIP_MESSAGE_ACCUMULATOR,
- .body_data = counter,
- };
if (!endpoint) {
ast_log(LOG_WARNING, "Unable to send unsolicited MWI to %s because endpoint does not exist\n",
@@ -410,23 +490,6 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
return;
}
- body.type = MWI_TYPE;
- body.subtype = MWI_SUBTYPE;
-
- body_text = ast_str_create(64);
-
- if (!body_text) {
- return;
- }
-
- if (ast_sip_pubsub_generate_body_content(body.type, body.subtype, &body_data, &body_text)) {
- ast_log(LOG_WARNING, "Unable to generate SIP MWI NOTIFY body.\n");
- ast_free(body_text);
- return;
- }
-
- body.body_text = ast_str_buffer(body_text);
-
endpoint_aors = ast_strdupa(endpoint->aors);
ast_debug(5, "Sending unsolicited MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n",
@@ -438,7 +501,7 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
struct unsolicited_mwi_data mwi_data = {
.sub = sub,
.endpoint = endpoint,
- .body = &body,
+ .counter = counter,
};
if (!aor) {
@@ -454,8 +517,6 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
ao2_callback(contacts, OBJ_NODATA, send_unsolicited_mwi_notify_to_contact, &mwi_data);
}
-
- ast_free(body_text);
}
static void send_mwi_notify(struct mwi_subscription *sub)
@@ -463,16 +524,30 @@ static void send_mwi_notify(struct mwi_subscription *sub)
struct ast_sip_message_accumulator counter = {
.old_msgs = 0,
.new_msgs = 0,
+ .message_account[0] = '\0',
};
struct ast_sip_body_data data = {
.body_type = AST_SIP_MESSAGE_ACCUMULATOR,
.body_data = &counter,
};
+ const char *resource = ast_sip_subscription_get_resource_name(sub->sip_sub);
ao2_callback(sub->stasis_subs, OBJ_NODATA, get_message_count, &counter);
if (sub->is_solicited) {
+ struct ast_sip_endpoint *endpoint = ast_sip_subscription_get_endpoint(sub->sip_sub);
+ struct ast_sip_aor *aor = find_aor_for_resource(endpoint, resource);
+ pjsip_dialog *dlg = ast_sip_subscription_get_dialog(sub->sip_sub);
+ pjsip_sip_uri *sip_uri = ast_sip_subscription_get_sip_uri(sub->sip_sub);
+
+ if (aor && dlg && sip_uri) {
+ set_voicemail_extension(dlg->pool, sip_uri, &counter, aor->voicemail_extension);
+ }
+
+ ao2_cleanup(aor);
+ ao2_cleanup(endpoint);
ast_sip_subscription_notify(sub->sip_sub, &data, 0);
+
return;
}
@@ -565,7 +640,12 @@ static int endpoint_receives_unsolicited_mwi_for_mailbox(struct ast_sip_endpoint
mwi_stasis = ao2_find(mwi_sub->stasis_subs, mailbox, OBJ_SEARCH_KEY);
if (mwi_stasis) {
- ret = 1;
+ if (endpoint->subscription.mwi.subscribe_replaces_unsolicited) {
+ unsubscribe_stasis(mwi_stasis, NULL, 0);
+ ao2_unlink(mwi_sub->stasis_subs, mwi_stasis);
+ } else {
+ ret = 1;
+ }
ao2_cleanup(mwi_stasis);
}
}
@@ -670,14 +750,9 @@ static struct mwi_subscription *mwi_subscribe_single(
struct ast_sip_aor *aor;
struct mwi_subscription *sub;
- aor = ast_sip_location_retrieve_aor(name);
+ aor = find_aor_for_resource(endpoint, name);
if (!aor) {
- /*! I suppose it's possible for the AOR to disappear on us
- * between accepting the subscription and sending the first
- * NOTIFY...
- */
- ast_log(LOG_WARNING, "Unable to locate aor %s. MWI subscription failed.\n",
- name);
+ ast_log(LOG_WARNING, "Unable to locate aor %s. MWI subscription failed.\n", name);
return NULL;
}
@@ -716,10 +791,9 @@ static int mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
return 200;
}
- aor = ast_sip_location_retrieve_aor(resource);
+ aor = find_aor_for_resource(endpoint, resource);
if (!aor) {
- ast_debug(1, "Unable to locate aor %s. MWI subscription failed.\n",
- resource);
+ ast_debug(1, "Unable to locate aor %s. MWI subscription failed.\n", resource);
return 404;
}
@@ -771,6 +845,8 @@ static void *mwi_get_notify_data(struct ast_sip_subscription *sub)
struct ast_sip_message_accumulator *counter;
struct mwi_subscription *mwi_sub;
struct ast_datastore *mwi_datastore;
+ struct ast_sip_aor *aor;
+ struct ast_sip_endpoint *endpoint = ast_sip_subscription_get_endpoint(sub);
mwi_datastore = ast_sip_subscription_get_datastore(sub, MWI_DATASTORE);
if (!mwi_datastore) {
@@ -784,6 +860,17 @@ static void *mwi_get_notify_data(struct ast_sip_subscription *sub)
return NULL;
}
+ if ((aor = find_aor_for_resource(endpoint, ast_sip_subscription_get_resource_name(sub)))) {
+ pjsip_dialog *dlg = ast_sip_subscription_get_dialog(sub);
+ pjsip_sip_uri *sip_uri = ast_sip_subscription_get_sip_uri(sub);
+
+ if (dlg && sip_uri) {
+ set_voicemail_extension(dlg->pool, sip_uri, counter, aor->voicemail_extension);
+ }
+ ao2_ref(aor, -1);
+ }
+ ao2_cleanup(endpoint);
+
ao2_callback(mwi_sub->stasis_subs, OBJ_NODATA, get_message_count, counter);
ao2_cleanup(mwi_datastore);
return counter;
@@ -966,9 +1053,14 @@ static int unsubscribe(void *obj, void *arg, int flags)
static void create_mwi_subscriptions(void)
{
struct ao2_container *endpoints;
+ struct ast_variable *var;
+
+ var = ast_variable_new("mailboxes !=", "", "");
endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "endpoint",
- AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+ AST_RETRIEVE_FLAG_MULTIPLE, var);
+
+ ast_variables_destroy(var);
if (!endpoints) {
return;
}
@@ -1079,6 +1171,16 @@ static void mwi_startup_event_cb(void *data, struct stasis_subscription *sub, st
stasis_unsubscribe(sub);
}
+static void global_loaded(const char *object_type)
+{
+ ast_free(default_voicemail_extension);
+ default_voicemail_extension = ast_sip_get_default_voicemail_extension();
+}
+
+static struct ast_sorcery_observer global_observer = {
+ .loaded = global_loaded,
+};
+
static int reload(void)
{
create_mwi_subscriptions();
@@ -1101,6 +1203,8 @@ static int load_module(void)
create_mwi_subscriptions();
ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &mwi_contact_observer);
+ ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
ast_sip_push_task(NULL, send_initial_notify_all, NULL);
@@ -1115,8 +1219,10 @@ static int unload_module(void)
{
ao2_callback(unsolicited_mwi, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe, NULL);
ao2_ref(unsolicited_mwi, -1);
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &mwi_contact_observer);
ast_sip_unregister_subscription_handler(&mwi_handler);
+ ast_free(default_voicemail_extension);
return 0;
}
diff --git a/res/res_pjsip_mwi_body_generator.c b/res/res_pjsip_mwi_body_generator.c
index e4b39d534..f46ce04e3 100644
--- a/res/res_pjsip_mwi_body_generator.c
+++ b/res/res_pjsip_mwi_body_generator.c
@@ -46,7 +46,7 @@ static void *mwi_allocate_body(void *data)
if (!mwi_str) {
return NULL;
}
- *mwi_str = ast_str_create(64);
+ *mwi_str = ast_str_create(128);
if (!*mwi_str) {
ast_free(mwi_str);
return NULL;
@@ -63,6 +63,9 @@ static int mwi_generate_body_content(void *body, void *data)
counter->new_msgs ? "yes" : "no");
ast_str_append(mwi, 0, "Voice-Message: %d/%d (0/0)\r\n",
counter->new_msgs, counter->old_msgs);
+ if (!ast_strlen_zero(counter->message_account)) {
+ ast_str_append(mwi, 0, "Message-Account: %s\r\n", counter->message_account);
+ }
return 0;
}
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 57ca95d8c..141c2fcf4 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -1644,6 +1644,12 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su
return sub;
}
+pjsip_dialog *ast_sip_subscription_get_dialog(struct ast_sip_subscription *sub)
+{
+ ast_assert(sub->tree->dlg != NULL);
+ return sub->tree->dlg;
+}
+
struct ast_sip_endpoint *ast_sip_subscription_get_endpoint(struct ast_sip_subscription *sub)
{
ast_assert(sub->tree->endpoint != NULL);
@@ -2271,6 +2277,11 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip
return res;
}
+pjsip_sip_uri *ast_sip_subscription_get_sip_uri(struct ast_sip_subscription *sub)
+{
+ return sub->uri;
+}
+
void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
{
pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, sub->uri, buf, size);
diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in
index 661652489..a75103bb6 100644
--- a/res/res_pjsip_pubsub.exports.in
+++ b/res/res_pjsip_pubsub.exports.in
@@ -1,44 +1,6 @@
{
global:
- LINKER_SYMBOL_PREFIXast_sip_create_subscription;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_endpoint;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_serializer;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_evsub;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_dlg;
- LINKER_SYMBOL_PREFIXast_sip_subscription_accept;
- LINKER_SYMBOL_PREFIXast_sip_subscription_send_request;
- LINKER_SYMBOL_PREFIXast_sip_subscription_alloc_datastore;
- LINKER_SYMBOL_PREFIXast_sip_subscription_add_datastore;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_datastore;
- LINKER_SYMBOL_PREFIXast_sip_subscription_remove_datastore;
- LINKER_SYMBOL_PREFIXast_sip_register_subscription_handler;
- LINKER_SYMBOL_PREFIXast_sip_unregister_subscription_handler;
- LINKER_SYMBOL_PREFIXast_sip_create_publication;
- LINKER_SYMBOL_PREFIXast_sip_publication_get_endpoint;
- LINKER_SYMBOL_PREFIXast_sip_publication_get_resource;
- LINKER_SYMBOL_PREFIXast_sip_publication_get_event_configuration;
- LINKER_SYMBOL_PREFIXast_sip_publication_create_response;
- LINKER_SYMBOL_PREFIXast_sip_publication_send_response;
- LINKER_SYMBOL_PREFIXast_sip_register_publish_handler;
- LINKER_SYMBOL_PREFIXast_sip_unregister_publish_handler;
- LINKER_SYMBOL_PREFIXast_sip_publication_add_datastore;
- LINKER_SYMBOL_PREFIXast_sip_publication_get_datastore;
- LINKER_SYMBOL_PREFIXast_sip_publication_remove_datastore;
- LINKER_SYMBOL_PREFIXast_sip_publication_remove_datastore;
- LINKER_SYMBOL_PREFIXast_sip_pubsub_register_body_generator;
- LINKER_SYMBOL_PREFIXast_sip_pubsub_unregister_body_generator;
- LINKER_SYMBOL_PREFIXast_sip_pubsub_register_body_supplement;
- LINKER_SYMBOL_PREFIXast_sip_pubsub_unregister_body_supplement;
- LINKER_SYMBOL_PREFIXast_sip_pubsub_generate_body_content;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_body_type;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_body_subtype;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_resource_name;
- LINKER_SYMBOL_PREFIXast_sip_subscription_notify;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_local_uri;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_remote_uri;
- LINKER_SYMBOL_PREFIXast_sip_subscription_get_header;
- LINKER_SYMBOL_PREFIXast_sip_subscription_is_terminated;
- LINKER_SYMBOL_PREFIXast_sip_subscription_destroy;
+ LINKER_SYMBOL_PREFIXast_sip_*;
local:
*;
};
diff --git a/res/res_pjsip_registrar_expire.c b/res/res_pjsip_registrar_expire.c
index 399e7bd0e..87edf5390 100644
--- a/res/res_pjsip_registrar_expire.c
+++ b/res/res_pjsip_registrar_expire.c
@@ -25,265 +25,102 @@
#include "asterisk.h"
#include <pjsip.h>
+#include <sys/time.h>
+#include <signal.h>
#include "asterisk/res_pjsip.h"
#include "asterisk/module.h"
-#include "asterisk/sched.h"
-#define CONTACT_AUTOEXPIRE_BUCKETS 977
+/*! \brief Thread keeping things alive */
+static pthread_t check_thread = AST_PTHREADT_NULL;
-static struct ao2_container *contact_autoexpire;
+/*! \brief The global interval at which to check for contact expiration */
+static unsigned int check_interval;
-/*! \brief Scheduler used for automatically expiring contacts */
-static struct ast_sched_context *sched;
-
-/*! \brief Structure used for contact auto-expiration */
-struct contact_expiration {
- /*! \brief Contact that is being auto-expired */
- struct ast_sip_contact *contact;
-
- /*! \brief Scheduled item for performing expiration */
- int sched;
-};
-
-/*! \brief Destructor function for contact auto-expiration */
-static void contact_expiration_destroy(void *obj)
-{
- struct contact_expiration *expiration = obj;
-
- ao2_cleanup(expiration->contact);
-}
-
-/*! \brief Hashing function for contact auto-expiration */
-static int contact_expiration_hash(const void *obj, const int flags)
+/*! \brief Callback function which deletes a contact */
+static int expire_contact(void *obj, void *arg, int flags)
{
- const struct contact_expiration *object;
- const char *key;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_KEY:
- key = obj;
- break;
- case OBJ_SEARCH_OBJECT:
- object = obj;
- key = ast_sorcery_object_get_id(object->contact);
- break;
- default:
- /* Hash can only work on something with a full key. */
- ast_assert(0);
- return 0;
- }
- return ast_str_hash(key);
-}
-
-/*! \brief Comparison function for contact auto-expiration */
-static int contact_expiration_cmp(void *obj, void *arg, int flags)
-{
- const struct contact_expiration *object_left = obj;
- const struct contact_expiration *object_right = arg;
- const char *right_key = arg;
- int cmp;
-
- switch (flags & OBJ_SEARCH_MASK) {
- case OBJ_SEARCH_OBJECT:
- right_key = ast_sorcery_object_get_id(object_right->contact);
- /* Fall through */
- case OBJ_SEARCH_KEY:
- cmp = strcmp(ast_sorcery_object_get_id(object_left->contact), right_key);
- break;
- case OBJ_SEARCH_PARTIAL_KEY:
- /*
- * We could also use a partial key struct containing a length
- * so strlen() does not get called for every comparison instead.
- */
- cmp = strncmp(ast_sorcery_object_get_id(object_left->contact), right_key,
- strlen(right_key));
- break;
- default:
- /*
- * What arg points to is specific to this traversal callback
- * and has no special meaning to astobj2.
- */
- cmp = 0;
- break;
- }
- if (cmp) {
- return 0;
- }
- /*
- * At this point the traversal callback is identical to a sorted
- * container.
- */
- return CMP_MATCH;
-}
-
-/*! \brief Scheduler function which deletes a contact */
-static int contact_expiration_expire(const void *data)
-{
- struct contact_expiration *expiration = (void *) data;
+ struct ast_sip_contact *contact = obj;
- expiration->sched = -1;
+ ast_sorcery_delete(ast_sip_get_sorcery(), contact);
- /* This will end up invoking the deleted observer callback, which will perform the unlinking and such */
- ast_sorcery_delete(ast_sip_get_sorcery(), expiration->contact);
- ao2_ref(expiration, -1);
return 0;
}
-/*! \brief Observer callback for when a contact is created */
-static void contact_expiration_observer_created(const void *object)
+static void *check_expiration_thread(void *data)
{
- const struct ast_sip_contact *contact = object;
- struct contact_expiration *expiration;
- int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow()));
+ struct ao2_container *contacts;
+ struct ast_variable *var;
+ char *time = alloca(64);
- if (ast_tvzero(contact->expiration_time)) {
- return;
- }
+ while (check_interval) {
+ sleep(check_interval);
- expiration = ao2_alloc_options(sizeof(*expiration), contact_expiration_destroy,
- AO2_ALLOC_OPT_LOCK_NOLOCK);
- if (!expiration) {
- return;
- }
+ sprintf(time, "%ld", ast_tvnow().tv_sec);
+ var = ast_variable_new("expiration_time <=", time, "");
- expiration->contact = (struct ast_sip_contact*)contact;
- ao2_ref(expiration->contact, +1);
+ ast_debug(4, "Woke up at %s Interval: %d\n", time, check_interval);
- ao2_ref(expiration, +1);
- if ((expiration->sched = ast_sched_add(sched, expires, contact_expiration_expire, expiration)) < 0) {
- ao2_ref(expiration, -1);
- ast_log(LOG_ERROR, "Scheduled expiration for contact '%s' could not be performed, contact may persist past life\n",
- ast_sorcery_object_get_id(contact));
- } else {
- ao2_link(contact_autoexpire, expiration);
- }
- ao2_ref(expiration, -1);
-}
-
-/*! \brief Observer callback for when a contact is updated */
-static void contact_expiration_observer_updated(const void *object)
-{
- const struct ast_sip_contact *contact = object;
- struct contact_expiration *expiration;
- int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow()));
+ contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact",
+ AST_RETRIEVE_FLAG_MULTIPLE, var);
- expiration = ao2_find(contact_autoexpire, ast_sorcery_object_get_id(contact),
- OBJ_SEARCH_KEY);
- if (!expiration) {
- return;
+ ast_variables_destroy(var);
+ if (contacts) {
+ ast_debug(3, "Expiring %d contacts\n\n", ao2_container_count(contacts));
+ ao2_callback(contacts, OBJ_NODATA, expire_contact, NULL);
+ ao2_ref(contacts, -1);
+ }
}
- AST_SCHED_REPLACE_UNREF(expiration->sched, sched, expires, contact_expiration_expire,
- expiration, ao2_cleanup(expiration), ao2_cleanup(expiration), ao2_ref(expiration, +1));
- ao2_ref(expiration, -1);
+ return NULL;
}
-/*! \brief Observer callback for when a contact is deleted */
-static void contact_expiration_observer_deleted(const void *object)
+static void expiration_global_loaded(const char *object_type)
{
- struct contact_expiration *expiration;
-
- expiration = ao2_find(contact_autoexpire, ast_sorcery_object_get_id(object),
- OBJ_SEARCH_KEY | OBJ_UNLINK);
- if (!expiration) {
- return;
- }
-
- AST_SCHED_DEL_UNREF(sched, expiration->sched, ao2_cleanup(expiration));
- ao2_ref(expiration, -1);
-}
-
-/*! \brief Observer callbacks for autoexpiring contacts */
-static const struct ast_sorcery_observer contact_expiration_observer = {
- .created = contact_expiration_observer_created,
- .updated = contact_expiration_observer_updated,
- .deleted = contact_expiration_observer_deleted,
-};
-
-/*! \brief Callback function which deletes a contact if it has expired or sets up auto-expiry */
-static int contact_expiration_setup(void *obj, void *arg, int flags)
-{
- struct ast_sip_contact *contact = obj;
- int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow()));
-
- if (!expires) {
- ast_sorcery_delete(ast_sip_get_sorcery(), contact);
+ check_interval = ast_sip_get_contact_expiration_check_interval();
+
+ /* Observer calls are serialized so this is safe without it's own lock */
+ if (check_interval) {
+ if (check_thread == AST_PTHREADT_NULL) {
+ if (ast_pthread_create_background(&check_thread, NULL, check_expiration_thread, NULL)) {
+ ast_log(LOG_ERROR, "Could not create thread for checking contact expiration.\n");
+ return;
+ }
+ ast_debug(3, "Interval = %d, starting thread\n", check_interval);
+ }
} else {
- contact_expiration_observer_created(contact);
- }
-
- return 0;
-}
-
-/*! \brief Initialize auto-expiration of any existing contacts */
-static void contact_expiration_initialize_existing(void)
-{
- struct ao2_container *contacts;
-
- contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact",
- AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
- if (!contacts) {
- return;
+ if (check_thread != AST_PTHREADT_NULL) {
+ pthread_kill(check_thread, SIGURG);
+ check_thread = AST_PTHREADT_NULL;
+ ast_debug(3, "Interval = 0, shutting thread down\n");
+ }
}
-
- ao2_callback(contacts, OBJ_NODATA, contact_expiration_setup, NULL);
- ao2_ref(contacts, -1);
}
-static int unload_observer_delete(void *obj, void *arg, int flags)
-{
- struct contact_expiration *expiration = obj;
-
- AST_SCHED_DEL_UNREF(sched, expiration->sched, ao2_cleanup(expiration));
- return CMP_MATCH;
-}
+/*! \brief Observer which is used to update our interval when the global setting changes */
+static struct ast_sorcery_observer expiration_global_observer = {
+ .loaded = expiration_global_loaded,
+};
static int unload_module(void)
{
- ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_expiration_observer);
- if (sched) {
- ao2_callback(contact_autoexpire, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK,
- unload_observer_delete, NULL);
- ast_sched_context_destroy(sched);
- sched = NULL;
+ if (check_thread != AST_PTHREADT_NULL) {
+ pthread_kill(check_thread, SIGURG);
+ check_thread = AST_PTHREADT_NULL;
}
- ao2_cleanup(contact_autoexpire);
- contact_autoexpire = NULL;
+
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &expiration_global_observer);
return 0;
}
+
static int load_module(void)
{
CHECK_PJSIP_MODULE_LOADED();
- contact_autoexpire = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK,
- CONTACT_AUTOEXPIRE_BUCKETS, contact_expiration_hash, contact_expiration_cmp);
- if (!contact_autoexpire) {
- ast_log(LOG_ERROR, "Could not create container for contact auto-expiration\n");
- return AST_MODULE_LOAD_FAILURE;
- }
-
- if (!(sched = ast_sched_context_create())) {
- ast_log(LOG_ERROR, "Could not create scheduler for contact auto-expiration\n");
- unload_module();
- return AST_MODULE_LOAD_FAILURE;
- }
-
- if (ast_sched_start_thread(sched)) {
- ast_log(LOG_ERROR, "Could not start scheduler thread for contact auto-expiration\n");
- unload_module();
- return AST_MODULE_LOAD_FAILURE;
- }
-
- contact_expiration_initialize_existing();
-
- if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_expiration_observer)) {
- ast_log(LOG_ERROR, "Could not add observer for notifications about contacts for contact auto-expiration\n");
- unload_module();
- return AST_MODULE_LOAD_FAILURE;
- }
+ ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &expiration_global_observer);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
return AST_MODULE_LOAD_SUCCESS;
}
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 789535ef0..9e3ff757c 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -2273,6 +2273,7 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
if (res > 0) {
ast_rtp_instance_set_last_tx(instance, time(NULL));
}
+
return res;
}
@@ -2283,7 +2284,16 @@ static int rtcp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size
static int rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int *ice)
{
- return __rtp_sendto(instance, buf, size, flags, sa, 0, ice, 1);
+ struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+ int hdrlen = 12;
+ int res;
+
+ if ((res = __rtp_sendto(instance, buf, size, flags, sa, 0, ice, 1)) > 0) {
+ rtp->txcount++;
+ rtp->txoctetcount += (res - hdrlen);
+ }
+
+ return res;
}
static int rtp_get_rate(struct ast_format *format)
@@ -3355,9 +3365,6 @@ static int ast_rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame
ast_set_flag(rtp, FLAG_NAT_INACTIVE_NOWARN);
}
} else {
- rtp->txcount++;
- rtp->txoctetcount += (res - hdrlen);
-
if (rtp->rtcp && rtp->rtcp->schedid < 0) {
ast_debug(1, "Starting RTCP transmission on RTP instance '%p'\n", instance);
ao2_ref(instance, +1);
@@ -4290,6 +4297,9 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance, unsigned int
return -1;
}
+ rtp->rxcount++;
+ rtp->rxoctetcount += (len - hdrlen);
+
/* If the payload coming in is not one of the negotiated ones then send it to the core, this will cause formats to change and the bridge to break */
if (ast_rtp_codecs_find_payload_code(ast_rtp_instance_get_codecs(instance1), bridged_payload) == -1) {
ast_debug(1, "Unsupported payload type received \n");
@@ -4520,6 +4530,7 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
rtp->seedrxseqno = 0;
rtp->rxcount = 0;
+ rtp->rxoctetcount = 0;
rtp->cycles = 0;
rtp->lastrxseqno = 0;
rtp->last_seqno = 0;
@@ -4563,6 +4574,7 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
}
rtp->rxcount++;
+ rtp->rxoctetcount += (res - hdrlen);
if (rtp->rxcount == 1) {
rtp->seedrxseqno = seqno;
}
@@ -4964,6 +4976,8 @@ static int ast_rtp_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_in
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXCOUNT, -1, stats->txcount, rtp->txcount);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXCOUNT, -1, stats->rxcount, rtp->rxcount);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXOCTETCOUNT, -1, stats->txoctetcount, rtp->txoctetcount);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXOCTETCOUNT, -1, stats->rxoctetcount, rtp->rxoctetcount);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXPLOSS, AST_RTP_INSTANCE_STAT_COMBINED_LOSS, stats->txploss, rtp->rtcp->reported_lost);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXPLOSS, AST_RTP_INSTANCE_STAT_COMBINED_LOSS, stats->rxploss, rtp->rtcp->expected_prior - rtp->rtcp->received_prior);
diff --git a/res/res_sorcery_astdb.c b/res/res_sorcery_astdb.c
index 4e2c3a809..b3642d81e 100644
--- a/res/res_sorcery_astdb.c
+++ b/res/res_sorcery_astdb.c
@@ -63,65 +63,6 @@ static struct ast_sorcery_wizard astdb_object_wizard = {
.close = sorcery_astdb_close,
};
-/*! \brief Helper function which converts from a sorcery object set to a json object */
-static struct ast_json *sorcery_objectset_to_json(const struct ast_variable *objectset)
-{
- struct ast_json *json = ast_json_object_create();
- const struct ast_variable *field;
-
- for (field = objectset; field; field = field->next) {
- struct ast_json *value = ast_json_string_create(field->value);
-
- if (!value) {
- ast_json_unref(json);
- return NULL;
- } else if (ast_json_object_set(json, field->name, value)) {
- ast_json_unref(json);
- return NULL;
- }
- }
-
- return json;
-}
-
-/*! \brief Helper function which converts a json object to a sorcery object set */
-static struct ast_variable *sorcery_json_to_objectset(struct ast_json *json)
-{
- struct ast_json_iter *field;
- struct ast_variable *objset = NULL;
-
- for (field = ast_json_object_iter(json); field; field = ast_json_object_iter_next(json, field)) {
- struct ast_json *value = ast_json_object_iter_value(field);
- struct ast_variable *variable = ast_variable_new(ast_json_object_iter_key(field), ast_json_string_get(value), "");
-
- if (!variable) {
- ast_variables_destroy(objset);
- return NULL;
- }
-
- variable->next = objset;
- objset = variable;
- }
-
- return objset;
-}
-
-/*! \brief Helper function which compares two json objects and sees if they are equal, but only looks at the criteria provided */
-static int sorcery_json_equal(struct ast_json *object, struct ast_json *criteria)
-{
- struct ast_json_iter *field;
-
- for (field = ast_json_object_iter(criteria); field; field = ast_json_object_iter_next(criteria, field)) {
- struct ast_json *object_field = ast_json_object_get(object, ast_json_object_iter_key(field));
-
- if (!object_field || !ast_json_equal(object_field, ast_json_object_iter_value(field))) {
- return 0;
- }
- }
-
- return 1;
-}
-
static int sorcery_astdb_create(const struct ast_sorcery *sorcery, void *data, void *object)
{
RAII_VAR(struct ast_json *, objset, ast_sorcery_objectset_json_create(sorcery, object), ast_json_unref);
@@ -144,12 +85,11 @@ static void *sorcery_astdb_retrieve_fields_common(const struct ast_sorcery *sorc
const char *prefix = data;
char family[strlen(prefix) + strlen(type) + 2];
RAII_VAR(struct ast_db_entry *, entries, NULL, ast_db_freetree);
- RAII_VAR(struct ast_json *, criteria, NULL, ast_json_unref);
struct ast_db_entry *entry;
snprintf(family, sizeof(family), "%s/%s", prefix, type);
- if (!(entries = ast_db_gettree(family, NULL)) || (fields && !(criteria = sorcery_objectset_to_json(fields)))) {
+ if (!(entries = ast_db_gettree(family, NULL))) {
return NULL;
}
@@ -158,14 +98,21 @@ static void *sorcery_astdb_retrieve_fields_common(const struct ast_sorcery *sorc
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
struct ast_json_error error;
RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
+ RAII_VAR(struct ast_variable *, existing, NULL, ast_variables_destroy);
void *object = NULL;
if (!(json = ast_json_load_string(entry->data, &error))) {
return NULL;
- } else if (criteria && !sorcery_json_equal(json, criteria)) {
+ }
+ if (ast_json_to_ast_variables(json, &existing) != AST_JSON_TO_AST_VARS_CODE_SUCCESS) {
+ return NULL;
+ }
+
+ if (fields && !ast_variable_lists_match(existing, fields, 0)) {
continue;
- } else if (!(objset = sorcery_json_to_objectset(json)) ||
- !(object = ast_sorcery_alloc(sorcery, type, key)) ||
+ }
+
+ if (!(object = ast_sorcery_alloc(sorcery, type, key)) ||
ast_sorcery_objectset_apply(sorcery, object, objset)) {
ao2_cleanup(object);
return NULL;
@@ -199,9 +146,11 @@ static void *sorcery_astdb_retrieve_id(const struct ast_sorcery *sorcery, void *
snprintf(family, sizeof(family), "%s/%s", prefix, type);
- if (ast_db_get_allocated(family, id, &value) || !(json = ast_json_load_string(value, &error)) ||
- !(objset = sorcery_json_to_objectset(json)) || !(object = ast_sorcery_alloc(sorcery, type, id)) ||
- ast_sorcery_objectset_apply(sorcery, object, objset)) {
+ if (ast_db_get_allocated(family, id, &value)
+ || !(json = ast_json_load_string(value, &error))
+ || (ast_json_to_ast_variables(json, &objset) != AST_JSON_TO_AST_VARS_CODE_SUCCESS)
+ || !(object = ast_sorcery_alloc(sorcery, type, id))
+ || ast_sorcery_objectset_apply(sorcery, object, objset)) {
ast_debug(3, "Failed to retrieve object '%s' from astdb\n", id);
ao2_cleanup(object);
return NULL;
@@ -310,10 +259,10 @@ static void sorcery_astdb_retrieve_regex(const struct ast_sorcery *sorcery, void
if (regexec(&expression, key, 0, NULL, 0)) {
continue;
- } else if (!(json = ast_json_load_string(entry->data, &error)) ||
- !(objset = sorcery_json_to_objectset(json)) ||
- !(object = ast_sorcery_alloc(sorcery, type, key)) ||
- ast_sorcery_objectset_apply(sorcery, object, objset)) {
+ } else if (!(json = ast_json_load_string(entry->data, &error))
+ || (ast_json_to_ast_variables(json, &objset) != AST_JSON_TO_AST_VARS_CODE_SUCCESS)
+ || !(object = ast_sorcery_alloc(sorcery, type, key))
+ || ast_sorcery_objectset_apply(sorcery, object, objset)) {
regfree(&expression);
return;
}
diff --git a/res/res_sorcery_config.c b/res/res_sorcery_config.c
index 092cc41c8..dd4ea8886 100644
--- a/res/res_sorcery_config.c
+++ b/res/res_sorcery_config.c
@@ -129,7 +129,6 @@ static int sorcery_config_fields_cmp(void *obj, void *arg, int flags)
{
const struct sorcery_config_fields_cmp_params *params = arg;
RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
- RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy);
if (params->regex) {
/* If a regular expression has been provided see if it matches, otherwise move on */
@@ -139,11 +138,10 @@ static int sorcery_config_fields_cmp(void *obj, void *arg, int flags)
return 0;
} else if (params->fields &&
(!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) ||
- (ast_sorcery_changeset_create(objset, params->fields, &diff)) ||
- diff)) {
+ (!ast_variable_lists_match(objset, params->fields, 0)))) {
/* If we can't turn the object into an object set OR if differences exist between the fields
- * passed in and what are present on the object they are not a match.
- */
+ * passed in and what are present on the object they are not a match.
+ */
return 0;
}
@@ -197,6 +195,7 @@ static void sorcery_config_retrieve_multiple(const struct ast_sorcery *sorcery,
if (!config_objects) {
return;
}
+
ao2_callback(config_objects, 0, sorcery_config_fields_cmp, &params);
}
diff --git a/res/res_sorcery_memory.c b/res/res_sorcery_memory.c
index 95cb24835..db1fc1ab8 100644
--- a/res/res_sorcery_memory.c
+++ b/res/res_sorcery_memory.c
@@ -120,7 +120,6 @@ static int sorcery_memory_fields_cmp(void *obj, void *arg, int flags)
{
const struct sorcery_memory_fields_cmp_params *params = arg;
RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
- RAII_VAR(struct ast_variable *, diff, NULL, ast_variables_destroy);
if (params->regex) {
/* If a regular expression has been provided see if it matches, otherwise move on */
@@ -130,8 +129,7 @@ static int sorcery_memory_fields_cmp(void *obj, void *arg, int flags)
return 0;
} else if (params->fields &&
(!(objset = ast_sorcery_objectset_create(params->sorcery, obj)) ||
- (ast_sorcery_changeset_create(objset, params->fields, &diff)) ||
- diff)) {
+ (!ast_variable_lists_match(objset, params->fields, 0)))) {
/* If we can't turn the object into an object set OR if differences exist between the fields
* passed in and what are present on the object they are not a match.
*/
diff --git a/res/res_sorcery_memory_cache.c b/res/res_sorcery_memory_cache.c
index 704372e12..f1fb3c38c 100644
--- a/res/res_sorcery_memory_cache.c
+++ b/res/res_sorcery_memory_cache.c
@@ -1253,8 +1253,7 @@ static int sorcery_memory_cache_fields_cmp(void *obj, void *arg, int flags)
}
return 0;
} else if (params->fields &&
- (ast_sorcery_changeset_create(cached->objectset, params->fields, &diff) ||
- diff)) {
+ (!ast_variable_lists_match(cached->objectset, params->fields, 0))) {
/* If we can't turn the object into an object set OR if differences exist between the fields
* passed in and what are present on the object they are not a match.
*/
diff --git a/res/res_sorcery_realtime.c b/res/res_sorcery_realtime.c
index 83736a102..abf2840fb 100644
--- a/res/res_sorcery_realtime.c
+++ b/res/res_sorcery_realtime.c
@@ -40,6 +40,18 @@ ASTERISK_REGISTER_FILE()
/*! \brief They key field used to store the unique identifier for the object */
#define UUID_FIELD "id"
+enum unqualified_fetch {
+ UNQUALIFIED_FETCH_NO,
+ UNQUALIFIED_FETCH_WARN,
+ UNQUALIFIED_FETCH_YES,
+ UNQUALIFIED_FETCH_ERROR,
+};
+
+struct sorcery_config {
+ enum unqualified_fetch fetch;
+ char family[];
+};
+
static void *sorcery_realtime_open(const char *data);
static int sorcery_realtime_create(const struct ast_sorcery *sorcery, void *data, void *object);
static void *sorcery_realtime_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id);
@@ -66,7 +78,7 @@ static struct ast_sorcery_wizard realtime_object_wizard = {
static int sorcery_realtime_create(const struct ast_sorcery *sorcery, void *data, void *object)
{
- const char *family = data;
+ struct sorcery_config *config = data;
RAII_VAR(struct ast_variable *, fields, ast_sorcery_objectset_create(sorcery, object), ast_variables_destroy);
struct ast_variable *id = ast_variable_new(UUID_FIELD, ast_sorcery_object_get_id(object), "");
@@ -79,7 +91,7 @@ static int sorcery_realtime_create(const struct ast_sorcery *sorcery, void *data
id->next = fields;
fields = id;
- return (ast_store_realtime_fields(family, fields) <= 0) ? -1 : 0;
+ return (ast_store_realtime_fields(config->family, fields) <= 0) ? -1 : 0;
}
/*! \brief Internal helper function which returns a filtered objectset.
@@ -149,12 +161,12 @@ static struct ast_variable *sorcery_realtime_filter_objectset(struct ast_variabl
static void *sorcery_realtime_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields)
{
- const char *family = data;
+ struct sorcery_config *config = data;
RAII_VAR(struct ast_variable *, objectset, NULL, ast_variables_destroy);
RAII_VAR(struct ast_variable *, id, NULL, ast_variables_destroy);
void *object = NULL;
- if (!(objectset = ast_load_realtime_fields(family, fields))) {
+ if (!(objectset = ast_load_realtime_fields(config->family, fields))) {
return NULL;
}
@@ -178,7 +190,7 @@ static void *sorcery_realtime_retrieve_id(const struct ast_sorcery *sorcery, voi
static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields)
{
- const char *family = data;
+ struct sorcery_config *config = data;
RAII_VAR(struct ast_config *, rows, NULL, ast_config_destroy);
RAII_VAR(struct ast_variable *, all, NULL, ast_variables_destroy);
struct ast_category *row = NULL;
@@ -186,6 +198,18 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery
if (!fields) {
char field[strlen(UUID_FIELD) + 6], value[2];
+ if (config->fetch == UNQUALIFIED_FETCH_NO) {
+ return;
+ }
+ if (config->fetch == UNQUALIFIED_FETCH_ERROR) {
+ ast_log(LOG_ERROR, "Unqualified fetch prevented on %s\n", config->family);
+ return;
+ }
+ if (config->fetch == UNQUALIFIED_FETCH_WARN) {
+ ast_log(LOG_WARNING, "Unqualified fetch attempted on %s\n", config->family);
+ return;
+ }
+
/* If no fields have been specified we want all rows, so trick realtime into doing it */
snprintf(field, sizeof(field), "%s LIKE", UUID_FIELD);
snprintf(value, sizeof(value), "%%");
@@ -197,7 +221,7 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery
fields = all;
}
- if (!(rows = ast_load_realtime_multientry_fields(family, fields))) {
+ if (!(rows = ast_load_realtime_multientry_fields(config->family, fields))) {
return;
}
@@ -221,16 +245,18 @@ static void sorcery_realtime_retrieve_regex(const struct ast_sorcery *sorcery, v
char field[strlen(UUID_FIELD) + 6], value[strlen(regex) + 3];
RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy);
- /* The realtime API provides no direct ability to do regex so for now we support a limited subset using pattern matching */
- snprintf(field, sizeof(field), "%s LIKE", UUID_FIELD);
- if (regex[0] == '^') {
- snprintf(value, sizeof(value), "%s%%", regex + 1);
- } else {
- snprintf(value, sizeof(value), "%%%s%%", regex);
- }
+ if (!ast_strlen_zero(regex)) {
+ /* The realtime API provides no direct ability to do regex so for now we support a limited subset using pattern matching */
+ snprintf(field, sizeof(field), "%s LIKE", UUID_FIELD);
+ if (regex[0] == '^') {
+ snprintf(value, sizeof(value), "%s%%", regex + 1);
+ } else {
+ snprintf(value, sizeof(value), "%%%s%%", regex);
+ }
- if (!(fields = ast_variable_new(field, value, ""))) {
- return;
+ if (!(fields = ast_variable_new(field, value, ""))) {
+ return;
+ }
}
sorcery_realtime_retrieve_multiple(sorcery, data, type, objects, fields);
@@ -238,31 +264,74 @@ static void sorcery_realtime_retrieve_regex(const struct ast_sorcery *sorcery, v
static int sorcery_realtime_update(const struct ast_sorcery *sorcery, void *data, void *object)
{
- const char *family = data;
+ struct sorcery_config *config = data;
RAII_VAR(struct ast_variable *, fields, ast_sorcery_objectset_create(sorcery, object), ast_variables_destroy);
if (!fields) {
return -1;
}
- return (ast_update_realtime_fields(family, UUID_FIELD, ast_sorcery_object_get_id(object), fields) <= 0) ? -1 : 0;
+ return (ast_update_realtime_fields(config->family, UUID_FIELD, ast_sorcery_object_get_id(object), fields) <= 0) ? -1 : 0;
}
static int sorcery_realtime_delete(const struct ast_sorcery *sorcery, void *data, void *object)
{
- const char *family = data;
+ struct sorcery_config *config = data;
- return (ast_destroy_realtime_fields(family, UUID_FIELD, ast_sorcery_object_get_id(object), NULL) <= 0) ? -1 : 0;
+ return (ast_destroy_realtime_fields(config->family, UUID_FIELD, ast_sorcery_object_get_id(object), NULL) <= 0) ? -1 : 0;
}
static void *sorcery_realtime_open(const char *data)
{
+ struct sorcery_config *config;
+ char *tmp;
+ char *family;
+ char *option;
+
/* We require a prefix for family string generation, or else stuff could mix together */
- if (ast_strlen_zero(data) || !ast_realtime_is_mapping_defined(data)) {
+ if (ast_strlen_zero(data)) {
+ return NULL;
+ }
+
+ tmp = ast_strdupa(data);
+ family = strsep(&tmp, ",");
+
+ if (!ast_realtime_is_mapping_defined(family)) {
+ return NULL;
+ }
+
+ config = ast_calloc(1, sizeof(*config) + strlen(family) + 1);
+ if (!config) {
return NULL;
}
- return ast_strdup(data);
+ strcpy(config->family, family); /* Safe */
+ config->fetch = UNQUALIFIED_FETCH_YES;
+
+ while ((option = strsep(&tmp, ","))) {
+ char *name = strsep(&option, "=");
+ char *value = option;
+
+ if (!strcasecmp(name, "allow_unqualified_fetch")) {
+ if (ast_strlen_zero(value) || !strcasecmp(value, "yes")) {
+ config->fetch = UNQUALIFIED_FETCH_YES;
+ } else if (!strcasecmp(value, "no")) {
+ config->fetch = UNQUALIFIED_FETCH_NO;
+ } else if (!strcasecmp(value, "warn")) {
+ config->fetch = UNQUALIFIED_FETCH_WARN;
+ } else if (!strcasecmp(value, "error")) {
+ config->fetch = UNQUALIFIED_FETCH_ERROR;
+ } else {
+ ast_log(LOG_ERROR, "Unrecognized value in %s:%s: '%s'\n", family, name, value);
+ return NULL;
+ }
+ } else {
+ ast_log(LOG_ERROR, "Unrecognized option in %s: '%s'\n", family, name);
+ return NULL;
+ }
+ }
+
+ return config;
}
static void sorcery_realtime_close(void *data)
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 63c565d44..5aa0aa9ac 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1179,6 +1179,11 @@ int stasis_app_control_is_done(struct stasis_app_control *control)
return control_is_done(control);
}
+void stasis_app_control_flush_queue(struct stasis_app_control *control)
+{
+ control_flush_queue(control);
+}
+
struct ast_datastore_info set_end_published_info = {
.type = "stasis_end_published",
};
@@ -1188,10 +1193,11 @@ void stasis_app_channel_set_stasis_end_published(struct ast_channel *chan)
struct ast_datastore *datastore;
datastore = ast_datastore_alloc(&set_end_published_info, NULL);
-
- ast_channel_lock(chan);
- ast_channel_datastore_add(chan, datastore);
- ast_channel_unlock(chan);
+ if (datastore) {
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+ }
}
int stasis_app_channel_is_stasis_end_published(struct ast_channel *chan)
@@ -1211,12 +1217,11 @@ static void remove_stasis_end_published(struct ast_channel *chan)
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &set_end_published_info, NULL);
- ast_channel_unlock(chan);
-
if (datastore) {
ast_channel_datastore_remove(chan, datastore);
ast_datastore_free(datastore);
}
+ ast_channel_unlock(chan);
}
/*! /brief Stasis dialplan application callback */
@@ -1371,6 +1376,11 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
remove_stasis_end_published(chan);
}
+ control_flush_queue(control);
+
+ /* Stop any lingering silence generator */
+ control_silence_stop_now(control);
+
/* There's an off chance that app is ready for cleanup. Go ahead
* and clean up, just in case
*/
diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c
index 74336abdc..97191c26d 100644
--- a/res/res_stasis_playback.c
+++ b/res/res_stasis_playback.c
@@ -118,6 +118,7 @@ static void playback_dtor(void *obj)
{
struct stasis_app_playback *playback = obj;
+ ao2_cleanup(playback->control);
ast_string_field_free_memory(playback);
}
@@ -143,6 +144,7 @@ static struct stasis_app_playback *playback_create(
ast_string_field_set(playback, id, uuid);
}
+ ao2_ref(control, +1);
playback->control = control;
ao2_ref(playback, +1);
diff --git a/res/res_stasis_recording.c b/res/res_stasis_recording.c
index dcabfa699..af5c41e87 100644
--- a/res/res_stasis_recording.c
+++ b/res/res_stasis_recording.c
@@ -265,7 +265,13 @@ static enum stasis_app_control_channel_result check_rule_recording(
return STASIS_APP_CHANNEL_RECORDING;
}
-struct stasis_app_control_rule rule_recording = {
+/*
+ * XXX This only works because there is one and only one rule in
+ * the system so it can be added to any number of channels
+ * without issue. However, as soon as there is another rule then
+ * watch out for weirdness because of cross linked lists.
+ */
+static struct stasis_app_control_rule rule_recording = {
.check_rule = check_rule_recording
};
@@ -358,6 +364,7 @@ static void recording_dtor(void *obj)
struct stasis_app_recording *recording = obj;
ast_free(recording->absolute_name);
+ ao2_cleanup(recording->control);
ao2_cleanup(recording->options);
}
@@ -413,6 +420,7 @@ struct stasis_app_recording *stasis_app_control_record(
ao2_ref(options, +1);
recording->options = options;
+ ao2_ref(control, +1);
recording->control = control;
recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
@@ -465,15 +473,7 @@ const char *stasis_app_recording_get_name(
struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
{
- RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
-
- recording = ao2_find(recordings, name, OBJ_KEY);
- if (recording == NULL) {
- return NULL;
- }
-
- ao2_ref(recording, +1);
- return recording;
+ return ao2_find(recordings, name, OBJ_KEY);
}
struct ast_json *stasis_app_recording_to_json(
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 41d538cbe..86f94423d 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -87,21 +87,19 @@ static void control_dtor(void *obj)
{
struct stasis_app_control *control = obj;
- AST_LIST_HEAD_DESTROY(&control->add_rules);
- AST_LIST_HEAD_DESTROY(&control->remove_rules);
+ ao2_cleanup(control->command_queue);
- /* We may have a lingering silence generator; free it */
- ast_channel_stop_silence_generator(control->channel, control->silgen);
- control->silgen = NULL;
+ ast_channel_cleanup(control->channel);
+ ao2_cleanup(control->app);
- ao2_cleanup(control->command_queue);
ast_cond_destroy(&control->wait_cond);
- ao2_cleanup(control->app);
+ AST_LIST_HEAD_DESTROY(&control->add_rules);
+ AST_LIST_HEAD_DESTROY(&control->remove_rules);
}
struct stasis_app_control *control_create(struct ast_channel *channel, struct stasis_app *app)
{
- RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+ struct stasis_app_control *control;
int res;
control = ao2_alloc(sizeof(*control), control_dtor);
@@ -109,28 +107,29 @@ struct stasis_app_control *control_create(struct ast_channel *channel, struct st
return NULL;
}
- control->app = ao2_bump(app);
+ AST_LIST_HEAD_INIT(&control->add_rules);
+ AST_LIST_HEAD_INIT(&control->remove_rules);
res = ast_cond_init(&control->wait_cond, NULL);
if (res != 0) {
ast_log(LOG_ERROR, "Error initializing ast_cond_t: %s\n",
strerror(errno));
+ ao2_ref(control, -1);
return NULL;
}
+ control->app = ao2_bump(app);
+
+ ast_channel_ref(channel);
+ control->channel = channel;
+
control->command_queue = ao2_container_alloc_list(
AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
-
if (!control->command_queue) {
+ ao2_ref(control, -1);
return NULL;
}
- control->channel = channel;
-
- AST_LIST_HEAD_INIT(&control->add_rules);
- AST_LIST_HEAD_INIT(&control->remove_rules);
-
- ao2_ref(control, +1);
return control;
}
@@ -252,6 +251,11 @@ static struct stasis_app_command *exec_command_on_condition(
}
ao2_lock(control->command_queue);
+ if (control->is_done) {
+ ao2_unlock(control->command_queue);
+ ao2_ref(command, -1);
+ return NULL;
+ }
if (can_exec_fn && (retval = can_exec_fn(control))) {
ao2_unlock(control->command_queue);
command_complete(command, retval);
@@ -403,7 +407,10 @@ int control_is_done(struct stasis_app_control *control)
void control_mark_done(struct stasis_app_control *control)
{
+ /* Locking necessary to sync with other threads adding commands to the queue. */
+ ao2_lock(control->command_queue);
control->is_done = 1;
+ ao2_unlock(control->command_queue);
}
struct stasis_app_control_continue_data {
@@ -428,7 +435,7 @@ static int app_control_continue(struct stasis_app_control *control,
/* Called from stasis_app_exec thread; no lock needed */
ast_explicit_goto(control->channel, continue_data->context, continue_data->extension, continue_data->priority);
- control->is_done = 1;
+ control_mark_done(control);
return 0;
}
@@ -785,8 +792,7 @@ void stasis_app_control_silence_start(struct stasis_app_control *control)
stasis_app_send_command_async(control, app_control_silence_start, NULL, NULL);
}
-static int app_control_silence_stop(struct stasis_app_control *control,
- struct ast_channel *chan, void *data)
+void control_silence_stop_now(struct stasis_app_control *control)
{
if (control->silgen) {
ast_debug(3, "%s: Stopping silence generator\n",
@@ -795,7 +801,12 @@ static int app_control_silence_stop(struct stasis_app_control *control,
control->channel, control->silgen);
control->silgen = NULL;
}
+}
+static int app_control_silence_stop(struct stasis_app_control *control,
+ struct ast_channel *chan, void *data)
+{
+ control_silence_stop_now(control);
return 0;
}
@@ -1112,24 +1123,36 @@ int stasis_app_control_queue_control(struct stasis_app_control *control,
return ast_queue_control(control->channel, frame_type);
}
+void control_flush_queue(struct stasis_app_control *control)
+{
+ struct ao2_iterator iter;
+ struct stasis_app_command *command;
+
+ iter = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK);
+ while ((command = ao2_iterator_next(&iter))) {
+ command_complete(command, -1);
+ ao2_ref(command, -1);
+ }
+ ao2_iterator_destroy(&iter);
+}
+
int control_dispatch_all(struct stasis_app_control *control,
struct ast_channel *chan)
{
int count = 0;
- struct ao2_iterator i;
- void *obj;
+ struct ao2_iterator iter;
+ struct stasis_app_command *command;
ast_assert(control->channel == chan);
- i = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK);
-
- while ((obj = ao2_iterator_next(&i))) {
- RAII_VAR(struct stasis_app_command *, command, obj, ao2_cleanup);
+ iter = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK);
+ while ((command = ao2_iterator_next(&iter))) {
command_invoke(command, control, chan);
+ ao2_ref(command, -1);
++count;
}
+ ao2_iterator_destroy(&iter);
- ao2_iterator_destroy(&i);
return count;
}
diff --git a/res/stasis/control.h b/res/stasis/control.h
index a139f82e4..1d37a494a 100644
--- a/res/stasis/control.h
+++ b/res/stasis/control.h
@@ -41,6 +41,16 @@
struct stasis_app_control *control_create(struct ast_channel *channel, struct stasis_app *app);
/*!
+ * \brief Flush the control command queue.
+ * \since 13.9.0
+ *
+ * \param control Control object to flush command queue.
+ *
+ * \return Nothing
+ */
+void control_flush_queue(struct stasis_app_control *control);
+
+/*!
* \brief Dispatch all commands enqueued to this control.
*
* \param control Control object to dispatch.
@@ -108,5 +118,13 @@ int control_add_channel_to_bridge(
struct stasis_app_control *control,
struct ast_channel *chan, void *obj);
+/*!
+ * \brief Stop playing silence to a channel right now.
+ * \since 13.9.0
+ *
+ * \param control The control for chan
+ */
+void control_silence_stop_now(struct stasis_app_control *control);
+
#endif /* _ASTERISK_RES_STASIS_CONTROL_H */
diff --git a/tests/test_config.c b/tests/test_config.c
index bbfec0df2..fe64a074f 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -1672,6 +1672,66 @@ out:
return res;
}
+AST_TEST_DEFINE(variable_lists_match)
+{
+ RAII_VAR(struct ast_variable *, left, NULL, ast_variables_destroy);
+ RAII_VAR(struct ast_variable *, right, NULL, ast_variables_destroy);
+ struct ast_variable *var;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "variable_lists_match";
+ info->category = "/main/config/";
+ info->summary = "Test ast_variable_lists_match";
+ info->description = "Test ast_variable_lists_match";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ var = ast_variable_new("aaa", "111", "");
+ ast_test_validate(test, var);
+ left = var;
+ var = ast_variable_new("bbb", "222", "");
+ ast_test_validate(test, var);
+ ast_variable_list_append(&left, var);
+
+ var = ast_variable_new("aaa", "111", "");
+ ast_test_validate(test, var);
+ right = var;
+
+ ast_test_validate(test, ast_variable_lists_match(left, right, 0));
+ ast_test_validate(test, !ast_variable_lists_match(left, right, 1));
+
+ var = ast_variable_new("bbb", "222", "");
+ ast_test_validate(test, var);
+ ast_variable_list_append(&right, var);
+
+ ast_test_validate(test, ast_variable_lists_match(left, right, 0));
+ ast_test_validate(test, ast_variable_lists_match(left, right, 1));
+
+ var = ast_variable_new("ccc >", "333", "");
+ ast_test_validate(test, var);
+ ast_variable_list_append(&right, var);
+
+ ast_test_validate(test, !ast_variable_lists_match(left, right, 0));
+ ast_test_validate(test, !ast_variable_lists_match(left, right, 1));
+
+ var = ast_variable_new("ccc", "444", "");
+ ast_test_validate(test, var);
+ ast_variable_list_append(&left, var);
+
+ ast_test_validate(test, ast_variable_lists_match(left, right, 0));
+ ast_test_validate(test, !ast_variable_lists_match(left, right, 1));
+
+ ast_test_validate(test, !ast_variable_lists_match(left, NULL, 0));
+ ast_test_validate(test, ast_variable_lists_match(NULL, NULL, 0));
+ ast_test_validate(test, !ast_variable_lists_match(NULL, right, 0));
+ ast_test_validate(test, ast_variable_lists_match(left, left, 0));
+
+ return AST_TEST_PASS;
+}
+
static int unload_module(void)
{
AST_TEST_UNREGISTER(config_basic_ops);
@@ -1682,6 +1742,7 @@ static int unload_module(void)
AST_TEST_UNREGISTER(ast_parse_arg_test);
AST_TEST_UNREGISTER(config_options_test);
AST_TEST_UNREGISTER(config_dialplan_function);
+ AST_TEST_UNREGISTER(variable_lists_match);
return 0;
}
@@ -1695,6 +1756,7 @@ static int load_module(void)
AST_TEST_REGISTER(ast_parse_arg_test);
AST_TEST_REGISTER(config_options_test);
AST_TEST_REGISTER(config_dialplan_function);
+ AST_TEST_REGISTER(variable_lists_match);
return AST_MODULE_LOAD_SUCCESS;
}
diff --git a/tests/test_sorcery_astdb.c b/tests/test_sorcery_astdb.c
index ce9783423..d62e844e7 100644
--- a/tests/test_sorcery_astdb.c
+++ b/tests/test_sorcery_astdb.c
@@ -298,7 +298,7 @@ AST_TEST_DEFINE(object_retrieve_multiple_field)
RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery);
RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
- RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe", "6", ""), ast_variables_destroy);
+ RAII_VAR(struct ast_variable *, fields, ast_variable_new("joe >=", "6", ""), ast_variables_destroy);
switch (cmd) {
case TEST_INIT:
@@ -345,7 +345,7 @@ AST_TEST_DEFINE(object_retrieve_multiple_field)
ao2_cleanup(objects);
ast_variables_destroy(fields);
- if (!(fields = ast_variable_new("joe", "7", ""))) {
+ if (!(fields = ast_variable_new("joe <", "6", ""))) {
ast_test_status_update(test, "Failed to create fields for multiple retrieval\n");
return AST_TEST_FAIL;
} else if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE, fields))) {
diff --git a/tests/test_sorcery_realtime.c b/tests/test_sorcery_realtime.c
index 76dfb6603..b33031e8e 100644
--- a/tests/test_sorcery_realtime.c
+++ b/tests/test_sorcery_realtime.c
@@ -42,60 +42,12 @@ ASTERISK_REGISTER_FILE()
/*! \brief Configuration structure which contains all stored objects */
static struct ast_config *realtime_objects;
-/*! \brief Helper function which finds a given variable */
-static const struct ast_variable *realtime_find_variable(const struct ast_variable *fields, const char *name)
-{
- const struct ast_variable *variable;
-
- for (variable = fields; variable; variable = variable->next) {
- if (!strcmp(variable->name, name)) {
- return variable;
- }
- }
-
- return NULL;
-}
-
-/*! \brief Helper function which returns if an object is matching or not */
-static int realtime_is_object_matching(const char *object_id, const struct ast_variable *fields)
-{
- const struct ast_variable *field;
-
- for (field = fields; field; field = field->next) {
- char *name = ast_strdupa(field->name), *like;
- const char *value;
-
- /* If we are doing a pattern matching we need to remove the LIKE from the name */
- if ((like = strstr(name, " LIKE"))) {
- char *field_value = ast_strdupa(field->value);
-
- *like = '\0';
-
- value = ast_strdupa(ast_variable_retrieve(realtime_objects, object_id, name));
-
- field_value = ast_strip_quoted(field_value, "%", "%");
-
- if (strncmp(value, field_value, strlen(field_value))) {
- return 0;
- }
- } else {
- value = ast_variable_retrieve(realtime_objects, object_id, name);
-
- if (ast_strlen_zero(value) || strcmp(value, field->value)) {
- return 0;
- }
- }
- }
-
- return 1;
-}
-
static struct ast_variable *realtime_sorcery(const char *database, const char *table, const struct ast_variable *fields)
{
char *object_id = NULL;
while ((object_id = ast_category_browse(realtime_objects, object_id))) {
- if (!realtime_is_object_matching(object_id, fields)) {
+ if (!ast_variable_lists_match(ast_category_root(realtime_objects, object_id), fields, 0)) {
continue;
}
@@ -116,8 +68,9 @@ static struct ast_config *realtime_sorcery_multi(const char *database, const cha
while ((object_id = ast_category_browse(realtime_objects, object_id))) {
struct ast_category *object;
+ const struct ast_variable *object_fields = ast_category_root(realtime_objects, object_id);
- if (!realtime_is_object_matching(object_id, fields)) {
+ if (!ast_variable_lists_match(object_fields, fields, 0)) {
continue;
}
@@ -154,7 +107,7 @@ static int realtime_sorcery_update(const char *database, const char *table, cons
static int realtime_sorcery_store(const char *database, const char *table, const struct ast_variable *fields)
{
/* The key field is explicit within res_sorcery_realtime */
- const struct ast_variable *keyfield = realtime_find_variable(fields, "id");
+ const struct ast_variable *keyfield = ast_variable_find_variable_in_list(fields, "id");
struct ast_category *object;
if (!keyfield || ast_category_exist(realtime_objects, keyfield->value, NULL) || !(object = ast_category_new(keyfield->value, "", 0))) {
@@ -201,7 +154,7 @@ static void *test_sorcery_object_alloc(const char *id)
return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
}
-static struct ast_sorcery *alloc_and_initialize_sorcery(void)
+static struct ast_sorcery *alloc_and_initialize_sorcery(char *table)
{
struct ast_sorcery *sorcery;
@@ -209,7 +162,7 @@ static struct ast_sorcery *alloc_and_initialize_sorcery(void)
return NULL;
}
- if ((ast_sorcery_apply_default(sorcery, "test", "realtime", "sorcery_realtime_test") != AST_SORCERY_APPLY_SUCCESS) ||
+ if ((ast_sorcery_apply_default(sorcery, "test", "realtime", table) != AST_SORCERY_APPLY_SUCCESS) ||
ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL) ||
!(realtime_objects = ast_config_new())) {
ast_sorcery_unref(sorcery);
@@ -246,7 +199,7 @@ AST_TEST_DEFINE(object_create)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -281,7 +234,7 @@ AST_TEST_DEFINE(object_retrieve_id)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -344,7 +297,7 @@ AST_TEST_DEFINE(object_retrieve_field)
return AST_TEST_FAIL;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -402,7 +355,7 @@ AST_TEST_DEFINE(object_retrieve_multiple_all)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -440,6 +393,63 @@ AST_TEST_DEFINE(object_retrieve_multiple_all)
return AST_TEST_PASS;
}
+AST_TEST_DEFINE(object_retrieve_multiple_all_nofetch)
+{
+ RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery);
+ RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "object_retrieve_multiple_all_nofetch";
+ info->category = "/res/sorcery_realtime/";
+ info->summary = "sorcery multiple object retrieval unit test";
+ info->description =
+ "Test multiple object retrieval in sorcery using realtime wizard";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test,allow_unqualified_fetch=no"))) {
+ ast_test_status_update(test, "Failed to open sorcery structure\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) {
+ ast_test_status_update(test, "Failed to allocate a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_sorcery_create(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to create object using realtime wizard\n");
+ return AST_TEST_FAIL;
+ }
+
+ ao2_cleanup(obj);
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah2"))) {
+ ast_test_status_update(test, "Failed to allocate second instance of a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_sorcery_create(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to create second object using realtime wizard\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(objects = ast_sorcery_retrieve_by_fields(sorcery, "test", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) {
+ ast_test_status_update(test, "Failed to retrieve a container of all objects\n");
+ return AST_TEST_FAIL;
+ } else if (ao2_container_count(objects) != 0) {
+ ast_test_status_update(test, "Received a container with objects in it when there should be none\n");
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+
AST_TEST_DEFINE(object_retrieve_multiple_field)
{
RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery);
@@ -464,7 +474,7 @@ AST_TEST_DEFINE(object_retrieve_multiple_field)
return AST_TEST_FAIL;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -524,7 +534,7 @@ AST_TEST_DEFINE(object_retrieve_regex)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -574,6 +584,74 @@ AST_TEST_DEFINE(object_retrieve_regex)
return AST_TEST_PASS;
}
+AST_TEST_DEFINE(object_retrieve_regex_nofetch)
+{
+ RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery);
+ RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "object_retrieve_regex_nofetch";
+ info->category = "/res/sorcery_realtime/";
+ info->summary = "sorcery multiple object retrieval using regex unit test";
+ info->description =
+ "Test multiple object retrieval in sorcery using regular expression for matching using realtime wizard";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test,allow_unqualified_fetch=no"))) {
+ ast_test_status_update(test, "Failed to open sorcery structure\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah-98joe"))) {
+ ast_test_status_update(test, "Failed to allocate a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_sorcery_create(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to create object using realtime wizard\n");
+ return AST_TEST_FAIL;
+ }
+
+ ao2_cleanup(obj);
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah-93joe"))) {
+ ast_test_status_update(test, "Failed to allocate second instance of a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_sorcery_create(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to create second object using astdb wizard\n");
+ return AST_TEST_FAIL;
+ }
+
+ ao2_cleanup(obj);
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "neener-93joe"))) {
+ ast_test_status_update(test, "Failed to allocate third instance of a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_sorcery_create(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to create third object using astdb wizard\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(objects = ast_sorcery_retrieve_by_regex(sorcery, "test", ""))) {
+ ast_test_status_update(test, "Failed to retrieve a container of objects\n");
+ return AST_TEST_FAIL;
+ } else if (ao2_container_count(objects) != 0) {
+ ast_test_status_update(test, "Received a container with incorrect number of objects in it: %d instead of 0\n", ao2_container_count(objects));
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
AST_TEST_DEFINE(object_update)
{
RAII_VAR(struct ast_sorcery *, sorcery, NULL, deinitialize_sorcery);
@@ -592,7 +670,7 @@ AST_TEST_DEFINE(object_update)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -650,7 +728,7 @@ AST_TEST_DEFINE(object_update_uncreated)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -685,7 +763,7 @@ AST_TEST_DEFINE(object_delete)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -732,7 +810,7 @@ AST_TEST_DEFINE(object_delete_uncreated)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -770,7 +848,7 @@ AST_TEST_DEFINE(object_allocate_on_retrieval)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -823,7 +901,7 @@ AST_TEST_DEFINE(object_filter)
break;
}
- if (!(sorcery = alloc_and_initialize_sorcery())) {
+ if (!(sorcery = alloc_and_initialize_sorcery("sorcery_realtime_test"))) {
ast_test_status_update(test, "Failed to open sorcery structure\n");
return AST_TEST_FAIL;
}
@@ -859,8 +937,10 @@ static int unload_module(void)
AST_TEST_UNREGISTER(object_retrieve_id);
AST_TEST_UNREGISTER(object_retrieve_field);
AST_TEST_UNREGISTER(object_retrieve_multiple_all);
+ AST_TEST_UNREGISTER(object_retrieve_multiple_all_nofetch);
AST_TEST_UNREGISTER(object_retrieve_multiple_field);
AST_TEST_UNREGISTER(object_retrieve_regex);
+ AST_TEST_UNREGISTER(object_retrieve_regex_nofetch);
AST_TEST_UNREGISTER(object_update);
AST_TEST_UNREGISTER(object_update_uncreated);
AST_TEST_UNREGISTER(object_delete);
@@ -879,8 +959,10 @@ static int load_module(void)
AST_TEST_REGISTER(object_retrieve_id);
AST_TEST_REGISTER(object_retrieve_field);
AST_TEST_REGISTER(object_retrieve_multiple_all);
+ AST_TEST_REGISTER(object_retrieve_multiple_all_nofetch);
AST_TEST_REGISTER(object_retrieve_multiple_field);
AST_TEST_REGISTER(object_retrieve_regex);
+ AST_TEST_REGISTER(object_retrieve_regex_nofetch);
AST_TEST_REGISTER(object_update);
AST_TEST_REGISTER(object_update_uncreated);
AST_TEST_REGISTER(object_delete);
diff --git a/tests/test_strings.c b/tests/test_strings.c
index a39ac6334..28f6e1606 100644
--- a/tests/test_strings.c
+++ b/tests/test_strings.c
@@ -523,6 +523,68 @@ AST_TEST_DEFINE(escape_test)
return AST_TEST_PASS;
}
+AST_TEST_DEFINE(strings_match)
+{
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "strings_match";
+ info->category = "/main/strings/";
+ info->summary = "Test ast_strings_match";
+ info->description = "Test ast_strings_match";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ ast_test_validate(test, ast_strings_match("aaa", NULL, "aaa"));
+ ast_test_validate(test, ast_strings_match("aaa", "", "aaa"));
+ ast_test_validate(test, ast_strings_match("aaa", "=", "aaa"));
+ ast_test_validate(test, !ast_strings_match("aaa", "!=", "aaa"));
+ ast_test_validate(test, !ast_strings_match("aaa", NULL, "aba"));
+ ast_test_validate(test, !ast_strings_match("aaa", "", "aba"));
+ ast_test_validate(test, !ast_strings_match("aaa", "=", "aba"));
+ ast_test_validate(test, ast_strings_match("aaa", "!=", "aba"));
+
+ ast_test_validate(test, ast_strings_match("aaa", "<=", "aba"));
+ ast_test_validate(test, ast_strings_match("aaa", "<=", "aaa"));
+ ast_test_validate(test, !ast_strings_match("aaa", "<", "aaa"));
+
+ ast_test_validate(test, !ast_strings_match("aaa", ">=", "aba"));
+ ast_test_validate(test, ast_strings_match("aaa", ">=", "aaa"));
+ ast_test_validate(test, !ast_strings_match("aaa", ">", "aaa"));
+
+ ast_test_validate(test, !ast_strings_match("aaa", "=", "aa"));
+ ast_test_validate(test, ast_strings_match("aaa", ">", "aa"));
+ ast_test_validate(test, !ast_strings_match("aaa", "<", "aa"));
+
+ ast_test_validate(test, ast_strings_match("1", "=", "1"));
+ ast_test_validate(test, !ast_strings_match("1", "!=", "1"));
+ ast_test_validate(test, !ast_strings_match("2", "=", "1"));
+ ast_test_validate(test, ast_strings_match("2", ">", "1"));
+ ast_test_validate(test, ast_strings_match("2", ">=", "1"));
+ ast_test_validate(test, ast_strings_match("2", ">", "1.9888"));
+ ast_test_validate(test, ast_strings_match("2.9", ">", "1"));
+ ast_test_validate(test, ast_strings_match("2", ">", "1"));
+ ast_test_validate(test, ast_strings_match("2.999", "<", "3"));
+ ast_test_validate(test, ast_strings_match("2", ">", "#"));
+
+ ast_test_validate(test, ast_strings_match("abcccc", "like", "%a%c"));
+ ast_test_validate(test, !ast_strings_match("abcccx", "like", "%a%c"));
+ ast_test_validate(test, ast_strings_match("abcccc", "regex", "a[bc]+c"));
+ ast_test_validate(test, !ast_strings_match("abcccx", "regex", "^a[bxdfgtc]+c$"));
+
+ ast_test_validate(test, !ast_strings_match("neener-93joe", "LIKE", "%blah-%"));
+ ast_test_validate(test, ast_strings_match("blah-93joe", "LIKE", "%blah-%"));
+
+ ast_test_validate(test, !ast_strings_match("abcccx", "regex", NULL));
+ ast_test_validate(test, !ast_strings_match("abcccx", NULL, NULL));
+ ast_test_validate(test, !ast_strings_match(NULL, "regex", NULL));
+ ast_test_validate(test, !ast_strings_match(NULL, NULL, "abc"));
+ ast_test_validate(test, !ast_strings_match(NULL, NULL, NULL));
+
+ return AST_TEST_PASS;
+}
+
static int unload_module(void)
{
AST_TEST_UNREGISTER(str_test);
@@ -531,6 +593,7 @@ static int unload_module(void)
AST_TEST_UNREGISTER(strsep_test);
AST_TEST_UNREGISTER(escape_semicolons_test);
AST_TEST_UNREGISTER(escape_test);
+ AST_TEST_UNREGISTER(strings_match);
return 0;
}
@@ -542,6 +605,7 @@ static int load_module(void)
AST_TEST_REGISTER(strsep_test);
AST_TEST_REGISTER(escape_semicolons_test);
AST_TEST_REGISTER(escape_test);
+ AST_TEST_REGISTER(strings_match);
return AST_MODULE_LOAD_SUCCESS;
}