diff options
50 files changed, 1947 insertions, 907 deletions
@@ -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(®exbuf, 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, ®exbuf); + regfree(®exbuf); + } + + 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(®exbuf, 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, ®exbuf); - regfree(®exbuf); - } - - 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(¤t_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, ¶ms); } 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; } |