summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES19
-rw-r--r--channels/chan_sip.c11
-rw-r--r--configs/samples/pjsip.conf.sample8
-rw-r--r--contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py22
-rw-r--r--funcs/func_holdintercept.c236
-rw-r--r--include/asterisk/res_pjsip.h5
-rw-r--r--include/asterisk/res_pjsip_pubsub.h9
-rw-r--r--main/format_cap.c29
-rw-r--r--main/manager.c12
-rw-r--r--main/strings.c91
-rw-r--r--main/xmldoc.c6
-rw-r--r--res/res_pjsip.c18
-rw-r--r--res/res_pjsip/location.c32
-rw-r--r--res/res_pjsip/pjsip_configuration.c30
-rw-r--r--res/res_pjsip_exten_state.c4
-rw-r--r--res/res_pjsip_mwi.c6
-rw-r--r--res/res_pjsip_outbound_registration.c29
-rw-r--r--res/res_pjsip_pubsub.c339
-rw-r--r--res/res_pjsip_pubsub.exports.in1
-rw-r--r--rest-api-templates/api.wiki.mustache2
-rw-r--r--rest-api-templates/asterisk_processor.py2
21 files changed, 701 insertions, 210 deletions
diff --git a/CHANGES b/CHANGES
index 231ef2a7a..1deba235d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,25 @@
==============================================================================
------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------
+------------------------------------------------------------------------------
+
+Dialplan Functions
+------------------
+ * The HOLD_INTERCEPT dialplan function now actually exists in the source tree.
+ While support for the events was added in Asterisk 13.4.0, the function
+ accidentally never made it in. That function is now present, and will cause
+ the 'hold' raised by a channel to be intercepted and converted into an
+ event instead.
+
+res_pjsip_outbound_registration
+-------------------------------
+* A new 'fatal_retry_interval' option has been added to outbound registration.
+ When set (default is zero), and upon receiving a failure response to an
+ outbound registration, registration is retried at the given interval up to
+ 'max_retries'.
+
+------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.5.0 to Asterisk 13.6.0 ------------
------------------------------------------------------------------------------
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 051bb2bc4..8a7ca5454 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -11095,7 +11095,7 @@ static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_
if (framing && p->autoframing) {
ast_debug(1, "Setting framing to %ld\n", framing);
- ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), framing);
+ ast_format_cap_set_framing(p->caps, framing);
}
found = TRUE;
} else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) {
@@ -13384,6 +13384,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
ast_str_append(&a_audio, 0, "a=maxptime:%d\r\n", max_audio_packet_size);
}
+ if (!ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+ ast_debug(1, "Setting framing on incoming call: %u\n", min_audio_packet_size);
+ ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), min_audio_packet_size);
+ }
+
if (!doing_directmedia) {
if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) {
add_ice_to_sdp(p->rtp, &a_audio);
@@ -13676,10 +13681,6 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const
add_cc_call_info_to_response(p, &resp);
}
if (p->rtp) {
- if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
- ast_debug(1, "Setting framing from config on incoming call\n");
- ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), ast_format_cap_get_framing(p->caps));
- }
ast_rtp_instance_activate(p->rtp);
try_suggested_sip_codec(p);
if (p->t38.state == T38_ENABLED) {
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index fc7094756..859635c98 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -923,6 +923,14 @@
; registration is unsuccessful (default: "60")
;forbidden_retry_interval=0 ; Interval used when receiving a 403 Forbidden
; response (default: "0")
+;fatal_retry_interval=0 ; Interval used when receiving a fatal response.
+ ; (default: "0") A fatal response is any permanent
+ ; failure (non-temporary 4xx, 5xx, 6xx) response
+ ; received from the registrar. NOTE - if also set
+ ; the 'forbidden_retry_interval' takes precedence
+ ; over this one when a 403 is received. Also, if
+ ; 'auth_rejection_permanent' equals 'yes' a 401 and
+ ; 407 become subject to this retry interval.
;server_uri= ; SIP URI of the server to register against (default: "")
;transport= ; Transport used for outbound authentication (default: "")
;type= ; Must be of type registration (default: "")
diff --git a/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py b/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py
new file mode 100644
index 000000000..8c499aee8
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py
@@ -0,0 +1,22 @@
+"""add fatal_response_interval
+
+Revision ID: 28ce1e718f05
+Revises: 154177371065
+Create Date: 2015-10-20 17:57:45.560585
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '28ce1e718f05'
+down_revision = '154177371065'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_registrations', sa.Column('fatal_retry_interval', sa.Integer))
+
+
+def downgrade():
+ op.drop_column('ps_registrations', 'fatal_retry_interval')
diff --git a/funcs/func_holdintercept.c b/funcs/func_holdintercept.c
new file mode 100644
index 000000000..56d9a9e83
--- /dev/null
+++ b/funcs/func_holdintercept.c
@@ -0,0 +1,236 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Function that intercepts HOLD frames from channels and raises events
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ *
+ * \ingroup functions
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/frame.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+
+/*** DOCUMENTATION
+ <function name="HOLD_INTERCEPT" language="en_US">
+ <synopsis>
+ Intercepts hold frames on a channel and raises an event instead of passing the frame on
+ </synopsis>
+ <syntax>
+ <parameter name="action" required="true">
+ <optionlist>
+ <option name="remove">
+ <para>W/O. Removes the hold interception function.</para>
+ </option>
+ <option name="set">
+ <para>W/O. Enable hold interception on the channel. When
+ enabled, the channel will intercept any hold action that
+ is signalled from the device, and instead simply raise an
+ event (AMI/ARI) indicating that the channel wanted to put other
+ parties on hold.</para>
+ </option>
+ </optionlist>
+ </parameter>
+ </syntax>
+ </function>
+***/
+
+/*! \brief Private data structure used with the function's datastore */
+struct hold_intercept_data {
+ int framehook_id;
+};
+
+/*! \brief The channel datastore the function uses to store state */
+static const struct ast_datastore_info hold_intercept_datastore = {
+ .type = "hold_intercept",
+};
+
+/*! \internal \brief Disable hold interception on the channel */
+static int remove_hold_intercept(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore = NULL;
+ struct hold_intercept_data *data;
+ SCOPED_CHANNELLOCK(chan_lock, chan);
+
+ datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL);
+ if (!datastore) {
+ ast_log(AST_LOG_WARNING, "Cannot remove HOLD_INTERCEPT from %s: HOLD_INTERCEPT not currently enabled\n",
+ ast_channel_name(chan));
+ return -1;
+ }
+ data = datastore->data;
+
+ if (ast_framehook_detach(chan, data->framehook_id)) {
+ ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT framehook from channel %s\n",
+ ast_channel_name(chan));
+ return -1;
+ }
+
+ if (ast_channel_datastore_remove(chan, datastore)) {
+ ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT datastore from channel %s\n",
+ ast_channel_name(chan));
+ return -1;
+ }
+ ast_datastore_free(datastore);
+
+ return 0;
+}
+
+/*! \brief Frame hook that is called to intercept hold/unhold */
+static struct ast_frame *hold_intercept_framehook(struct ast_channel *chan,
+ struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+ int frame_type;
+
+ if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+ return f;
+ }
+
+ if (f->frametype != AST_FRAME_CONTROL) {
+ return f;
+ }
+
+ frame_type = f->subclass.integer;
+ if (frame_type != AST_CONTROL_HOLD && frame_type != AST_CONTROL_UNHOLD) {
+ return f;
+ }
+
+ /* Munch munch */
+ ast_frfree(f);
+ f = &ast_null_frame;
+
+ ast_channel_publish_cached_blob(chan,
+ frame_type == AST_CONTROL_HOLD ? ast_channel_hold_type() : ast_channel_unhold_type(),
+ NULL);
+
+ return f;
+}
+
+/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
+static int hold_intercept_framehook_consume(void *data, enum ast_frame_type type)
+{
+ return (type == AST_FRAME_CONTROL ? 1 : 0);
+}
+
+/*! \internal \brief Enable hold interception on the channel */
+static int set_hold_intercept(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+ struct hold_intercept_data *data;
+ static struct ast_framehook_interface hold_framehook_interface = {
+ .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+ .event_cb = hold_intercept_framehook,
+ .consume_cb = hold_intercept_framehook_consume,
+ .disable_inheritance = 1,
+ };
+ SCOPED_CHANNELLOCK(chan_lock, chan);
+
+ datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL);
+ if (datastore) {
+ ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT already set on '%s'\n",
+ ast_channel_name(chan));
+ return 0;
+ }
+
+ datastore = ast_datastore_alloc(&hold_intercept_datastore, NULL);
+ if (!datastore) {
+ return -1;
+ }
+
+ data = ast_calloc(1, sizeof(*data));
+ if (!data) {
+ ast_datastore_free(datastore);
+ return -1;
+ }
+
+ data->framehook_id = ast_framehook_attach(chan, &hold_framehook_interface);
+ if (data->framehook_id < 0) {
+ ast_log(AST_LOG_WARNING, "Failed to attach HOLD_INTERCEPT framehook to '%s'\n",
+ ast_channel_name(chan));
+ ast_datastore_free(datastore);
+ ast_free(data);
+ return -1;
+ }
+ datastore->data = data;
+
+ ast_channel_datastore_add(chan, datastore);
+
+ return 0;
+}
+
+/*! \internal \brief HOLD_INTERCEPT write function callback */
+static int hold_intercept_fn_write(struct ast_channel *chan, const char *function,
+ char *data, const char *value)
+{
+ int res;
+
+ if (!chan) {
+ return -1;
+ }
+
+ if (ast_strlen_zero(data)) {
+ ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT requires an argument\n");
+ return -1;
+ }
+
+ if (!strcasecmp(data, "set")) {
+ res = set_hold_intercept(chan);
+ } else if (!strcasecmp(data, "remove")) {
+ res = remove_hold_intercept(chan);
+ } else {
+ ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT: unknown option %s\n", data);
+ res = -1;
+ }
+
+ return res;
+}
+
+/*! \brief Definition of the HOLD_INTERCEPT function */
+static struct ast_custom_function hold_intercept_function = {
+ .name = "HOLD_INTERCEPT",
+ .write = hold_intercept_fn_write,
+};
+
+/*! \internal \brief Unload the module */
+static int unload_module(void)
+{
+ return ast_custom_function_unregister(&hold_intercept_function);
+}
+
+/*! \internal \brief Load the module */
+static int load_module(void)
+{
+ return ast_custom_function_register(&hold_intercept_function) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hold interception dialplan function");
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index a36935dd3..459082901 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -1190,6 +1190,11 @@ int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void
* cause a deadlock. If you are in a SIP servant thread, just call your function
* in-line.
*
+ * \warning \b Never hold locks that may be acquired by a SIP servant thread when
+ * calling this function. Doing so may cause a deadlock if all SIP servant threads
+ * are blocked waiting to acquire the lock while the thread holding the lock is
+ * waiting for a free SIP servant thread.
+ *
* \param serializer The SIP serializer to which the task belongs. May be NULL.
* \param sip_task The task to execute
* \param task_data The parameter to pass to the task when it executes
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index afa0d6930..c9b66dce3 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -684,6 +684,15 @@ const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub)
*/
const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub);
+/*!
+ * \since 13.6.0
+ * \brief Alert the pubsub core that the subscription is ready for destruction
+ *
+ * \param sub The subscription that is complete
+ * \return Nothing
+ */
+void ast_sip_subscription_destroy(struct ast_sip_subscription *sub);
+
/*! \brief Determines whether the res_pjsip_pubsub module is loaded */
#define CHECK_PJSIP_PUBSUB_MODULE_LOADED() \
do { \
diff --git a/main/format_cap.c b/main/format_cap.c
index b344f846e..1725e3862 100644
--- a/main/format_cap.c
+++ b/main/format_cap.c
@@ -93,14 +93,27 @@ static void format_cap_destroy(void *obj)
AST_VECTOR_FREE(&cap->preference_order);
}
-static inline void format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags)
+/*
+ * \brief Initialize values on an ast_format_cap
+ *
+ * \param cap ast_format_cap to initialize
+ * \param flags Unused.
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+static inline int format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags)
{
- AST_VECTOR_INIT(&cap->formats, 0);
+ if (AST_VECTOR_INIT(&cap->formats, 0)) {
+ return -1;
+ }
/* TODO: Look at common usage of this and determine a good starting point */
- AST_VECTOR_INIT(&cap->preference_order, 5);
+ if (AST_VECTOR_INIT(&cap->preference_order, 5)) {
+ return -1;
+ }
cap->framing = UINT_MAX;
+ return 0;
}
struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags)
@@ -112,7 +125,10 @@ struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags)
return NULL;
}
- format_cap_init(cap, flags);
+ if (format_cap_init(cap, flags)) {
+ ao2_ref(cap, -1);
+ return NULL;
+ }
return cap;
}
@@ -126,7 +142,10 @@ struct ast_format_cap *__ast_format_cap_alloc_debug(enum ast_format_cap_flags fl
return NULL;
}
- format_cap_init(cap, flags);
+ if (format_cap_init(cap, flags)) {
+ ao2_ref(cap, -1);
+ return NULL;
+ }
return cap;
}
diff --git a/main/manager.c b/main/manager.c
index 75b1d4d48..8295303c7 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -2811,6 +2811,7 @@ AST_THREADSTORAGE(userevent_buf);
*/
void astman_append(struct mansession *s, const char *fmt, ...)
{
+ int res;
va_list ap;
struct ast_str *buf;
@@ -2819,8 +2820,11 @@ void astman_append(struct mansession *s, const char *fmt, ...)
}
va_start(ap, fmt);
- ast_str_set_va(&buf, 0, fmt, ap);
+ res = ast_str_set_va(&buf, 0, fmt, ap);
va_end(ap);
+ if (res == AST_DYNSTR_BUILD_FAILED) {
+ return;
+ }
if (s->f != NULL || s->session->f != NULL) {
send_string(s, ast_str_buffer(buf));
@@ -2880,6 +2884,7 @@ void astman_send_error(struct mansession *s, const struct message *m, char *erro
void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...)
{
+ int res;
va_list ap;
struct ast_str *buf;
char *msg;
@@ -2889,8 +2894,11 @@ void astman_send_error_va(struct mansession *s, const struct message *m, const c
}
va_start(ap, fmt);
- ast_str_set_va(&buf, 0, fmt, ap);
+ res = ast_str_set_va(&buf, 0, fmt, ap);
va_end(ap);
+ if (res == AST_DYNSTR_BUILD_FAILED) {
+ return;
+ }
/* astman_append will use the same underlying buffer, so copy the message out
* before sending the response */
diff --git a/main/strings.c b/main/strings.c
index da63cdfc0..53d50954f 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -60,55 +60,78 @@ int __ast_str_helper(struct ast_str **buf, ssize_t max_len,
int append, const char *fmt, va_list ap)
#endif
{
- int res, need;
+ int res;
+ int added;
+ int need;
int offset = (append && (*buf)->__AST_STR_LEN) ? (*buf)->__AST_STR_USED : 0;
va_list aq;
+ if (max_len < 0) {
+ max_len = (*buf)->__AST_STR_LEN; /* don't exceed the allocated space */
+ }
+
do {
- if (max_len < 0) {
- max_len = (*buf)->__AST_STR_LEN; /* don't exceed the allocated space */
- }
- /*
- * Ask vsnprintf how much space we need. Remember that vsnprintf
- * does not count the final <code>'\\0'</code> so we must add 1.
- */
va_copy(aq, ap);
res = vsnprintf((*buf)->__AST_STR_STR + offset, (*buf)->__AST_STR_LEN - offset, fmt, aq);
+ va_end(aq);
+
+ if (res < 0) {
+ /*
+ * vsnprintf write to string failed.
+ * I don't think this is possible with a memory buffer.
+ */
+ res = AST_DYNSTR_BUILD_FAILED;
+ added = 0;
+ break;
+ }
- need = res + offset + 1;
/*
- * If there is not enough space and we are below the max length,
- * reallocate the buffer and return a message telling to retry.
+ * vsnprintf returns how much space we used or would need.
+ * Remember that vsnprintf does not count the nil terminator
+ * so we must add 1.
*/
- if (need > (*buf)->__AST_STR_LEN && (max_len == 0 || (*buf)->__AST_STR_LEN < max_len) ) {
- int len = (int)(*buf)->__AST_STR_LEN;
- if (max_len && max_len < need) { /* truncate as needed */
- need = max_len;
- } else if (max_len == 0) { /* if unbounded, give more room for next time */
- need += 16 + need / 4;
- }
- if (
+ added = res;
+ need = offset + added + 1;
+ if (need <= (*buf)->__AST_STR_LEN
+ || (max_len && max_len <= (*buf)->__AST_STR_LEN)) {
+ /*
+ * There was enough room for the string or we are not
+ * allowed to try growing the string buffer.
+ */
+ break;
+ }
+
+ /* Reallocate the buffer and try again. */
+ if (max_len == 0) {
+ /* unbounded, give more room for next time */
+ need += 16 + need / 4;
+ } else if (max_len < need) {
+ /* truncate as needed */
+ need = max_len;
+ }
+
+ if (
#if (defined(MALLOC_DEBUG) && !defined(STANDALONE))
- _ast_str_make_space(buf, need, file, lineno, function)
+ _ast_str_make_space(buf, need, file, lineno, function)
#else
- ast_str_make_space(buf, need)
+ ast_str_make_space(buf, need)
#endif
- ) {
- ast_log_safe(LOG_VERBOSE, "failed to extend from %d to %d\n", len, need);
- va_end(aq);
- return AST_DYNSTR_BUILD_FAILED;
- }
- (*buf)->__AST_STR_STR[offset] = '\0'; /* Truncate the partial write. */
+ ) {
+ ast_log_safe(LOG_VERBOSE, "failed to extend from %d to %d\n",
+ (int) (*buf)->__AST_STR_LEN, need);
- /* Restart va_copy before calling vsnprintf() again. */
- va_end(aq);
- continue;
+ res = AST_DYNSTR_BUILD_FAILED;
+ break;
}
- va_end(aq);
- break;
} while (1);
- /* update space used, keep in mind the truncation */
- (*buf)->__AST_STR_USED = (res + offset > (*buf)->__AST_STR_LEN) ? (*buf)->__AST_STR_LEN - 1: res + offset;
+
+ /* Update space used, keep in mind truncation may be necessary. */
+ (*buf)->__AST_STR_USED = ((*buf)->__AST_STR_LEN <= offset + added)
+ ? (*buf)->__AST_STR_LEN - 1
+ : offset + added;
+
+ /* Ensure that the string is terminated. */
+ (*buf)->__AST_STR_STR[(*buf)->__AST_STR_USED] = '\0';
return res;
}
diff --git a/main/xmldoc.c b/main/xmldoc.c
index fcf1b2cac..1a04e8168 100644
--- a/main/xmldoc.c
+++ b/main/xmldoc.c
@@ -2646,14 +2646,18 @@ struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_q
struct documentation_tree *doctree;
RAII_VAR(struct ast_str *, xpath_str, ast_str_create(128), ast_free);
va_list ap;
+ int res;
if (!xpath_str) {
return NULL;
}
va_start(ap, fmt);
- ast_str_set_va(&xpath_str, 0, fmt, ap);
+ res = ast_str_set_va(&xpath_str, 0, fmt, ap);
va_end(ap);
+ if (res == AST_DYNSTR_BUILD_FAILED) {
+ return NULL;
+ }
AST_RWLIST_RDLOCK(&xmldoc_tree);
AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 9e5e404b6..d2b393fcc 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -2563,6 +2563,8 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
pj_strdup2_with_null(dlg->pool, &tmp, outbound_proxy);
if (!(route = pjsip_parse_hdr(dlg->pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL))) {
+ ast_log(LOG_ERROR, "Could not create dialog to endpoint '%s' as outbound proxy URI '%s' is not valid\n",
+ ast_sorcery_object_get_id(endpoint), outbound_proxy);
dlg->sess_count--;
pjsip_dlg_terminate(dlg);
return NULL;
@@ -2756,6 +2758,7 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
pj_str_t from;
pj_pool_t *pool;
pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+ pjsip_uri *sip_uri;
if (ast_strlen_zero(uri)) {
if (!endpoint && (!contact || ast_strlen_zero(contact->uri))) {
@@ -2792,6 +2795,16 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
return -1;
}
+ sip_uri = pjsip_parse_uri(pool, remote_uri.ptr, remote_uri.slen, 0);
+ if (!sip_uri || (!PJSIP_URI_SCHEME_IS_SIP(sip_uri) && !PJSIP_URI_SCHEME_IS_SIPS(sip_uri))) {
+ ast_log(LOG_ERROR, "Unable to create outbound %.*s request to endpoint %s as URI '%s' is not valid\n",
+ (int) pj_strlen(&method->name), pj_strbuf(&method->name),
+ endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>",
+ pj_strbuf(&remote_uri));
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+ return -1;
+ }
+
if (sip_dialog_create_from(pool, &from, endpoint ? endpoint->fromuser : NULL,
endpoint ? endpoint->fromdomain : NULL, &remote_uri, &selector)) {
ast_log(LOG_ERROR, "Unable to create From header for %.*s request to endpoint %s\n",
@@ -2816,8 +2829,9 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
/* If an outbound proxy is specified on the endpoint apply it to this request */
if (endpoint && !ast_strlen_zero(endpoint->outbound_proxy) &&
ast_sip_set_outbound_proxy((*tdata), endpoint->outbound_proxy)) {
- ast_log(LOG_ERROR, "Unable to apply outbound proxy on request %.*s to endpoint %s\n",
- (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint));
+ ast_log(LOG_ERROR, "Unable to apply outbound proxy on request %.*s to endpoint %s as outbound proxy URI '%s' is not valid\n",
+ (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint),
+ endpoint->outbound_proxy);
pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
return -1;
}
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 9625f04ef..331d839a1 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -319,32 +319,6 @@ static int expiration_struct2str(const void *obj, const intptr_t *args, char **b
return (ast_asprintf(buf, "%ld", contact->expiration_time.tv_sec) < 0) ? -1 : 0;
}
-/*! \brief Helper function which validates a permanent contact */
-static int permanent_contact_validate(void *data)
-{
- const char *value = data;
- pj_pool_t *pool;
- pj_str_t contact_uri;
- static const pj_str_t HCONTACT = { "Contact", 7 };
- pjsip_contact_hdr *contact_hdr;
- int rc = 0;
-
- pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Permanent Contact Validation", 256, 256);
- if (!pool) {
- return -1;
- }
-
- pj_strdup2_with_null(pool, &contact_uri, value);
- if (!(contact_hdr = pjsip_parse_hdr(pool, &HCONTACT, contact_uri.ptr, contact_uri.slen, NULL))
- || !(PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri)
- || PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri))) {
- rc = -1;
- }
-
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
- return rc;
-}
-
static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags)
{
const struct ast_sip_contact *object_left = obj_left;
@@ -393,12 +367,6 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab
struct ast_sip_contact_status *status;
char contact_id[strlen(aor_id) + strlen(contact_uri) + 2 + 1];
- if (ast_sip_push_task_synchronous(NULL, permanent_contact_validate, contact_uri)) {
- ast_log(LOG_ERROR, "Permanent URI on aor '%s' with contact '%s' failed to parse\n",
- ast_sorcery_object_get_id(aor), contact_uri);
- return -1;
- }
-
if (!aor->permanent_contacts) {
aor->permanent_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 2fdfc9d0b..bcbb791e6 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -1084,29 +1084,6 @@ static struct ast_endpoint *persistent_endpoint_find_or_create(const struct ast_
return persistent->endpoint;
}
-/*! \brief Helper function which validates an outbound proxy */
-static int outbound_proxy_validate(void *data)
-{
- const char *proxy = data;
- pj_pool_t *pool;
- pj_str_t tmp;
- static const pj_str_t ROUTE_HNAME = { "Route", 5 };
-
- pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound Proxy Validation", 256, 256);
- if (!pool) {
- return -1;
- }
-
- pj_strdup2_with_null(pool, &tmp, proxy);
- if (!pjsip_parse_hdr(pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL)) {
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
- return -1;
- }
-
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
- return 0;
-}
-
/*! \brief Callback function for when an object is finalized */
static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *obj)
{
@@ -1116,12 +1093,7 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
return -1;
}
- if (!ast_strlen_zero(endpoint->outbound_proxy) &&
- ast_sip_push_task_synchronous(NULL, outbound_proxy_validate, (char*)endpoint->outbound_proxy)) {
- ast_log(LOG_ERROR, "Invalid outbound proxy '%s' specified on endpoint '%s'\n",
- endpoint->outbound_proxy, ast_sorcery_object_get_id(endpoint));
- return -1;
- } else if (endpoint->extensions.timer.min_se < 90) {
+ if (endpoint->extensions.timer.min_se < 90) {
ast_log(LOG_ERROR, "Session timer minimum expires time must be 90 or greater on endpoint '%s'\n",
ast_sorcery_object_get_id(endpoint));
return -1;
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index 3dc8c1a8d..4e225dd1a 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -115,7 +115,7 @@ static void exten_state_subscription_destructor(void *obj)
struct exten_state_subscription *sub = obj;
ast_free(sub->user_agent);
- ao2_cleanup(sub->sip_sub);
+ ast_sip_subscription_destroy(sub->sip_sub);
ast_taskprocessor_unreference(sub->serializer);
}
@@ -160,7 +160,7 @@ static struct exten_state_subscription *exten_state_subscription_alloc(
return NULL;
}
- exten_state_sub->sip_sub = ao2_bump(sip_sub);
+ exten_state_sub->sip_sub = sip_sub;
/* We keep our own reference to the serializer as there is no guarantee in state_changed
* that the subscription tree is still valid when it is called. This can occur when
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index 06587daf7..f349324a4 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -204,7 +204,9 @@ static void mwi_subscription_destructor(void *obj)
struct mwi_subscription *sub = obj;
ast_debug(3, "Destroying MWI subscription for endpoint %s\n", sub->id);
- ao2_cleanup(sub->sip_sub);
+ if (sub->is_solicited) {
+ ast_sip_subscription_destroy(sub->sip_sub);
+ }
ao2_cleanup(sub->stasis_subs);
ast_free(sub->aors);
}
@@ -233,7 +235,7 @@ static struct mwi_subscription *mwi_subscription_alloc(struct ast_sip_endpoint *
* state not being updated on the device
*/
if (is_solicited) {
- sub->sip_sub = ao2_bump(sip_sub);
+ sub->sip_sub = sip_sub;
}
sub->stasis_subs = ao2_container_alloc(STASIS_BUCKETS, stasis_sub_hash, stasis_sub_cmp);
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index c4d02491b..b51a48a67 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -100,6 +100,21 @@
buggy registrars.
</para></description>
</configOption>
+ <configOption name="fatal_retry_interval" default="0">
+ <synopsis>Interval used when receiving a Fatal response.</synopsis>
+ <description><para>
+ If a fatal response is received, chan_pjsip will wait
+ <replaceable>fatal_retry_interval</replaceable> seconds before
+ attempting registration again. If 0 is specified, chan_pjsip will not
+ retry after receiving a fatal (non-temporary 4xx, 5xx, 6xx) response.
+ Setting this to a non-zero value may go against a "SHOULD NOT" in RFC3261,
+ but can be used to work around buggy registrars.</para>
+ <note><para>if also set the <replaceable>forbidden_retry_interval</replaceable>
+ takes precedence over this one when a 403 is received.
+ Also, if <replaceable>auth_rejection_permanent</replaceable> equals 'yes' then
+ a 401 and 407 become subject to this retry interval.</para></note>
+ </description>
+ </configOption>
<configOption name="server_uri">
<synopsis>SIP URI of the server to register against</synopsis>
<description><para>
@@ -277,6 +292,8 @@ struct sip_outbound_registration {
unsigned int retry_interval;
/*! \brief Interval at which retries should occur for permanent responses */
unsigned int forbidden_retry_interval;
+ /*! \brief Interval at which retries should occur for all permanent responses */
+ unsigned int fatal_retry_interval;
/*! \brief Treat authentication challenges that we cannot handle as permanent failures */
unsigned int auth_rejection_permanent;
/*! \brief Maximum number of retries permitted */
@@ -312,6 +329,8 @@ struct sip_outbound_registration_client_state {
unsigned int retry_interval;
/*! \brief Interval at which retries should occur for permanent responses */
unsigned int forbidden_retry_interval;
+ /*! \brief Interval at which retries should occur for all permanent responses */
+ unsigned int fatal_retry_interval;
/*! \brief Treat authentication challenges that we cannot handle as permanent failures */
unsigned int auth_rejection_permanent;
/*! \brief Determines whether SIP Path support should be advertised */
@@ -799,6 +818,14 @@ static int handle_registration_response(void *data)
schedule_registration(response->client_state, response->client_state->forbidden_retry_interval);
ast_log(LOG_WARNING, "403 Forbidden fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n",
server_uri, client_uri, response->client_state->forbidden_retry_interval);
+ } else if (response->client_state->fatal_retry_interval
+ && response->client_state->retries < response->client_state->max_retries) {
+ /* Some kind of fatal failure response received, so retry according to configured interval */
+ response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+ response->client_state->retries++;
+ schedule_registration(response->client_state, response->client_state->fatal_retry_interval);
+ ast_log(LOG_WARNING, "'%d' fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n",
+ response->code, server_uri, client_uri, response->client_state->fatal_retry_interval);
} else {
/* Finally if there's no hope of registering give up */
response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
@@ -1186,6 +1213,7 @@ static int sip_outbound_registration_perform(void *data)
}
state->client_state->retry_interval = registration->retry_interval;
state->client_state->forbidden_retry_interval = registration->forbidden_retry_interval;
+ state->client_state->fatal_retry_interval = registration->fatal_retry_interval;
state->client_state->max_retries = registration->max_retries;
state->client_state->retries = 0;
state->client_state->support_path = registration->support_path;
@@ -1909,6 +1937,7 @@ static int load_module(void)
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "retry_interval", "60", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, retry_interval));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "fatal_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, fatal_retry_interval));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0);
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index eb49aafd8..cbedaf28c 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -411,6 +411,8 @@ struct sip_subscription_tree {
int is_list;
/*! Next item in the list */
AST_LIST_ENTRY(sip_subscription_tree) next;
+ /*! Indicates that a NOTIFY is currently being sent on the SIP subscription */
+ int last_notify;
};
/*!
@@ -596,6 +598,7 @@ static void subscription_persistence_remove(struct sip_subscription_tree *sub_tr
ast_sorcery_delete(ast_sip_get_sorcery(), sub_tree->persistence);
ao2_ref(sub_tree->persistence, -1);
+ sub_tree->persistence = NULL;
}
@@ -1020,26 +1023,6 @@ static int datastore_cmp(void *obj, void *arg, int flags)
return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
}
-static int subscription_remove_serializer(void *obj)
-{
- struct sip_subscription_tree *sub_tree = obj;
-
- /* This is why we keep the dialog on the subscription. When the subscription
- * is destroyed, there is no guarantee that the underlying dialog is ready
- * to be destroyed. Furthermore, there's no guarantee in the opposite direction
- * either. The dialog could be destroyed before our subscription is. We fix
- * this problem by keeping a reference to the dialog until it is time to
- * destroy the subscription. We need to have the dialog available when the
- * subscription is destroyed so that we can guarantee that our attempt to
- * remove the serializer will be successful.
- */
- ast_sip_dialog_set_serializer(sub_tree->dlg, NULL);
- ast_sip_dialog_set_endpoint(sub_tree->dlg, NULL);
- pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
-
- return 0;
-}
-
static void add_subscription(struct sip_subscription_tree *obj)
{
SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
@@ -1063,14 +1046,32 @@ static void remove_subscription(struct sip_subscription_tree *obj)
AST_RWLIST_TRAVERSE_SAFE_END;
}
-static void subscription_destructor(void *obj)
+static void destroy_subscription(struct ast_sip_subscription *sub)
{
- struct ast_sip_subscription *sub = obj;
-
ast_debug(3, "Destroying SIP subscription to resource %s\n", sub->resource);
ast_free(sub->body_text);
+ AST_VECTOR_FREE(&sub->children);
ao2_cleanup(sub->datastores);
+ ast_free(sub);
+}
+
+static void destroy_subscriptions(struct ast_sip_subscription *root)
+{
+ int i;
+
+ if (!root) {
+ return;
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(&root->children); ++i) {
+ struct ast_sip_subscription *child;
+
+ child = AST_VECTOR_GET(&root->children, i);
+ destroy_subscriptions(child);
+ }
+
+ destroy_subscription(root);
}
static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_subscription_handler *handler,
@@ -1079,7 +1080,7 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
struct ast_sip_subscription *sub;
pjsip_sip_uri *contact_uri;
- sub = ao2_alloc(sizeof(*sub) + strlen(resource) + 1, subscription_destructor);
+ sub = ast_calloc(1, sizeof(*sub) + strlen(resource) + 1);
if (!sub) {
return NULL;
}
@@ -1087,13 +1088,13 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
sub->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
if (!sub->datastores) {
- ao2_ref(sub, -1);
+ destroy_subscription(sub);
return NULL;
}
sub->body_text = ast_str_create(128);
if (!sub->body_text) {
- ao2_ref(sub, -1);
+ destroy_subscription(sub);
return NULL;
}
@@ -1104,7 +1105,7 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
sub->handler = handler;
sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE;
- sub->tree = tree;
+ sub->tree = ao2_bump(tree);
return sub;
}
@@ -1132,6 +1133,7 @@ static struct ast_sip_subscription *create_virtual_subscriptions(const struct as
sub->full_state = current->full_state;
sub->body_generator = generator;
+ AST_VECTOR_INIT(&sub->children, AST_VECTOR_SIZE(&current->children));
for (i = 0; i < AST_VECTOR_SIZE(&current->children); ++i) {
struct ast_sip_subscription *child;
@@ -1166,7 +1168,6 @@ static void shutdown_subscriptions(struct ast_sip_subscription *sub)
if (AST_VECTOR_SIZE(&sub->children) > 0) {
for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
shutdown_subscriptions(AST_VECTOR_GET(&sub->children, i));
- ao2_cleanup(AST_VECTOR_GET(&sub->children, i));
}
return;
}
@@ -1176,37 +1177,55 @@ static void shutdown_subscriptions(struct ast_sip_subscription *sub)
sub->handler->subscription_shutdown(sub);
}
}
+static int subscription_unreference_dialog(void *obj)
+{
+ struct sip_subscription_tree *sub_tree = obj;
+
+ /* This is why we keep the dialog on the subscription. When the subscription
+ * is destroyed, there is no guarantee that the underlying dialog is ready
+ * to be destroyed. Furthermore, there's no guarantee in the opposite direction
+ * either. The dialog could be destroyed before our subscription is. We fix
+ * this problem by keeping a reference to the dialog until it is time to
+ * destroy the subscription. We need to have the dialog available when the
+ * subscription is destroyed so that we can guarantee that our attempt to
+ * remove the serializer will be successful.
+ */
+ pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
+ sub_tree->dlg = NULL;
+
+ return 0;
+}
static void subscription_tree_destructor(void *obj)
{
struct sip_subscription_tree *sub_tree = obj;
+ ast_debug(3, "Destroying subscription tree %p\n", sub_tree);
+
remove_subscription(sub_tree);
- subscription_persistence_remove(sub_tree);
ao2_cleanup(sub_tree->endpoint);
- if (sub_tree->dlg) {
- ast_sip_push_task_synchronous(NULL, subscription_remove_serializer, sub_tree);
- }
-
- shutdown_subscriptions(sub_tree->root);
- ao2_cleanup(sub_tree->root);
+ destroy_subscriptions(sub_tree->root);
+ ast_sip_push_task_synchronous(sub_tree->serializer, subscription_unreference_dialog, sub_tree);
ast_taskprocessor_unreference(sub_tree->serializer);
ast_module_unref(ast_module_info->self);
}
+void ast_sip_subscription_destroy(struct ast_sip_subscription *sub)
+{
+ ast_debug(3, "Removing subscription %p reference to subscription tree %p\n", sub, sub->tree);
+ ao2_cleanup(sub->tree);
+}
+
static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)
{
- /* We keep a reference to the dialog until our subscription is destroyed. See
- * the subscription_destructor for more details
- */
- pjsip_dlg_inc_session(dlg, &pubsub_module);
sub_tree->dlg = dlg;
ast_sip_dialog_set_serializer(dlg, sub_tree->serializer);
ast_sip_dialog_set_endpoint(dlg, sub_tree->endpoint);
pjsip_evsub_set_mod_data(sub_tree->evsub, pubsub_module.id, sub_tree);
+ pjsip_dlg_inc_session(dlg, &pubsub_module);
}
static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint)
@@ -1497,7 +1516,11 @@ static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree,
ast_str_append(buf, 0, "Endpoint: %s\r\n",
ast_sorcery_object_get_id(sub_tree->endpoint));
- ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
+ if (sub_tree->dlg) {
+ ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
+ } else {
+ ast_copy_string(str, "<unknown>", sizeof(str));
+ }
ast_str_append(buf, 0, "Callid: %s\r\n", str);
ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(sub_tree->evsub));
@@ -1518,10 +1541,12 @@ static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree,
void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, const char *header)
{
- pjsip_dialog *dlg = sub->tree->dlg;
- pjsip_msg *msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
+ pjsip_dialog *dlg;
+ pjsip_msg *msg;
pj_str_t name;
+ dlg = sub->tree->dlg;
+ msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
pj_cstr(&name, header);
return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
@@ -1654,6 +1679,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
#ifdef TEST_FRAMEWORK
struct ast_sip_endpoint *endpoint = sub_tree->endpoint;
#endif
+ pjsip_evsub *evsub = sub_tree->evsub;
int res;
if (allocate_tdata_buffer(tdata)) {
@@ -1661,13 +1687,13 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
return -1;
}
- res = pjsip_evsub_send_request(sub_tree->evsub, tdata) == PJ_SUCCESS ? 0 : -1;
+ res = pjsip_evsub_send_request(evsub, tdata) == PJ_SUCCESS ? 0 : -1;
subscription_persistence_update(sub_tree, NULL);
ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
"StateText: %s\r\n"
"Endpoint: %s\r\n",
- pjsip_evsub_get_state_name(sub_tree->evsub),
+ pjsip_evsub_get_state_name(evsub),
ast_sorcery_object_get_id(endpoint));
return res;
@@ -2075,6 +2101,8 @@ static pjsip_require_hdr *create_require_eventlist(pj_pool_t *pool)
/*!
* \brief Send a NOTIFY request to a subscriber
*
+ * \pre sub_tree->dlg is locked
+ *
* \param sub_tree The subscription tree representing the subscription
* \param force_full_state If true, ignore resource list settings and send full resource list state.
* \retval 0 Success
@@ -2107,6 +2135,9 @@ static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int forc
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) require);
}
+ if (sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED) {
+ sub_tree->last_notify = 1;
+ }
if (sip_subscription_send_request(sub_tree, tdata)) {
return -1;
}
@@ -2119,13 +2150,16 @@ static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int forc
static int serialized_send_notify(void *userdata)
{
struct sip_subscription_tree *sub_tree = userdata;
+ pjsip_dialog *dlg = sub_tree->dlg;
+ pjsip_dlg_inc_lock(dlg);
/* It's possible that between when the notification was scheduled
* and now, that a new SUBSCRIBE arrived, requiring full state to be
* sent out in an immediate NOTIFY. If that has happened, we need to
* bail out here instead of sending the batched NOTIFY.
*/
if (!sub_tree->send_scheduled_notify) {
+ pjsip_dlg_dec_lock(dlg);
ao2_cleanup(sub_tree);
return 0;
}
@@ -2135,6 +2169,7 @@ static int serialized_send_notify(void *userdata)
"Resource: %s",
sub_tree->root->resource);
sub_tree->notify_sched_id = -1;
+ pjsip_dlg_dec_lock(dlg);
ao2_cleanup(sub_tree);
return 0;
}
@@ -2167,8 +2202,19 @@ static int schedule_notification(struct sip_subscription_tree *sub_tree)
int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip_body_data *notify_data,
int terminate)
{
+ int res;
+ pjsip_dialog *dlg = sub->tree->dlg;
+
+ pjsip_dlg_inc_lock(dlg);
+
+ if (!sub->tree->evsub) {
+ pjsip_dlg_dec_lock(dlg);
+ return 0;
+ }
+
if (ast_sip_pubsub_generate_body_content(ast_sip_subscription_get_body_type(sub),
ast_sip_subscription_get_body_subtype(sub), notify_data, &sub->body_text)) {
+ pjsip_dlg_dec_lock(dlg);
return -1;
}
@@ -2178,9 +2224,8 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip
}
if (sub->tree->notification_batch_interval) {
- return schedule_notification(sub->tree);
+ res = schedule_notification(sub->tree);
} else {
- int res;
/* See the note in pubsub_on_rx_refresh() for why sub->tree is refbumped here */
ao2_ref(sub->tree, +1);
res = send_notify(sub->tree, 0);
@@ -2188,9 +2233,10 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip
"Resource: %s",
sub->tree->root->resource);
ao2_ref(sub->tree, -1);
-
- return res;
}
+
+ pjsip_dlg_dec_lock(dlg);
+ return res;
}
void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
@@ -2200,7 +2246,9 @@ void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *
void ast_sip_subscription_get_remote_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
{
- pjsip_dialog *dlg = sub->tree->dlg;
+ pjsip_dialog *dlg;
+
+ dlg = sub->tree->dlg;
ast_copy_pj_str(buf, &dlg->remote.info_str, size);
}
@@ -3139,10 +3187,88 @@ static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
return PJ_FALSE;
}
+static void set_state_terminated(struct ast_sip_subscription *sub)
+{
+ int i;
+
+ sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
+ for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
+ set_state_terminated(AST_VECTOR_GET(&sub->children, i));
+ }
+}
+
+/* XXX This function and serialized_pubsub_on_rx_refresh are nearly identical */
+static int serialized_pubsub_on_server_timeout(void *userdata)
+{
+ struct sip_subscription_tree *sub_tree = userdata;
+ pjsip_dialog *dlg = sub_tree->dlg;
+
+ pjsip_dlg_inc_lock(dlg);
+ if (!sub_tree->evsub) {
+ pjsip_dlg_dec_lock(dlg);
+ return 0;
+ }
+ set_state_terminated(sub_tree->root);
+ send_notify(sub_tree, 1);
+ ast_test_suite_event_notify("SUBSCRIPTION_TERMINATED",
+ "Resource: %s",
+ sub_tree->root->resource);
+
+ pjsip_dlg_dec_lock(dlg);
+ ao2_cleanup(sub_tree);
+ return 0;
+}
+
+/*!
+ * \brief PJSIP callback when underlying SIP subscription changes state
+ *
+ * This callback is a bit of a mess, because it's not always called when
+ * you might expect it to be, and it can be called multiple times for the
+ * same state.
+ *
+ * For instance, this function is not called at all when an incoming SUBSCRIBE
+ * arrives to refresh a subscription. That makes sense in a way, since the
+ * subscription state has not made a change; it was active and remains active.
+ *
+ * However, if an incoming SUBSCRIBE arrives to end a subscription, then this
+ * will be called into once upon receiving the SUBSCRIBE (after the call to
+ * pubsub_on_rx_refresh) and again when sending a NOTIFY to end the subscription.
+ * In both cases, the apparent state of the subscription is "terminated".
+ *
+ * However, the double-terminated state changes don't happen in all cases. For
+ * instance, if a subscription expires, then the only time this callback is
+ * called is when we send the NOTIFY to end the subscription.
+ *
+ * As far as state changes are concerned, we only ever care about transitions
+ * to the "terminated" state. The action we take here is dependent on the
+ * conditions behind why the state change to "terminated" occurred. If the
+ * state change has occurred because we are sending a NOTIFY to end the
+ * subscription, we consider this to be the final hurrah of the subscription
+ * and take measures to start shutting things down. If the state change to
+ * terminated occurs for a different reason (e.g. transaction timeout,
+ * incoming SUBSCRIBE to end the subscription), then we push a task to
+ * send out a NOTIFY. When that NOTIFY is sent, this callback will be
+ * called again and we will actually shut down the subscription. The
+ * subscription tree's last_notify field let's us know if this is being
+ * called as a result of a terminating NOTIFY or not.
+ *
+ * There is no guarantee that this function will be called from a serializer
+ * thread since it can be called due to a transaction timeout. Therefore
+ * synchronization primitives are necessary to ensure that no operations
+ * step on each others' toes. The dialog lock is always held when this
+ * callback is called, so we ensure that relevant structures that may
+ * be touched in this function are always protected by the dialog lock
+ * elsewhere as well. The dialog lock in particular protects
+ *
+ * \li The subscription tree's last_notify field
+ * \li The subscription tree's evsub pointer
+ */
static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
{
struct sip_subscription_tree *sub_tree;
+ ast_debug(3, "on_evsub_state called with state %s\n", pjsip_evsub_get_state_name(evsub));
+
if (pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
return;
}
@@ -3152,21 +3278,63 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
return;
}
- ao2_cleanup(sub_tree);
+ if (!sub_tree->last_notify) {
+ if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_server_timeout, ao2_bump(sub_tree))) {
+ ast_log(LOG_ERROR, "Failed to push task to send final NOTIFY.\n");
+ ao2_ref(sub_tree, -1);
+ } else {
+ return;
+ }
+ }
pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
+ sub_tree->evsub = NULL;
+ ast_sip_dialog_set_serializer(sub_tree->dlg, NULL);
+ ast_sip_dialog_set_endpoint(sub_tree->dlg, NULL);
+ subscription_persistence_remove(sub_tree);
+ shutdown_subscriptions(sub_tree->root);
+
+ /* Remove evsub's reference to the sub_tree */
+ ao2_ref(sub_tree, -1);
}
-static void set_state_terminated(struct ast_sip_subscription *sub)
+static int serialized_pubsub_on_rx_refresh(void *userdata)
{
- int i;
+ struct sip_subscription_tree *sub_tree = userdata;
+ pjsip_dialog *dlg = sub_tree->dlg;
- sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
- for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
- set_state_terminated(AST_VECTOR_GET(&sub->children, i));
+ pjsip_dlg_inc_lock(dlg);
+ if (!sub_tree->evsub) {
+ pjsip_dlg_dec_lock(dlg);
+ return 0;
+ }
+
+ if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ set_state_terminated(sub_tree->root);
}
+
+ send_notify(sub_tree, 1);
+
+ ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ?
+ "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED",
+ "Resource: %s", sub_tree->root->resource);
+
+ pjsip_dlg_dec_lock(dlg);
+ ao2_cleanup(sub_tree);
+ return 0;
}
+/*!
+ * \brief Called whenever an in-dialog SUBSCRIBE is received
+ *
+ * This includes both SUBSCRIBE requests that actually refresh the subscription
+ * as well as SUBSCRIBE requests that end the subscription.
+ *
+ * In the case where the SUBSCRIBE is actually refreshing the subscription we
+ * push a task to send an appropriate NOTIFY request. In the case where the
+ * SUBSCRIBE is ending the subscription, we let the pubsub_on_evsub_state
+ * callback take care of sending the terminal NOTIFY request instead.
+ */
static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
{
@@ -3177,31 +3345,19 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
return;
}
- /* If sending a NOTIFY to terminate a subscription, then pubsub_on_evsub_state()
- * will be called when we send the NOTIFY, and that will result in dropping the
- * refcount of sub_tree by one, and possibly destroying the sub_tree. We need to
- * hold a reference to the sub_tree until this function returns so that we don't
- * try to read from or write to freed memory by accident
+ /* PJSIP will set the evsub's state to terminated before calling into this function
+ * if the Expires value of the incoming SUBSCRIBE is 0.
*/
- ao2_ref(sub_tree, +1);
-
- if (pjsip_evsub_get_state(evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
- set_state_terminated(sub_tree->root);
- }
-
- if (send_notify(sub_tree, 1)) {
- *p_st_code = 500;
+ if (pjsip_evsub_get_state(sub_tree->evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
+ if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_rx_refresh, ao2_bump(sub_tree))) {
+ /* If we can't push the NOTIFY refreshing task...we'll just go with it. */
+ ao2_ref(sub_tree, -1);
+ }
}
- ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ?
- "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED",
- "Resource: %s", sub_tree->root->resource);
-
if (sub_tree->is_list) {
pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool));
}
-
- ao2_ref(sub_tree, -1);
}
static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code,
@@ -3239,31 +3395,24 @@ static void pubsub_on_client_refresh(pjsip_evsub *evsub)
ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_client_refresh, sub_tree);
}
-static int serialized_pubsub_on_server_timeout(void *userdata)
-{
- struct sip_subscription_tree *sub_tree = userdata;
-
- set_state_terminated(sub_tree->root);
- send_notify(sub_tree, 1);
- ast_test_suite_event_notify("SUBSCRIPTION_TERMINATED",
- "Resource: %s",
- sub_tree->root->resource);
-
- ao2_cleanup(sub_tree);
- return 0;
-}
-
static void pubsub_on_server_timeout(pjsip_evsub *evsub)
{
- struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+ struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
if (!sub_tree) {
- /* if a subscription has been terminated and the subscription
- timeout/expires is less than the time it takes for all pending
- transactions to end then the subscription timer will not have
- been canceled yet and sub will be null, so do nothing since
- the subscription has already been terminated. */
- return;
+ /* PJSIP does not terminate the server timeout timer when a SUBSCRIBE
+ * with Expires: 0 arrives to end a subscription, nor does it terminate
+ * this timer when we send a NOTIFY request in response to receiving such
+ * a SUBSCRIBE. PJSIP does not stop the server timeout timer until the
+ * NOTIFY transaction has finished (either through receiving a response
+ * or through a transaction timeout).
+ *
+ * Therefore, it is possible that we can be told that a server timeout
+ * occurred after we already thought that the subscription had been
+ * terminated. In such a case, we will have already removed the sub_tree
+ * from the evsub's mod_data array.
+ */
+ return;
}
ao2_ref(sub_tree, +1);
diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in
index 58702d6c4..661652489 100644
--- a/res/res_pjsip_pubsub.exports.in
+++ b/res/res_pjsip_pubsub.exports.in
@@ -38,6 +38,7 @@
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;
local:
*;
};
diff --git a/rest-api-templates/api.wiki.mustache b/rest-api-templates/api.wiki.mustache
index 73aa2448a..0a54a64a7 100644
--- a/rest-api-templates/api.wiki.mustache
+++ b/rest-api-templates/api.wiki.mustache
@@ -67,7 +67,7 @@ h3. Header parameters
h3. Error Responses
{{#error_responses}}
-* {{code}} - {{{reason}}}
+* {{code}} - {{{wiki_reason}}}
{{/error_responses}}
{{/has_error_responses}}
{{/operations}}
diff --git a/rest-api-templates/asterisk_processor.py b/rest-api-templates/asterisk_processor.py
index ab8a8afd2..68a679994 100644
--- a/rest-api-templates/asterisk_processor.py
+++ b/rest-api-templates/asterisk_processor.py
@@ -199,6 +199,8 @@ class AsteriskProcessor(SwaggerPostProcessor):
raise SwaggerError("Summary should end with .", context)
operation.wiki_summary = wikify(operation.summary or "")
operation.wiki_notes = wikify(operation.notes or "")
+ for error_response in operation.error_responses:
+ error_response.wiki_reason = wikify(error_response.reason or "")
operation.parse_body = (operation.body_parameter or operation.has_query_parameters) and True
def process_parameter(self, parameter, context):