mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-11-03 20:38:59 +00:00 
			
		
		
		
	When adding AOC to an outgoing response the code assumed that a body would exist for comparing the Content-Type. This isn't always true. The code now checks to make sure the response has a body before checking the Content-Type. ASTERISK-21502 Change-Id: Iaead371434fc3bc693dad487228106a7d7a5ac76
		
			
				
	
	
		
			699 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			699 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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,
 | 
						|
			&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",
 | 
						|
);
 |