[Asterisk-code-review] res_pjsip_aoc: New module for sending advice-of-charge with chan_pjsip (asterisk[master])

Michael Kuron asteriskteam at digium.com
Tue Oct 25 14:06:33 CDT 2022


Michael Kuron has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/19460 )


Change subject: res_pjsip_aoc: New module for sending advice-of-charge with chan_pjsip
......................................................................

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 callback hook for AOC indications to chan_pjsip. It
also adds a new res_pjsip_aoc module that inserts AOC information into
outgoing messages or sends SIP INFO messages as follows:

* 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)
* AOC-D in SIP INFO
* 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 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 at digium.com>

Change-Id: Iebb7ad0d5f88526bc6629d3a1f9f11665434d333
---
M channels/chan_pjsip.c
M include/asterisk/res_pjsip.h
M res/res_pjsip.c
A res/res_pjsip_aoc.c
4 files changed, 694 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/60/19460/1

diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 3e8abd2..3c5ea91 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -1831,6 +1831,9 @@
 		break;
 	case AST_CONTROL_STREAM_TOPOLOGY_SOURCE_CHANGED:
 		break;
+	case AST_CONTROL_AOC:
+		res = ast_sip_indicate_aoc(ast, data, datalen);
+		break;
 	case -1:
 		res = -1;
 		break;
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 743b19f..42c7100 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -1247,6 +1247,25 @@
 };
 
 /*!
+ * \brief an interchangeable way of sending out advice-of-charge indications
+ *
+ * An AOC callback takes AOC indications and sends them out immediately in a new SIP request
+ * or stores them for later use in other SIP requests/responses.
+ */
+struct ast_sip_aoc_callback {
+	/*!
+	 * \brief Indicate that an AOC message has been posted for a channel
+	 *
+	 * \param ast An Asterisk channel
+	 * \param data Pointer to AOC message, to be decoded with ast_aoc_decode
+	 * \param datalen Length of the AOC message
+	 * \retval 0 Successfully processed AOC message
+	 * \retval -1 Failed to process AOC message
+	 */
+	int (*indicate)(struct ast_channel *ast, const void *data, size_t datalen);
+};
+
+/*!
  * \brief An entity responsible for identifying the source of a SIP message
  */
 struct ast_sip_endpoint_identifier {
@@ -1458,6 +1477,30 @@
 pjsip_endpoint *ast_sip_get_pjsip_endpoint(void);
 
 /*!
+ * \brief Register an AOC callback element.
+ * \since 21
+ *
+ * \param element What we are registering.
+ */
+int ast_sip_register_aoc_callback(struct ast_sip_aoc_callback *element);
+
+/*!
+ * \brief Unregister an AOC callback element.
+ * \since 21
+ *
+ * \param element What we are unregistering.
+ */
+void ast_sip_unregister_aoc_callback(struct ast_sip_aoc_callback *element);
+
+/*!
+ * \brief Use a registered AOC callback to indicate that an AOC message has been received for a channel
+ * \since 21
+ *
+ * \param element What we are registering.
+ */
+int ast_sip_indicate_aoc(struct ast_channel *ast, const void *data, size_t datalen);
+
+/*!
  * \brief Get a pointer to the SIP sorcery structure.
  *
  * \retval NULL sorcery has not been initialized
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 0ad5ec2..98295d2 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -516,6 +516,40 @@
 	return ast_pjsip_endpoint;
 }
 
+static struct ast_sip_aoc_callback *registered_aoc_callback;
+
+int ast_sip_register_aoc_callback(struct ast_sip_aoc_callback *element)
+{
+	if (registered_aoc_callback) {
+		ast_log(LOG_WARNING, "AOC callback %p is already registered. Cannot register a new one\n", registered_aoc_callback);
+		return -1;
+	}
+	registered_aoc_callback = element;
+	ast_debug(1, "Registered SIP AOC callback %p\n", element);
+
+	return 0;
+}
+
+void ast_sip_unregister_aoc_callback(struct ast_sip_aoc_callback *element)
+{
+	if (registered_aoc_callback != element) {
+		ast_log(LOG_WARNING, "Trying to unregister AOC callback %p but AOC callback %p registered\n",
+				element, registered_aoc_callback);
+		return;
+	}
+	registered_aoc_callback = NULL;
+	ast_debug(1, "Unregistered SIP AOC callback %p\n", element);
+}
+
+int ast_sip_indicate_aoc(struct ast_channel *ast, const void *data, size_t datalen)
+{
+	if (!registered_aoc_callback) {
+		ast_log(LOG_WARNING, "No AOC callback registered. Ignoring AOC indicationl\n");
+		return -1;
+	}
+	return registered_aoc_callback->indicate(ast, data, datalen);
+}
+
 int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint *endpoint,
 	pjsip_rx_data *rdata)
 {
diff --git a/res/res_pjsip_aoc.c b/res/res_pjsip_aoc.c
new file mode 100644
index 0000000..341e0ab
--- /dev/null
+++ b/res/res_pjsip_aoc.c
@@ -0,0 +1,574 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Michael Kuron
+ *
+ * Michael Kuron <m.kuron at 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>core</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_ALLOC_T(pool, pj_xml_node);
+
+	pj_list_init(&node->attr_head);
+	pj_list_init(&node->node_head);
+
+	pj_strdup2(pool, &node->name, name);
+
+	node->content.ptr = NULL;
+	node->content.slen = 0;
+
+	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 int aoc_indicate(struct ast_channel *ast, const void *data, size_t datalen)
+{
+	struct ast_aoc_decoded *decoded;
+	pj_pool_t *pool;
+
+	decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, ast);
+	if (!decoded) {
+		ast_log(LOG_ERROR, "Error decoding indicated AOC data\n");
+		return -1;
+	}
+
+	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "AOC",
+			1024, 1024);
+
+	if (ast_aoc_get_msg_type(decoded) == AST_AOC_D ||
+			ast_aoc_get_msg_type(decoded) == AST_AOC_E) {
+		struct ast_sip_channel_pvt *channel;
+		struct ast_sip_session *session;
+		pj_xml_node *aoc;
+		pj_xml_node *aoc_type;
+		pj_xml_node *charging_info;
+		pj_xml_node *charges;
+		pj_xml_node *charge;
+		char *xml;
+		size_t size;
+
+		channel = ast_channel_tech_pvt(ast);
+		session = channel->session;
+
+		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(decoded) == AST_AOC_D ? "aoc-d" : "aoc-e");
+		charging_info = aoc_xml_create_node(pool, aoc_type, "charging-info");
+		aoc_xml_set_node_content(pool, charging_info,
+				ast_aoc_get_msg_type(decoded) == AST_AOC_D ? "subtotal" : "total");
+		charges = aoc_xml_create_node(pool, aoc_type, "recorded-charges");
+
+		if (ast_aoc_get_charge_type(decoded) == AST_AOC_CHARGE_FREE) {
+			charge = aoc_xml_create_node(pool, charges, "free-charge");
+		} else if (ast_aoc_get_charge_type(decoded) == AST_AOC_CHARGE_CURRENCY ||
+				ast_aoc_get_charge_type(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(decoded) == AST_AOC_CHARGE_CURRENCY) {
+			const char *currency;
+			pj_xml_node *amount;
+			char *amount_str;
+
+			currency = ast_aoc_get_currency_name(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(decoded),
+					ast_aoc_get_currency_multiplier(decoded));
+			aoc_xml_set_node_content(pool, amount, amount_str);
+		} else if (ast_aoc_get_charge_type(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(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);
+			}
+		}
+
+		const size_t xml_max_size = 512;
+		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_WARNING, "aoc+xml body text too large\n");
+			return -1;
+		}
+		xml[size] = 0;
+
+		if (ast_aoc_get_msg_type(decoded) == AST_AOC_D) {
+			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", session->inv_session->dlg,
+					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(session, tdata);
+		} else if (ast_aoc_get_msg_type(decoded) == AST_AOC_E) {
+			pbx_builtin_setvar_helper(ast, "AOCE", xml);
+		}
+	} else if (ast_aoc_get_msg_type(decoded) == AST_AOC_S) {
+		struct ast_sip_channel_pvt *channel;
+		struct ast_sip_session *session;
+		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;
+
+		channel = ast_channel_tech_pvt(ast);
+		session = channel->session;
+
+		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(decoded); idx++) {
+			pj_xml_node *charged_item;
+			pj_xml_node *charge;
+
+			if (!(entry = ast_aoc_s_get_rate_info(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 = (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 = (AST_AOC_RATE_TYPE_DURATION ? entry->rate.duration.amount :
+						entry->rate.flat.amount);
+				multiplier_val = (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");
+			}
+		}
+
+		const size_t xml_max_size = 1024;
+		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_WARNING, "aoc+xml body text too large\n");
+			return -1;
+		}
+		xml[size] = 0;
+
+		if (ast_channel_state(ast) == AST_STATE_UP) {
+			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", session->inv_session->dlg,
+					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(session, tdata);
+		} else {
+			pbx_builtin_setvar_helper(ast, "AOCS", xml);
+		}
+	}
+
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+	ast_aoc_destroy_decoded(decoded);
+	return 0;
+}
+
+static struct ast_sip_aoc_callback aoc_callback = {
+	.indicate = aoc_indicate,
+};
+
+static void aoc_bye_outgoing_response(struct ast_sip_session *session,
+		struct pjsip_tx_data *tdata)
+{
+	const char *xml;
+
+	xml = pbx_builtin_getvar_helper(session->channel, "AOCE");
+	if (!xml) {
+		return;
+	}
+
+	struct ast_sip_body body = {
+		.type = "application",
+		.subtype = "vnd.etsi.aoc+xml",
+		.body_text = xml
+	};
+
+	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)
+{
+	const char *xml;
+
+	xml = pbx_builtin_getvar_helper(session->channel, "AOCE");
+	if (!xml) {
+		return;
+	}
+
+	struct ast_sip_body body = {
+		.type = "application",
+		.subtype = "vnd.etsi.aoc+xml",
+		.body_text = xml
+	};
+
+	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)
+{
+	const char *xml;
+	pjsip_sdp_info *tdata_sdp_info;
+	pjsip_msg_body *multipart_body;
+	pjsip_multipart_part *part;
+	pj_str_t body_text;
+	pj_str_t type;
+	pj_str_t subtype;
+
+	if(tdata->msg->line.status.code != 180 && tdata->msg->line.status.code != 183 &&
+			tdata->msg->line.status.code != 200) {
+		return;
+	}
+
+	xml = pbx_builtin_getvar_helper(session->channel, "AOCS");
+	if (!xml) {
+		return;
+	}
+
+	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, xml);
+	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 pj_status_t aoc_outgoing_response(pjsip_tx_data *tdata)
+{
+	pjsip_cseq_hdr *cseq;
+
+	cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, tdata->msg->hdr.next);
+	if (cseq->method.id == PJSIP_BYE_METHOD) {
+		struct ast_channel_iterator *chan_iter;
+		struct ast_channel *chan;
+		pjsip_cid_hdr *cid;
+
+		cid = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CALL_ID, tdata->msg->hdr.next);
+
+		chan_iter = ast_channel_iterator_by_name_new("PJSIP", 5);
+		for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) {
+			const char *pvtid;
+
+			ast_channel_lock(chan);
+			pvtid = ast_channel_tech(chan)->get_pvt_uniqueid(chan);
+			if (pj_strcmp2(&cid->id, pvtid) == 0) {
+				struct ast_sip_channel_pvt *channel;
+
+				channel = ast_channel_tech_pvt(chan);
+				aoc_bye_outgoing_response(channel->session, tdata);
+				ast_channel_unlock(chan);
+				ast_channel_unref(chan);
+				break;
+			}
+			ast_channel_unlock(chan);
+		}
+		ast_channel_iterator_destroy(chan_iter);
+	}
+	return PJ_SUCCESS;
+}
+
+static struct ast_sip_session_supplement aoc_bye_supplement = {
+	.method = "BYE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST,
+	.outgoing_request = aoc_bye_outgoing_request,
+};
+
+static struct ast_sip_session_supplement aoc_invite_supplement = {
+	.method = "INVITE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST,
+	.outgoing_response = aoc_invite_outgoing_response,
+};
+
+static pjsip_module aoc_module = {
+	.name = { "AOC", 3 },
+	.id = -1,
+	.priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 2,
+	.on_tx_response = aoc_outgoing_response,
+};
+
+static int load_module(void)
+{
+	if (ast_sip_register_aoc_callback(&aoc_callback)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	if (ast_sip_register_service(&aoc_module)) {
+		ast_sip_unregister_aoc_callback(&aoc_callback);
+		ast_log(LOG_ERROR, "Could not register AOC module\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	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_unregister_aoc_callback(&aoc_callback);
+	ast_sip_unregister_service(&aoc_module);
+	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_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+	.requires = "res_pjsip",
+);

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/19460
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Iebb7ad0d5f88526bc6629d3a1f9f11665434d333
Gerrit-Change-Number: 19460
Gerrit-PatchSet: 1
Gerrit-Owner: Michael Kuron <m.kuron at gmx.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20221025/dc5046c0/attachment-0001.html>


More information about the asterisk-code-review mailing list