diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index b6a9cd0e6f..0a93cbe9e2 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -964,6 +964,11 @@ ; by the channel driver from the dialplan before they're forwarded ; the remote endpoint. ; +; send_aoc = + ; This options turns on and off support for sending AOC to endpoints. + ; AOC updates can be sent using the AOCMessage AMI action or come + ; from PRI channels. + ; (default: no) ;==========================AUTH SECTION OPTIONS========================= diff --git a/contrib/ast-db-manage/config/versions/5a2247c957d2_add_aoc_option.py b/contrib/ast-db-manage/config/versions/5a2247c957d2_add_aoc_option.py new file mode 100644 index 0000000000..a603e7fb54 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/5a2247c957d2_add_aoc_option.py @@ -0,0 +1,38 @@ +"""add aoc option + +Revision ID: 5a2247c957d2 +Revises: ccf795ee535f +Create Date: 2022-10-30 12:47:56.173511 + +""" + +# revision identifiers, used by Alembic. +revision = '5a2247c957d2' +down_revision = 'ccf795ee535f' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +AST_BOOL_NAME = 'ast_bool_values' +# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write +# those aliases. +AST_BOOL_VALUES = [ '0', '1', + 'off', 'on', + 'false', 'true', + 'no', 'yes' ] + +def upgrade(): + ############################# Enums ############################## + + # ast_bool_values has already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False) + + op.add_column('ps_endpoints', sa.Column('send_aoc', ast_bool_values)) + +def downgrade(): + if op.get_context().bind.dialect.name == 'mssql': + op.drop_constraint('ck_ps_endpoints_send_aoc_ast_bool_values', 'ps_endpoints') + op.drop_column('ps_endpoints', 'send_aoc') + pass diff --git a/doc/CHANGES-staging/res_pjsip_aoc.txt b/doc/CHANGES-staging/res_pjsip_aoc.txt new file mode 100644 index 0000000000..496bd0b385 --- /dev/null +++ b/doc/CHANGES-staging/res_pjsip_aoc.txt @@ -0,0 +1,4 @@ +Subject: res_pjsip_aoc + +Added res_pjsip_aoc which gives chan_pjsip the ability to send Advice-of-Charge messages. +A new endpoint option, send_aoc, controls this. diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index f31cd10529..35c7339bcb 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -1067,6 +1067,8 @@ struct ast_sip_endpoint { AST_STRING_FIELD_EXTENDED(geoloc_outgoing_call_profile); /*! 100rel mode to use with this endpoint */ enum ast_sip_100rel_mode rel100; + /*! Send Advice-of-Charge messages */ + unsigned int send_aoc; }; /*! URI parameter for symmetric transport */ diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml index ba8ba91bcd..99ca64571e 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -1506,6 +1506,9 @@ + + Send Advice-of-Charge messages + Authentication type diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index e63018e888..bb015d0a45 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -2263,6 +2263,7 @@ int ast_res_pjsip_initialize_configuration(void) ast_sorcery_object_field_register(sip_sorcery, "endpoint", "geoloc_outgoing_call_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, geoloc_outgoing_call_profile)); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_mechanisms", "", security_mechanism_handler, NULL, NULL, 0, 0); ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_negotiation", "no", security_negotiation_handler, NULL, NULL, 0, 0); + ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_aoc", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_aoc)); if (ast_sip_initialize_sorcery_transport()) { ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); diff --git a/res/res_pjsip_aoc.c b/res/res_pjsip_aoc.c new file mode 100644 index 0000000000..dba4e269a8 --- /dev/null +++ b/res/res_pjsip_aoc.c @@ -0,0 +1,698 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2022, Michael Kuron + * + * Michael Kuron + * + * 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 + pjproject + res_pjsip + extended + ***/ + +#include "asterisk.h" + +#include +#include + +#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 (pjsip_media_type_cmp(&tdata->msg->body->content_type, + &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", +); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index 67dcdf50d1..652b735408 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -2568,13 +2568,20 @@ int ast_sip_session_regenerate_answer(struct ast_sip_session *session, void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata) { - handle_outgoing_response(session, tdata); + pjsip_dialog *dlg = pjsip_tdata_get_dlg(tdata); + RAII_VAR(struct ast_sip_session *, dlg_session, dlg ? ast_sip_dialog_get_session(dlg) : NULL, ao2_cleanup); + if (!dlg_session) { + /* If the dialog has a session, handle_outgoing_response will be called + from session_on_tx_response. If it does not, call it from here. */ + handle_outgoing_response(session, tdata); + } pjsip_inv_send_msg(session->inv_session, tdata); return; } static pj_bool_t session_on_rx_request(pjsip_rx_data *rdata); static pj_bool_t session_on_rx_response(pjsip_rx_data *rdata); +static pj_status_t session_on_tx_response(pjsip_tx_data *tdata); static void session_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e); static pjsip_module session_module = { @@ -2583,6 +2590,7 @@ static pjsip_module session_module = { .on_rx_request = session_on_rx_request, .on_rx_response = session_on_rx_response, .on_tsx_state = session_on_tsx_state, + .on_tx_response = session_on_tx_response, }; /*! \brief Determine whether the SDP provided requires deferral of negotiating or not @@ -4266,6 +4274,18 @@ static pj_bool_t session_on_rx_request(pjsip_rx_data *rdata) handled == PJ_TRUE ? "yes" : "no"); } + +static pj_bool_t session_on_tx_response(pjsip_tx_data *tdata) +{ + pjsip_dialog *dlg = pjsip_tdata_get_dlg(tdata); + RAII_VAR(struct ast_sip_session *, session, dlg ? ast_sip_dialog_get_session(dlg) : NULL, ao2_cleanup); + if (session) { + handle_outgoing_response(session, tdata); + } + + return PJ_SUCCESS; +} + static void resend_reinvite(pj_timer_heap_t *timer, pj_timer_entry *entry) { struct ast_sip_session *session = entry->user_data;