From 1cd704de365c6711f8e156f62cea51e21c77e5d0 Mon Sep 17 00:00:00 2001 From: Nathan Bruning Date: Thu, 22 Feb 2018 19:18:48 +0100 Subject: res_pjsip_notify.c: enable in-dialog NOTIFY This patch adds support to send in-dialog SIP NOTIFY commands on chan_pjsip channels, similar to the functionality recently added for chan_sip (ASTERISK_27461). This extends res_pjsip_notify to allow for in-dialog messages. ASTERISK-27697 Change-Id: If7f3151a6d633e414d5dc319d5efc1443c43dd29 --- CHANGES | 5 ++ res/res_pjsip.c | 3 +- res/res_pjsip_notify.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 225 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index ac50a6dc9..3a81ca7ba 100644 --- a/CHANGES +++ b/CHANGES @@ -122,6 +122,11 @@ res_pjproject MALLOC_DEBUG. The cache gets in the way of determining if the pool contents are used after free and who freed it. +res_pjsip_notify +------------------ + * Extend the PJSIPNotify AMI command to send an in-dialog notify on a + channel. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 15.2.0 to Asterisk 15.3.0 ------------ ------------------------------------------------------------------------------ diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 7c9929740..3241d777d 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -3801,8 +3801,6 @@ int ast_sip_create_request(const char *method, struct pjsip_dialog *dlg, { const pjsip_method *pmethod = get_pjsip_method(method); - ast_assert(endpoint != NULL); - if (!pmethod) { ast_log(LOG_WARNING, "Unknown method '%s'. Cannot send request\n", method); return -1; @@ -3811,6 +3809,7 @@ int ast_sip_create_request(const char *method, struct pjsip_dialog *dlg, if (dlg) { return create_in_dialog_request(pmethod, dlg, tdata); } else { + ast_assert(endpoint != NULL); return create_out_of_dialog_request(pmethod, endpoint, uri, contact, tdata); } } diff --git a/res/res_pjsip_notify.c b/res/res_pjsip_notify.c index 253cf9ac8..98a75c964 100644 --- a/res/res_pjsip_notify.c +++ b/res/res_pjsip_notify.c @@ -25,6 +25,7 @@ #include "asterisk.h" #include +#include #include "asterisk/cli.h" #include "asterisk/config.h" @@ -32,12 +33,13 @@ #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_session.h" #include "asterisk/sorcery.h" /*** DOCUMENTATION - Send a NOTIFY to either an endpoint or an arbitrary URI. + Send a NOTIFY to either an endpoint, an arbitrary URI, or inside a SIP dialog. @@ -47,6 +49,9 @@ Abritrary URI to which to send the NOTIFY. + + Channel name to send the NOTIFY. Must be a PJSIP channel. + Appends variables as headers/content to the NOTIFY. If the variable is named Content, then the value will compose the body @@ -55,14 +60,14 @@ - Sends a NOTIFY to an endpoint or an arbitrary URI. + Sends a NOTIFY to an endpoint, an arbitrary URI, or inside a SIP dialog. All parameters for this event must be specified in the body of this request via multiple Variable: name=value sequences. - One (and only one) of Endpoint or - URI must be specified. If URI is used, - the default outbound endpoint will be used to send the message. If the default - outbound endpoint isn't configured, this command can not send to an arbitrary - URI. + One (and only one) of Endpoint, + URI, or Channel must be specified. + If URI is used, the default outbound endpoint will be used + to send the message. If the default outbound endpoint isn't configured, this command + can not send to an arbitrary URI. @@ -289,6 +294,16 @@ struct notify_uri_data { void (*build_notify)(pjsip_tx_data *, void *); }; +/*! + * \internal + * \brief Structure to hold task data for notifications (channel variant) + */ +struct notify_channel_data { + struct ast_sip_session *session; + void *info; + void (*build_notify)(pjsip_tx_data *, void *); +}; + static void notify_cli_uri_data_destroy(void *obj) { struct notify_uri_data *data = obj; @@ -381,6 +396,19 @@ static void notify_ami_uri_data_destroy(void *obj) ast_variables_destroy(info); } +/*! + * \internal + * \brief Destroy the notify AMI channel data releasing any resources. + */ +static void notify_ami_channel_data_destroy(void *obj) +{ + struct notify_channel_data *data = obj; + struct ast_variable *info = data->info; + + ao2_cleanup(data->session); + ast_variables_destroy(info); +} + static void build_ami_notify(pjsip_tx_data *tdata, void *info); /*! @@ -430,6 +458,28 @@ static struct notify_uri_data* notify_ami_uri_data_create( return data; } +/*! + * \internal + * \brief Construct a notify channel data object for AMI. + */ +static struct notify_channel_data *notify_ami_channel_data_create( + struct ast_sip_session *session, void *info) +{ + struct notify_channel_data *data; + + data = ao2_alloc_options(sizeof(*data), notify_ami_channel_data_destroy, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!data) { + return NULL; + } + + data->session = session; + data->info = info; + data->build_notify = build_ami_notify; + + return data; +} + /*! * \internal * \brief Checks if the given header name is not allowed. @@ -672,9 +722,45 @@ static int notify_uri(void *obj) return 0; } +/*! + * \internal + * \brief Send a notify request to a channel. + */ +static int notify_channel(void *obj) +{ + RAII_VAR(struct notify_channel_data *, data, obj, ao2_cleanup); + pjsip_tx_data *tdata; + struct pjsip_dialog *dlg; + + if (!data->session->channel + || !data->session->inv_session + || data->session->inv_session->state < PJSIP_INV_STATE_EARLY + || data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { + return -1; + } + + ast_debug(1, "Sending notify on channel %s\n", ast_channel_name(data->session->channel)); + + dlg = data->session->inv_session->dlg; + + if (ast_sip_create_request("NOTIFY", dlg, NULL, NULL, NULL, &tdata)) { + return -1; + } + + ast_sip_add_header(tdata, "Subscription-State", "terminated"); + data->build_notify(tdata, data->info); + + if (ast_sip_send_request(tdata, dlg, NULL, NULL, NULL)) { + return -1; + } + + return 0; +} + enum notify_result { SUCCESS, INVALID_ENDPOINT, + INVALID_CHANNEL, ALLOC_ERROR, TASK_PUSH_ERROR }; @@ -684,6 +770,10 @@ typedef struct notify_data *(*task_data_create)( typedef struct notify_uri_data *(*task_uri_data_create)( const char *uri, void *info); + +typedef struct notify_channel_data *(*task_channel_data_create)( + struct ast_sip_session *session, void *info); + /*! * \internal * \brief Send a NOTIFY request to the endpoint within a threaded task. @@ -732,6 +822,68 @@ static enum notify_result push_notify_uri(const char *uri, void *info, return SUCCESS; } +/*! + * \internal + * \brief Send a NOTIFY request in a channel within an threaded task. + */ +static enum notify_result push_notify_channel(const char *channel_name, void *info, + task_channel_data_create data_create) +{ + struct notify_channel_data *data; + struct ast_channel *ch; + struct ast_sip_session *session; + struct ast_sip_channel_pvt *ch_pvt; + + /* note: this increases the refcount of the channel */ + ch = ast_channel_get_by_name(channel_name); + if (!ch) { + ast_debug(1, "No channel found with name %s", channel_name); + return INVALID_CHANNEL; + } + + if (strcmp(ast_channel_tech(ch)->type, "PJSIP")) { + ast_log(LOG_WARNING, "Channel was a non-PJSIP channel: %s\n", channel_name); + ast_channel_unref(ch); + return INVALID_CHANNEL; + } + + ast_channel_lock(ch); + ch_pvt = ast_channel_tech_pvt(ch); + session = ch_pvt->session; + + if (!session || !session->inv_session + || session->inv_session->state < PJSIP_INV_STATE_EARLY + || session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { + ast_debug(1, "No active session for channel %s\n", channel_name); + ast_channel_unlock(ch); + ast_channel_unref(ch); + return INVALID_CHANNEL; + } + + ao2_ref(session, +1); + ast_channel_unlock(ch); + + /* don't keep a reference to the channel, we've got a reference to the session */ + ast_channel_unref(ch); + + /* + * data_create will take ownership of the session, + * and take care of releasing the ref. + */ + data = data_create(session, info); + if (!data) { + ao2_ref(session, -1); + return ALLOC_ERROR; + } + + if (ast_sip_push_task(session->serializer, notify_channel, data)) { + ao2_ref(data, -1); + return TASK_PUSH_ERROR; + } + + return SUCCESS; +} + /*! * \internal * \brief Do completion on the endpoint. @@ -915,6 +1067,10 @@ static void manager_notify_endpoint(struct mansession *s, } switch (push_notify(endpoint_name, vars, notify_ami_data_create)) { + case INVALID_CHANNEL: + /* Shouldn't be possible. */ + ast_assert(0); + break; case INVALID_ENDPOINT: ast_variables_destroy(vars); astman_send_error_va(s, m, "Unable to retrieve endpoint %s", @@ -944,6 +1100,10 @@ static void manager_notify_uri(struct mansession *s, struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL); switch (push_notify_uri(uri, vars, notify_ami_uri_data_create)) { + case INVALID_CHANNEL: + /* Shouldn't be possible. */ + ast_assert(0); + break; case INVALID_ENDPOINT: /* Shouldn't be possible. */ ast_assert(0); @@ -962,6 +1122,38 @@ static void manager_notify_uri(struct mansession *s, } } +/*! + * \internal + * \brief Completes SIPNotify AMI command in channel mode. + */ +static void manager_notify_channel(struct mansession *s, + const struct message *m, const char *channel) +{ + struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL); + + switch (push_notify_channel(channel, vars, notify_ami_channel_data_create)) { + case INVALID_CHANNEL: + ast_variables_destroy(vars); + astman_send_error(s, m, "Channel not found"); + break; + case INVALID_ENDPOINT: + /* Shouldn't be possible. */ + ast_assert(0); + break; + case ALLOC_ERROR: + ast_variables_destroy(vars); + astman_send_error(s, m, "Unable to allocate NOTIFY task data"); + break; + case TASK_PUSH_ERROR: + /* Don't need to destroy vars since it is handled by cleanup in push_notify_channel */ + astman_send_error(s, m, "Unable to push Notify task"); + break; + case SUCCESS: + astman_send_ack(s, m, "NOTIFY sent"); + break; + } +} + /*! * \internal * \brief AMI entry point to send a SIP notify to an endpoint. @@ -970,16 +1162,32 @@ static int manager_notify(struct mansession *s, const struct message *m) { const char *endpoint_name = astman_get_header(m, "Endpoint"); const char *uri = astman_get_header(m, "URI"); + const char *channel = astman_get_header(m, "Channel"); + int count = 0; + + if (!ast_strlen_zero(endpoint_name)) { + ++count; + } + if (!ast_strlen_zero(uri)) { + ++count; + } + if (!ast_strlen_zero(channel)) { + ++count; + } - if (!ast_strlen_zero(endpoint_name) && !ast_strlen_zero(uri)) { - astman_send_error(s, m, "PJSIPNotify action can not handle a request specifying " - "both 'URI' and 'Endpoint'. You must use only one of the two.\n"); + if (1 < count) { + astman_send_error(s, m, + "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel. " + "You must use only one of them."); } else if (!ast_strlen_zero(endpoint_name)) { manager_notify_endpoint(s, m, endpoint_name); } else if (!ast_strlen_zero(uri)) { manager_notify_uri(s, m, uri); + } else if (!ast_strlen_zero(channel)) { + manager_notify_channel(s, m, channel); } else { - astman_send_error(s, m, "PJSIPNotify requires either an endpoint name or a SIP URI."); + astman_send_error(s, m, + "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel."); } return 0; -- cgit v1.2.3