asterisk/res/res_pjsip_aoc.c

699 lines
20 KiB
C
Raw Normal View History

res_pjsip_aoc: New module for sending advice-of-charge with chan_pjsip chan_sip supported sending AOC-D and AOC-E information in SIP INFO messages in an "AOC" header in a format that was originally defined by Snom. In the meantime, ETSI TS 124 647 introduced an XML-based AOC format that is supported by devices from multiple vendors, including Snom phones with firmware >= 8.4.2 (released in 2010). This commit adds a new res_pjsip_aoc module that inserts AOC information into outgoing messages or sends SIP INFO messages as described below. It also fixes a small issue in res_pjsip_session which didn't always call session supplements on outgoing_response. * AOC-S in the 180/183/200 responses to an INVITE request * AOC-S in SIP INFO (if a 200 response has already been sent or if the INVITE was sent by Asterisk) * AOC-D in SIP INFO * AOC-D in the 200 response to a BYE request (if the client hangs up) * AOC-D in a BYE request (if Asterisk hangs up) * AOC-E in the 200 response to a BYE request (if the client hangs up) * AOC-E in a BYE request (if Asterisk hangs up) The specification defines one more, AOC-S in an INVITE request, which is not implemented here because it is not currently possible in Asterisk to have AOC data ready at this point in call setup. Once specifying AOC-S via the dialplan or passing it through from another SIP channel's INVITE is possible, that might be added. The SIP INFO requests are sent out immediately when the AOC indication is received. The others are inserted into an appropriate outgoing message whenever that is ready to be sent. In the latter case, the XML is stored in a channel variable at the time the AOC indication is received. Depending on where the AOC indications are coming from (e.g. PRI or AMI), it may not always be possible to guarantee that the AOC-E is available in time for the BYE. Successfully tested AOC-D and both variants of AOC-E with a Snom D735 running firmware 10.1.127.10. It does not appear to properly support AOC-S however, so that could only be tested by inspecting SIP traces. ASTERISK-21502 #close Reported-by: Matt Jordan <mjordan@digium.com> Change-Id: Iebb7ad0d5f88526bc6629d3a1f9f11665434d333
2022-10-23 09:42:34 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Michael Kuron
*
* Michael Kuron <m.kuron@gmx.de>
*
* 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.
*/
/*** MODULEINFO
<depend>pjproject</depend>
<depend>res_pjsip</depend>
<support_level>extended</support_level>
***/
#include "asterisk.h"
#include <pjsip.h>
#include <pjlib.h>
#include "asterisk/aoc.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
static pj_xml_attr *aoc_xml_create_attr(pj_pool_t *pool, pj_xml_node *node,
const char *name, const char *value)
{
pj_xml_attr *attr;
attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
pj_strdup2(pool, &attr->name, name);
pj_strdup2(pool, &attr->value, value);
pj_xml_add_attr(node, attr);
return attr;
}
static pj_xml_node *aoc_xml_create_node(pj_pool_t *pool, pj_xml_node *parent,
const char *name)
{
pj_xml_node *node;
node = PJ_POOL_ZALLOC_T(pool, pj_xml_node);
pj_list_init(&node->attr_head);
pj_list_init(&node->node_head);
pj_strdup2(pool, &node->name, name);
if (parent) {
pj_xml_add_node(parent, node);
}
return node;
}
static void aoc_xml_set_node_content(pj_pool_t *pool, pj_xml_node *node,
const char *content)
{
pj_strdup2(pool, &node->content, content);
}
static char * aoc_format_amount(pj_pool_t *pool, unsigned int amount,
enum ast_aoc_currency_multiplier multiplier)
{
const size_t amount_max_size = 16;
char *amount_str;
amount_str = pj_pool_alloc(pool, amount_max_size);
switch (multiplier) {
case AST_AOC_MULT_ONETHOUSANDTH:
pj_ansi_snprintf(amount_str, amount_max_size, "%.3f", amount*0.001f);
break;
case AST_AOC_MULT_ONEHUNDREDTH:
pj_ansi_snprintf(amount_str, amount_max_size, "%.2f", amount*0.01f);
break;
case AST_AOC_MULT_ONETENTH:
pj_ansi_snprintf(amount_str, amount_max_size, "%.1f", amount*0.1f);
break;
case AST_AOC_MULT_ONE:
pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount);
break;
case AST_AOC_MULT_TEN:
pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount*10);
break;
case AST_AOC_MULT_HUNDRED:
pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount*100);
break;
case AST_AOC_MULT_THOUSAND:
pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount*1000);
break;
default:
pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount);
}
return amount_str;
}
static const char *aoc_time_scale_str(enum ast_aoc_time_scale value)
{
const char *str;
switch (value) {
default:
case AST_AOC_TIME_SCALE_HUNDREDTH_SECOND:
str = "one-hundredth-second";
break;
case AST_AOC_TIME_SCALE_TENTH_SECOND:
str = "one-tenth-second";
break;
case AST_AOC_TIME_SCALE_SECOND:
str = "one-second";
break;
case AST_AOC_TIME_SCALE_TEN_SECOND:
str = "ten-seconds";
break;
case AST_AOC_TIME_SCALE_MINUTE:
str = "one-minute";
break;
case AST_AOC_TIME_SCALE_HOUR:
str = "one-hour";
break;
case AST_AOC_TIME_SCALE_DAY:
str = "twenty-four-hours";
break;
}
return str;
}
static void aoc_datastore_destroy(void *obj)
{
char *xml = obj;
ast_free(xml);
}
static const struct ast_datastore_info aoc_s_datastore = {
.type = "AOC-S",
.destroy = aoc_datastore_destroy,
};
static const struct ast_datastore_info aoc_d_datastore = {
.type = "AOC-D",
.destroy = aoc_datastore_destroy,
};
static const struct ast_datastore_info aoc_e_datastore = {
.type = "AOC-E",
.destroy = aoc_datastore_destroy,
};
struct aoc_data {
struct ast_sip_session *session;
struct ast_aoc_decoded *decoded;
enum ast_channel_state channel_state;
};
static void aoc_release_pool(void * data)
{
pj_pool_t *pool = data;
pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
}
static int aoc_send_as_xml(void * data)
{
RAII_VAR(struct aoc_data *, adata, data, ao2_cleanup);
RAII_VAR(pj_pool_t *, pool, NULL, aoc_release_pool);
pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "AOC", 2048, 512);
if (!pool) {
ast_log(LOG_ERROR, "Could not create a memory pool for AOC XML\n");
return 1;
}
if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D ||
ast_aoc_get_msg_type(adata->decoded) == AST_AOC_E) {
pj_xml_node *aoc;
pj_xml_node *aoc_type;
pj_xml_node *charging_info = NULL;
pj_xml_node *charges;
pj_xml_node *charge;
char *xml;
size_t size;
const size_t xml_max_size = 512;
aoc = aoc_xml_create_node(pool, NULL, "aoc");
aoc_xml_create_attr(pool, aoc, "xmlns",
"http://uri.etsi.org/ngn/params/xml/simservs/aoc");
aoc_type = aoc_xml_create_node(pool, aoc,
ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D ? "aoc-d" : "aoc-e");
if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D) {
charging_info = aoc_xml_create_node(pool, aoc_type, "charging-info");
aoc_xml_set_node_content(pool, charging_info,
ast_aoc_get_total_type(adata->decoded) == AST_AOC_SUBTOTAL ? "subtotal" : "total");
}
charges = aoc_xml_create_node(pool, aoc_type, "recorded-charges");
if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_FREE) {
charge = aoc_xml_create_node(pool, charges, "free-charge");
} else if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_CURRENCY ||
ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_UNIT) {
charge = aoc_xml_create_node(pool, charges, "recorded-currency-units");
} else {
charge = aoc_xml_create_node(pool, charges, "not-available");
}
if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_CURRENCY) {
const char *currency;
pj_xml_node *amount;
char *amount_str;
currency = ast_aoc_get_currency_name(adata->decoded);
if (!ast_strlen_zero(currency)) {
pj_xml_node *currency_id;
currency_id = aoc_xml_create_node(pool, charge, "currency-id");
aoc_xml_set_node_content(pool, currency_id, currency);
}
amount = aoc_xml_create_node(pool, charge, "currency-amount");
amount_str = aoc_format_amount(pool, ast_aoc_get_currency_amount(adata->decoded),
ast_aoc_get_currency_multiplier(adata->decoded));
aoc_xml_set_node_content(pool, amount, amount_str);
} else if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_UNIT) {
pj_xml_node *currency_id;
const struct ast_aoc_unit_entry *unit_entry;
currency_id = aoc_xml_create_node(pool, charge, "currency-id");
aoc_xml_set_node_content(pool, currency_id, "UNIT");
unit_entry = ast_aoc_get_unit_info(adata->decoded, 0);
if (unit_entry) {
pj_xml_node *amount;
char *amount_str;
amount = aoc_xml_create_node(pool, charge, "currency-amount");
amount_str = aoc_format_amount(pool, unit_entry->amount,
AST_AOC_MULT_ONE);
aoc_xml_set_node_content(pool, amount, amount_str);
}
}
xml = pj_pool_alloc(pool, xml_max_size);
size = pj_xml_print(aoc, xml, xml_max_size - 1, PJ_TRUE);
if (size >= xml_max_size) {
ast_log(LOG_ERROR, "aoc+xml body text too large\n");
return 1;
}
xml[size] = 0;
if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D) {
RAII_VAR(struct ast_datastore *, datastore,
ast_sip_session_get_datastore(adata->session, aoc_d_datastore.type),
ao2_cleanup);
struct pjsip_tx_data *tdata;
struct ast_sip_body body = {
.type = "application",
.subtype = "vnd.etsi.aoc+xml",
.body_text = xml
};
if (ast_sip_create_request("INFO", adata->session->inv_session->dlg,
adata->session->endpoint, NULL, NULL, &tdata)) {
ast_log(LOG_ERROR, "Could not create AOC INFO request\n");
return 1;
}
if (ast_sip_add_body(tdata, &body)) {
ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
pjsip_tx_data_dec_ref(tdata);
return 1;
}
ast_sip_session_send_request(adata->session, tdata);
if (!datastore) {
datastore = ast_sip_session_alloc_datastore(&aoc_d_datastore, aoc_d_datastore.type);
if (!datastore) {
ast_log(LOG_ERROR, "Unable to create datastore for AOC-D.\n");
return 1;
}
datastore->data = NULL;
if (ast_sip_session_add_datastore(adata->session, datastore)) {
ast_log(LOG_ERROR, "Unable to create datastore for AOC-D.\n");
return 1;
}
} else {
ast_free(datastore->data);
}
aoc_xml_set_node_content(pool, charging_info, "total");
size = pj_xml_print(aoc, xml, xml_max_size - 1, PJ_TRUE);
xml[size] = 0;
datastore->data = ast_strdup(xml);
} else if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_E) {
RAII_VAR(struct ast_datastore *, datastore,
ast_sip_session_get_datastore(adata->session, aoc_e_datastore.type),
ao2_cleanup);
if (!datastore) {
datastore = ast_sip_session_alloc_datastore(&aoc_e_datastore, aoc_e_datastore.type);
if (!datastore) {
ast_log(LOG_ERROR, "Unable to create datastore for AOC-E.\n");
return 1;
}
datastore->data = NULL;
if (ast_sip_session_add_datastore(adata->session, datastore)) {
ast_log(LOG_ERROR, "Unable to create datastore for AOC-E.\n");
return 1;
}
} else {
ast_free(datastore->data);
}
datastore->data = ast_strdup(xml);
}
} else if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_S) {
pj_xml_node *aoc;
pj_xml_node *aoc_type;
pj_xml_node *charged_items;
const struct ast_aoc_s_entry *entry;
int idx;
char *xml;
size_t size;
const size_t xml_max_size = 1024;
aoc = aoc_xml_create_node(pool, NULL, "aoc");
aoc_xml_create_attr(pool, aoc, "xmlns",
"http://uri.etsi.org/ngn/params/xml/simservs/aoc");
aoc_type = aoc_xml_create_node(pool, aoc, "aoc-s");
charged_items = aoc_xml_create_node(pool, aoc_type, "charged-items");
for (idx = 0; idx < ast_aoc_s_get_count(adata->decoded); idx++) {
pj_xml_node *charged_item;
pj_xml_node *charge;
if (!(entry = ast_aoc_s_get_rate_info(adata->decoded, idx))) {
break;
}
if (entry->charged_item == AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION) {
charged_item = aoc_xml_create_node(pool, charged_items, "basic");
} else if (entry->charged_item == AST_AOC_CHARGED_ITEM_CALL_ATTEMPT) {
charged_item = aoc_xml_create_node(pool, charged_items,
"communication-attempt");
} else if (entry->charged_item == AST_AOC_CHARGED_ITEM_CALL_SETUP) {
charged_item = aoc_xml_create_node(pool, charged_items,
"communication-setup");
} else {
continue;
}
if (entry->rate_type == AST_AOC_RATE_TYPE_FREE) {
charge = aoc_xml_create_node(pool, charged_item, "free-charge");
} else if (entry->rate_type == AST_AOC_RATE_TYPE_FLAT) {
charge = aoc_xml_create_node(pool, charged_item, "flat-rate");
} else if (entry->rate_type == AST_AOC_RATE_TYPE_DURATION &&
entry->charged_item == AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION) {
charge = aoc_xml_create_node(pool, charged_item, "price-time");
} else {
continue;
}
if (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ||
entry->rate_type == AST_AOC_RATE_TYPE_FLAT) {
const char *currency;
pj_xml_node *amount;
uint32_t amount_val;
enum ast_aoc_currency_multiplier multiplier_val;
char *amount_str;
currency = (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ?
entry->rate.duration.currency_name :
entry->rate.flat.currency_name);
if (!ast_strlen_zero(currency)) {
pj_xml_node *currency_id;
currency_id = aoc_xml_create_node(pool, charge, "currency-id");
aoc_xml_set_node_content(pool, currency_id, currency);
}
amount = aoc_xml_create_node(pool, charge, "currency-amount");
amount_val = (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ?
entry->rate.duration.amount : entry->rate.flat.amount);
multiplier_val = (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ?
entry->rate.duration.multiplier : entry->rate.flat.multiplier);
amount_str = aoc_format_amount(pool, amount_val, multiplier_val);
aoc_xml_set_node_content(pool, amount, amount_str);
}
if (entry->rate_type == AST_AOC_RATE_TYPE_DURATION) {
pj_xml_node *length_time_unit;
pj_xml_node *time_unit;
char *time_str;
pj_xml_node *scale;
pj_xml_node *charging_type;
length_time_unit = aoc_xml_create_node(pool, charge, "length-time-unit");
time_unit = aoc_xml_create_node(pool, length_time_unit, "time-unit");
time_str = aoc_format_amount(pool, entry->rate.duration.time,
AST_AOC_MULT_ONE);
aoc_xml_set_node_content(pool, time_unit, time_str);
scale = aoc_xml_create_node(pool, length_time_unit, "scale");
aoc_xml_set_node_content(pool, scale,
aoc_time_scale_str(entry->rate.duration.time_scale));
charging_type = aoc_xml_create_node(pool, charge, "charging-type");
aoc_xml_set_node_content(pool, charging_type,
entry->rate.duration.charging_type ? "step-function" :
"continuous");
}
}
xml = pj_pool_alloc(pool, xml_max_size);
size = pj_xml_print(aoc, xml, xml_max_size - 1, PJ_TRUE);
if (size >= xml_max_size) {
ast_log(LOG_ERROR, "aoc+xml body text too large\n");
return 1;
}
xml[size] = 0;
if (adata->channel_state == AST_STATE_UP ||
adata->session->call_direction == AST_SIP_SESSION_OUTGOING_CALL) {
struct pjsip_tx_data *tdata;
struct ast_sip_body body = {
.type = "application",
.subtype = "vnd.etsi.aoc+xml",
.body_text = xml
};
if (ast_sip_create_request("INFO", adata->session->inv_session->dlg,
adata->session->endpoint, NULL, NULL, &tdata)) {
ast_log(LOG_ERROR, "Could not create AOC INFO request\n");
return 1;
}
if (ast_sip_add_body(tdata, &body)) {
ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
pjsip_tx_data_dec_ref(tdata);
return 1;
}
ast_sip_session_send_request(adata->session, tdata);
} else {
RAII_VAR(struct ast_datastore *, datastore,
ast_sip_session_get_datastore(adata->session, aoc_s_datastore.type),
ao2_cleanup);
if (!datastore) {
datastore = ast_sip_session_alloc_datastore(&aoc_s_datastore, aoc_s_datastore.type);
if (!datastore) {
ast_log(LOG_ERROR, "Unable to create datastore for AOC-S.\n");
return 1;
}
if (ast_sip_session_add_datastore(adata->session, datastore)) {
ast_log(LOG_ERROR, "Unable to create datastore for AOC-S.\n");
return 1;
}
} else {
ast_free(datastore->data);
}
datastore->data = ast_strdup(xml);
}
}
return 0;
}
static void aoc_data_destroy(void * data)
{
struct aoc_data *adata = data;
ast_aoc_destroy_decoded(adata->decoded);
ao2_cleanup(adata->session);
}
static struct ast_frame *aoc_framehook(struct ast_channel *ast, struct ast_frame *f,
enum ast_framehook_event event, void *data)
{
struct ast_sip_channel_pvt *channel;
struct aoc_data *adata;
if (!f || f->frametype != AST_FRAME_CONTROL || event != AST_FRAMEHOOK_EVENT_WRITE ||
f->subclass.integer != AST_CONTROL_AOC) {
return f;
}
adata = ao2_alloc(sizeof(struct aoc_data), aoc_data_destroy);
if (!adata) {
ast_log(LOG_ERROR, "Failed to allocate AOC data\n");
return f;
}
adata->decoded = ast_aoc_decode((struct ast_aoc_encoded *) f->data.ptr, f->datalen, ast);
if (!adata->decoded) {
ast_log(LOG_ERROR, "Error decoding indicated AOC data\n");
ao2_ref(adata, -1);
return f;
}
channel = ast_channel_tech_pvt(ast);
adata->session = ao2_bump(channel->session);
adata->channel_state = ast_channel_state(ast);
if (ast_sip_push_task(adata->session->serializer, aoc_send_as_xml, adata)) {
ast_log(LOG_ERROR, "Unable to send AOC XML for channel %s\n", ast_channel_name(ast));
ao2_ref(adata, -1);
}
return &ast_null_frame;
}
static int aoc_consume(void *data, enum ast_frame_type type)
{
return (type == AST_FRAME_CONTROL) ? 1 : 0;
}
static void aoc_attach_framehook(struct ast_sip_session *session)
{
int framehook_id;
static struct ast_framehook_interface hook = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = aoc_framehook,
.consume_cb = aoc_consume,
};
if (!session->channel || !session->endpoint->send_aoc) {
return;
}
ast_channel_lock(session->channel);
framehook_id = ast_framehook_attach(session->channel, &hook);
if (framehook_id < 0) {
ast_log(LOG_WARNING, "Could not attach AOC Frame hook, AOC will be unavailable on '%s'\n",
ast_channel_name(session->channel));
}
ast_channel_unlock(session->channel);
}
static int aoc_incoming_invite_request(struct ast_sip_session *session,
struct pjsip_rx_data *rdata)
{
aoc_attach_framehook(session);
return 0;
}
static void aoc_outgoing_invite_request(struct ast_sip_session *session,
struct pjsip_tx_data *tdata)
{
aoc_attach_framehook(session);
}
static void aoc_bye_outgoing_response(struct ast_sip_session *session,
struct pjsip_tx_data *tdata)
{
struct ast_sip_body body = {
.type = "application",
.subtype = "vnd.etsi.aoc+xml",
};
RAII_VAR(struct ast_datastore *, datastore_d, ast_sip_session_get_datastore(session,
aoc_d_datastore.type), ao2_cleanup);
RAII_VAR(struct ast_datastore *, datastore_e, ast_sip_session_get_datastore(session,
aoc_e_datastore.type), ao2_cleanup);
if (datastore_e) {
body.body_text = datastore_e->data;
} else if (datastore_d) {
body.body_text = datastore_d->data;
}
else {
return;
}
if (ast_sip_add_body(tdata, &body)) {
ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
}
}
static void aoc_bye_outgoing_request(struct ast_sip_session *session,
struct pjsip_tx_data *tdata)
{
struct ast_sip_body body = {
.type = "application",
.subtype = "vnd.etsi.aoc+xml",
};
RAII_VAR(struct ast_datastore *, datastore_d, ast_sip_session_get_datastore(session,
aoc_d_datastore.type), ao2_cleanup);
RAII_VAR(struct ast_datastore *, datastore_e, ast_sip_session_get_datastore(session,
aoc_e_datastore.type), ao2_cleanup);
if (datastore_e) {
body.body_text = datastore_e->data;
} else if (datastore_d) {
body.body_text = datastore_d->data;
}
else {
return;
}
if (ast_sip_add_body(tdata, &body)) {
ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
}
}
static void aoc_invite_outgoing_response(struct ast_sip_session *session,
struct pjsip_tx_data *tdata)
{
pjsip_msg_body *multipart_body;
pjsip_multipart_part *part;
pj_str_t body_text;
pj_str_t type;
pj_str_t subtype;
RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session,
aoc_s_datastore.type), ao2_cleanup);
if (tdata->msg->line.status.code != 180 && tdata->msg->line.status.code != 183 &&
tdata->msg->line.status.code != 200) {
return;
}
if (!datastore) {
return;
}
if (tdata->msg->body && pjsip_media_type_cmp(&tdata->msg->body->content_type,
res_pjsip_aoc: New module for sending advice-of-charge with chan_pjsip chan_sip supported sending AOC-D and AOC-E information in SIP INFO messages in an "AOC" header in a format that was originally defined by Snom. In the meantime, ETSI TS 124 647 introduced an XML-based AOC format that is supported by devices from multiple vendors, including Snom phones with firmware >= 8.4.2 (released in 2010). This commit adds a new res_pjsip_aoc module that inserts AOC information into outgoing messages or sends SIP INFO messages as described below. It also fixes a small issue in res_pjsip_session which didn't always call session supplements on outgoing_response. * AOC-S in the 180/183/200 responses to an INVITE request * AOC-S in SIP INFO (if a 200 response has already been sent or if the INVITE was sent by Asterisk) * AOC-D in SIP INFO * AOC-D in the 200 response to a BYE request (if the client hangs up) * AOC-D in a BYE request (if Asterisk hangs up) * AOC-E in the 200 response to a BYE request (if the client hangs up) * AOC-E in a BYE request (if Asterisk hangs up) The specification defines one more, AOC-S in an INVITE request, which is not implemented here because it is not currently possible in Asterisk to have AOC data ready at this point in call setup. Once specifying AOC-S via the dialplan or passing it through from another SIP channel's INVITE is possible, that might be added. The SIP INFO requests are sent out immediately when the AOC indication is received. The others are inserted into an appropriate outgoing message whenever that is ready to be sent. In the latter case, the XML is stored in a channel variable at the time the AOC indication is received. Depending on where the AOC indications are coming from (e.g. PRI or AMI), it may not always be possible to guarantee that the AOC-E is available in time for the BYE. Successfully tested AOC-D and both variants of AOC-E with a Snom D735 running firmware 10.1.127.10. It does not appear to properly support AOC-S however, so that could only be tested by inspecting SIP traces. ASTERISK-21502 #close Reported-by: Matt Jordan <mjordan@digium.com> Change-Id: Iebb7ad0d5f88526bc6629d3a1f9f11665434d333
2022-10-23 09:42:34 +00:00
&pjsip_media_type_multipart_mixed, 0) == 0) {
multipart_body = tdata->msg->body;
} else {
pjsip_sdp_info *tdata_sdp_info;
tdata_sdp_info = pjsip_tdata_get_sdp_info(tdata);
if (tdata_sdp_info->sdp) {
pj_status_t rc;
rc = pjsip_create_multipart_sdp_body(tdata->pool, tdata_sdp_info->sdp,
&multipart_body);
if (rc != PJ_SUCCESS) {
ast_log(LOG_ERROR, "Unable to create sdp multipart body\n");
return;
}
} else {
multipart_body = pjsip_multipart_create(tdata->pool,
&pjsip_media_type_multipart_mixed, NULL);
}
}
part = pjsip_multipart_create_part(tdata->pool);
pj_strdup2(tdata->pool, &body_text, datastore->data);
pj_cstr(&type, "application");
pj_cstr(&subtype, "vnd.etsi.aoc+xml");
part->body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &body_text);
pjsip_multipart_add_part(tdata->pool, multipart_body, part);
tdata->msg->body = multipart_body;
}
static struct ast_sip_session_supplement aoc_bye_supplement = {
.method = "BYE",
.priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST,
.outgoing_request = aoc_bye_outgoing_request,
.outgoing_response = aoc_bye_outgoing_response,
};
static struct ast_sip_session_supplement aoc_invite_supplement = {
.method = "INVITE",
.priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST,
.incoming_request = aoc_incoming_invite_request,
.outgoing_request = aoc_outgoing_invite_request,
.outgoing_response = aoc_invite_outgoing_response,
};
static int load_module(void)
{
ast_sip_session_register_supplement(&aoc_bye_supplement);
ast_sip_session_register_supplement(&aoc_invite_supplement);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
ast_sip_session_unregister_supplement(&aoc_bye_supplement);
ast_sip_session_unregister_supplement(&aoc_invite_supplement);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP AOC Support",
.support_level = AST_MODULE_SUPPORT_EXTENDED,
.load = load_module,
.unload = unload_module,
.load_pri = AST_MODPRI_CHANNEL_DEPEND,
.requires = "res_pjsip",
);