summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorRichard Mudgett <rmudgett@digium.com>2010-06-02 18:10:15 +0000
committerRichard Mudgett <rmudgett@digium.com>2010-06-02 18:10:15 +0000
commitafd4454c4446ee1889616d09541171fc6355bbf7 (patch)
tree985bf496a795d25e80f0f4948c0f68cbadb0f7ac /main
parent266db9fa8c43f9e3c8c55e2f5bd44e432a7b91ee (diff)
Generic Advice of Charge.
Asterisk Generic AOC Representation - Generic AOC encode/decode routines. (Generic AOC must be encoded to be passed on the wire in the AST_CONTROL_AOC frame) - AST_CONTROL_AOC frame type to represent generic encoded AOC data - Manager events for AOC-S, AOC-D, and AOC-E messages Asterisk App Support - app_dial AOC-S pass-through support on call setup - app_queue AOC-S pass-through support on call setup AOC Unit Tests - AOC Unit Tests for encode/decode routines - AOC Unit Test for manager event representation. SIP AOC Support - Pass-through of generic AOC-D and AOC-E messages to snom phones via the snom AOC specification. - Creation of chan_sip page3 flags for the addition of the new 'snom_aoc_enabled' sip.conf option. IAX AOC Support - Natively supports AOC pass-through through the use of the new AST_CONTROL_AOC frame type DAHDI AOC Support - ETSI PRI full AOC Pass-through support - 'aoc_enable' chan_dahdi.conf option for independently enabling pass-through of AOC-S, AOC-D, AOC-E. - 'aoce_delayhangup' option for retrieving AOC-E on disconnect. - DAHDI A() dial string option for requesting AOC services. example usage: ;requests AOC-S, AOC-D, and AOC-E on call setup exten=>1111,1,Dial(DAHDI/g1/1112/A(s,d,e)) Review: https://reviewboard.asterisk.org/r/552/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@267096 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main')
-rw-r--r--main/aoc.c1607
-rw-r--r--main/asterisk.c3
-rw-r--r--main/channel.c5
-rw-r--r--main/features.c1
-rw-r--r--main/manager.c338
5 files changed, 1954 insertions, 0 deletions
diff --git a/main/aoc.c b/main/aoc.c
new file mode 100644
index 000000000..c7cc8cf2f
--- /dev/null
+++ b/main/aoc.c
@@ -0,0 +1,1607 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * 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.
+ */
+
+/*!
+ * \file
+ * \brief generic AOC payload generation encoding and decoding
+ *
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#include "asterisk.h"
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include "asterisk/aoc.h"
+#include "asterisk/utils.h"
+#include "asterisk/strings.h"
+#include "asterisk/_private.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+
+/* Encoded Payload Flags */
+#define AST_AOC_ENCODED_TYPE_REQUEST (0 << 0)
+#define AST_AOC_ENCODED_TYPE_D (1 << 0)
+#define AST_AOC_ENCODED_TYPE_E (2 << 0)
+#define AST_AOC_ENCODED_TYPE_S (3 << 0)
+
+#define AST_AOC_ENCODED_REQUEST_S (1 << 2)
+#define AST_AOC_ENCODED_REQUEST_D (1 << 3)
+#define AST_AOC_ENCODED_REQUEST_E (1 << 4)
+
+#define AST_AOC_ENCODED_CHARGE_NA (0 << 5)
+#define AST_AOC_ENCODED_CHARGE_FREE (1 << 5)
+#define AST_AOC_ENCODED_CHARGE_CURRENCY (2 << 5)
+#define AST_AOC_ENCODED_CHARGE_UNIT (3 << 5)
+
+#define AST_AOC_ENCODED_CHARGE_SUBTOTAL (1 << 7)
+#define AST_AOC_ENCODED_CHARGE_TOTAL (0 << 7)
+
+#define AST_AOC_ENCODE_VERSION 1
+
+
+static char aoc_debug_enabled = 0;
+static void aoc_display_decoded_debug(const struct ast_aoc_decoded *decoded, int decoding, struct ast_channel *chan);
+static int aoc_s_add_entry(struct ast_aoc_decoded *decoded, struct ast_aoc_s_entry *entry);
+
+/* AOC Payload Header. Holds all the encoded AOC data to pass on the wire */
+struct ast_aoc_encoded {
+ uint8_t version;
+ uint8_t flags;
+ uint16_t datalen;
+ unsigned char data[0];
+};
+
+/* Decoded AOC data */
+struct ast_aoc_decoded {
+ enum ast_aoc_type msg_type;
+ enum ast_aoc_charge_type charge_type;
+ enum ast_aoc_request request_flag;
+ enum ast_aoc_total_type total_type;
+
+ /* currency information */
+ enum ast_aoc_currency_multiplier multiplier;
+ unsigned int currency_amount;
+ char currency_name[AOC_CURRENCY_NAME_SIZE];
+
+ /* unit information */
+ int unit_count;
+ struct ast_aoc_unit_entry unit_list[32];
+
+ /* Billing Id */
+ enum ast_aoc_billing_id billing_id;
+
+ /* Charging Association information */
+ struct ast_aoc_charging_association charging_association;
+
+ /* AOC-S charge information */
+ int aoc_s_count;
+ struct ast_aoc_s_entry aoc_s_entries[10];
+
+ /* Is this an AOC Termination Request */
+ char termination_request;
+};
+
+/*! \brief AOC Payload Information Elements */
+enum AOC_IE {
+ AOC_IE_CURRENCY = 1,
+ AOC_IE_UNIT = 2,
+ AOC_IE_BILLING = 3,
+ AOC_IE_CHARGING_ASSOCIATION = 4,
+ AOC_IE_RATE = 5,
+ AOC_IE_TERMINATION_REQUEST = 6,
+};
+
+/*! \brief AOC IE payload header */
+struct aoc_pl_ie_hdr {
+ uint8_t ie_id;
+ uint8_t datalen;
+ char data[0];
+} __attribute__((packed));
+
+struct aoc_ie_currency {
+ uint32_t amount;
+ uint8_t multiplier;
+ char name[AOC_CURRENCY_NAME_SIZE];
+} __attribute__((packed));
+
+struct aoc_ie_unit {
+ uint32_t amount;
+ uint8_t valid_type;
+ uint8_t valid_amount;
+ uint8_t type;
+} __attribute__((packed));
+
+struct aoc_ie_billing {
+ uint8_t id;
+} __attribute__((packed));
+
+struct aoc_ie_charging_association {
+ struct ast_aoc_charging_association ca;
+} __attribute__((packed));
+
+struct aoc_ie_charging_rate {
+ struct ast_aoc_s_entry entry;
+} __attribute__((packed));
+
+struct ast_aoc_decoded *ast_aoc_create(const enum ast_aoc_type msg_type,
+ const enum ast_aoc_charge_type charge_type,
+ const enum ast_aoc_request requests)
+{
+ struct ast_aoc_decoded *decoded = NULL;
+
+ /* verify input */
+ if (((unsigned int) charge_type > AST_AOC_CHARGE_UNIT) ||
+ ((unsigned int) msg_type > AST_AOC_E) ||
+ ((msg_type == AST_AOC_REQUEST) && !requests)) {
+
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_decoded object, invalid input\n");
+ return NULL;
+ }
+
+ if (!(decoded = ast_calloc(1, sizeof(struct ast_aoc_decoded)))) {
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_decoded object \n");
+ return NULL;
+ }
+
+ decoded->msg_type = msg_type;
+
+ if (msg_type == AST_AOC_REQUEST) {
+ decoded->request_flag = requests;
+ } else if ((msg_type == AST_AOC_D) || (msg_type == AST_AOC_E)) {
+ decoded->charge_type = charge_type;
+ }
+
+ return decoded;
+}
+
+void *ast_aoc_destroy_decoded(struct ast_aoc_decoded *decoded)
+{
+ ast_free(decoded);
+ return NULL;
+}
+
+void *ast_aoc_destroy_encoded(struct ast_aoc_encoded *encoded)
+{
+ ast_free(encoded);
+ return NULL;
+}
+
+static void aoc_parse_ie_charging_rate(struct ast_aoc_decoded *decoded, const struct aoc_ie_charging_rate *ie)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = ntohs(ie->entry.charged_item);
+ entry.rate_type = ntohs(ie->entry.rate_type);
+
+ switch (entry.rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ entry.rate.duration.multiplier = ntohs(ie->entry.rate.duration.multiplier);
+ entry.rate.duration.amount = ntohl(ie->entry.rate.duration.amount);
+ entry.rate.duration.time = ntohl(ie->entry.rate.duration.time);
+ entry.rate.duration.time_scale = ntohs(ie->entry.rate.duration.time_scale);
+ entry.rate.duration.granularity_time = ntohl(ie->entry.rate.duration.granularity_time);
+ entry.rate.duration.granularity_time_scale = ntohs(ie->entry.rate.duration.granularity_time_scale);
+ entry.rate.duration.charging_type = ie->entry.rate.duration.charging_type; /* only one byte */
+
+ if (!ast_strlen_zero(ie->entry.rate.duration.currency_name)) {
+ ast_copy_string(entry.rate.duration.currency_name,
+ ie->entry.rate.duration.currency_name,
+ sizeof(entry.rate.duration.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ entry.rate.flat.multiplier = ntohs(ie->entry.rate.flat.multiplier);
+ entry.rate.flat.amount = ntohl(ie->entry.rate.flat.amount);
+ if (!ast_strlen_zero(ie->entry.rate.flat.currency_name)) {
+ ast_copy_string(entry.rate.flat.currency_name,
+ ie->entry.rate.flat.currency_name,
+ sizeof(entry.rate.flat.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ entry.rate.volume.multiplier = ntohs(ie->entry.rate.volume.multiplier);
+ entry.rate.volume.amount = ntohl(ie->entry.rate.volume.amount);
+ entry.rate.volume.volume_unit = ntohs(ie->entry.rate.volume.volume_unit);
+ if (!ast_strlen_zero(ie->entry.rate.volume.currency_name)) {
+ ast_copy_string(entry.rate.volume.currency_name,
+ ie->entry.rate.volume.currency_name,
+ sizeof(entry.rate.volume.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ entry.rate.special_code = ntohs(ie->entry.rate.special_code);
+ break;
+ }
+
+ aoc_s_add_entry(decoded, &entry);
+}
+
+static int aoc_parse_ie(struct ast_aoc_decoded *decoded, unsigned char *data, unsigned int datalen)
+{
+ enum AOC_IE ie_id;
+ unsigned int len;
+
+ while (datalen >= 2) {
+ ie_id = data[0];
+ len = data[1];
+ if (len > datalen -2) {
+ ast_log(LOG_ERROR, "AOC information element length exceeds the total message size\n");
+ return -1;
+ }
+
+ switch(ie_id) {
+ case AOC_IE_CURRENCY:
+ if (len == sizeof(struct aoc_ie_currency)) {
+ struct aoc_ie_currency ie;
+ memcpy(&ie, data + 2, len);
+ decoded->currency_amount = ntohl(ie.amount);
+ decoded->multiplier = ie.multiplier; /* only one byte */
+ memcpy(decoded->currency_name, ie.name, sizeof(decoded->currency_name));
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid currency ie\n");
+ }
+ break;
+ case AOC_IE_UNIT:
+ if (len == sizeof(struct aoc_ie_unit)) {
+ struct aoc_ie_unit ie;
+ memcpy(&ie, data + 2, len);
+ ast_aoc_add_unit_entry(decoded, ie.valid_amount, ntohl(ie.amount), ie.valid_type, ie.type);
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid unit ie\n");
+ }
+ break;
+ case AOC_IE_BILLING:
+ if (len == sizeof(struct aoc_ie_billing)) {
+ struct aoc_ie_billing ie;
+ memcpy(&ie, data + 2, len);
+ decoded->billing_id = ie.id; /* only one byte */
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid billing ie\n");
+ }
+ break;
+ case AOC_IE_CHARGING_ASSOCIATION:
+ if (len == sizeof(struct aoc_ie_charging_association)) {
+ memcpy(&decoded->charging_association, data + 2, sizeof(decoded->charging_association));
+ /* everything in the charging_association struct is a single byte except for the id */
+ if (decoded->charging_association.charging_type == AST_AOC_CHARGING_ASSOCIATION_ID) {
+ decoded->charging_association.charge.id = ntohl(decoded->charging_association.charge.id);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid charging association ie\n");
+ }
+ break;
+ case AOC_IE_RATE:
+ if (len == sizeof(struct aoc_ie_charging_rate)) {
+ struct aoc_ie_charging_rate ie;
+ memcpy(&ie, data + 2, len);
+ aoc_parse_ie_charging_rate(decoded, &ie);
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid charging rate ie\n");
+ }
+ break;
+ case AOC_IE_TERMINATION_REQUEST:
+ if (len == 0) {
+ decoded->termination_request = 1;
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid termination request ie\n");
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Unknown AOC Information Element, ignoring.\n");
+ }
+
+ datalen -= (len + 2);
+ data += (len + 2);
+ }
+ return 0;
+}
+
+struct ast_aoc_decoded *ast_aoc_decode(struct ast_aoc_encoded *encoded, size_t size, struct ast_channel *chan)
+{
+ struct ast_aoc_decoded *decoded;
+
+ /* verify our encoded payload is actually large enough to hold all the ies */
+ if ((size - (sizeof(struct ast_aoc_encoded)) != ntohs(encoded->datalen))) {
+ ast_log(LOG_WARNING, "Corrupted aoc encoded object, can not decode\n");
+ return NULL;
+ }
+
+ if (!(decoded = ast_calloc(1, sizeof(struct ast_aoc_decoded)))) {
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_decoded object \n");
+ return NULL;
+ }
+
+ /* decode flags */
+
+ if ((encoded->flags & AST_AOC_ENCODED_TYPE_S) == AST_AOC_ENCODED_TYPE_S) {
+ decoded->msg_type = AST_AOC_S;
+ } else if (encoded->flags & AST_AOC_ENCODED_TYPE_E) {
+ decoded->msg_type = AST_AOC_E;
+ } else if (encoded->flags & AST_AOC_ENCODED_TYPE_D) {
+ decoded->msg_type = AST_AOC_D;
+ } else {
+ decoded->msg_type = AST_AOC_REQUEST;
+ }
+
+ if (decoded->msg_type == AST_AOC_REQUEST) {
+ if (encoded->flags & AST_AOC_ENCODED_REQUEST_S) {
+ decoded->request_flag |= AST_AOC_REQUEST_S;
+ }
+ if (encoded->flags & AST_AOC_ENCODED_REQUEST_D) {
+ decoded->request_flag |= AST_AOC_REQUEST_D;
+ }
+ if (encoded->flags & AST_AOC_ENCODED_REQUEST_E) {
+ decoded->request_flag |= AST_AOC_REQUEST_E;
+ }
+ } else if ((decoded->msg_type == AST_AOC_D) || (decoded->msg_type == AST_AOC_E)) {
+ if ((encoded->flags & AST_AOC_ENCODED_CHARGE_UNIT) == AST_AOC_ENCODED_CHARGE_UNIT) {
+ decoded->charge_type = AST_AOC_CHARGE_UNIT;
+ } else if ((encoded->flags & AST_AOC_ENCODED_CHARGE_CURRENCY) == AST_AOC_ENCODED_CHARGE_CURRENCY) {
+ decoded->charge_type = AST_AOC_CHARGE_CURRENCY;
+ } else if ((encoded->flags & AST_AOC_ENCODED_CHARGE_FREE) == AST_AOC_ENCODED_CHARGE_FREE) {
+ decoded->charge_type = AST_AOC_CHARGE_FREE;
+ } else {
+ decoded->charge_type = AST_AOC_CHARGE_NA;
+ }
+
+ if (encoded->flags & AST_AOC_ENCODED_CHARGE_SUBTOTAL) {
+ decoded->total_type = AST_AOC_SUBTOTAL;
+ }
+ }
+
+ /* decode information elements */
+ aoc_parse_ie(decoded, encoded->data, ntohs(encoded->datalen));
+
+ if (aoc_debug_enabled) {
+ aoc_display_decoded_debug(decoded, 1, chan);
+ }
+
+ return decoded;
+}
+
+struct aoc_ie_data {
+ unsigned char buf[1024];
+ int pos;
+};
+
+/*!
+ * \internal
+ * \brief append an AOC information element
+ * \note data is expected to already be in network byte order at this point
+ */
+static int aoc_append_ie(struct aoc_ie_data *ied, unsigned short ie_id, const void *data, unsigned short datalen)
+{
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ ast_log(LOG_WARNING, "Failure to append AOC information element, out of space \n");
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie_id;
+ ied->buf[ied->pos++] = datalen;
+ if (datalen) {
+ memcpy(ied->buf + ied->pos, data, datalen);
+ ied->pos += datalen;
+ }
+ return 0;
+}
+
+static void aoc_create_ie_data_charging_rate(const struct ast_aoc_s_entry *entry, struct aoc_ie_charging_rate *ie)
+{
+ ie->entry.charged_item = htons(entry->charged_item);
+ ie->entry.rate_type = htons(entry->rate_type);
+
+ switch (entry->rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ ie->entry.rate.duration.multiplier = htons(entry->rate.duration.multiplier);
+ ie->entry.rate.duration.amount = htonl(entry->rate.duration.amount);
+ ie->entry.rate.duration.time = htonl(entry->rate.duration.time);
+ ie->entry.rate.duration.time_scale = htons(entry->rate.duration.time_scale);
+ ie->entry.rate.duration.granularity_time = htonl(entry->rate.duration.granularity_time);
+ ie->entry.rate.duration.granularity_time_scale = htons(entry->rate.duration.granularity_time_scale);
+ ie->entry.rate.duration.charging_type = entry->rate.duration.charging_type; /* only one byte */
+
+ if (!ast_strlen_zero(entry->rate.duration.currency_name)) {
+ ast_copy_string(ie->entry.rate.duration.currency_name,
+ entry->rate.duration.currency_name,
+ sizeof(ie->entry.rate.duration.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ ie->entry.rate.flat.multiplier = htons(entry->rate.flat.multiplier);
+ ie->entry.rate.flat.amount = htonl(entry->rate.flat.amount);
+ if (!ast_strlen_zero(entry->rate.flat.currency_name)) {
+ ast_copy_string(ie->entry.rate.flat.currency_name,
+ entry->rate.flat.currency_name,
+ sizeof(ie->entry.rate.flat.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ ie->entry.rate.volume.multiplier = htons(entry->rate.volume.multiplier);
+ ie->entry.rate.volume.amount = htonl(entry->rate.volume.amount);
+ ie->entry.rate.volume.volume_unit = htons(entry->rate.volume.volume_unit);
+ if (!ast_strlen_zero(entry->rate.volume.currency_name)) {
+ ast_copy_string(ie->entry.rate.volume.currency_name,
+ entry->rate.volume.currency_name,
+ sizeof(ie->entry.rate.volume.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ ie->entry.rate.special_code = htons(entry->rate.special_code);
+ break;
+ }
+
+}
+static void aoc_create_ie_data(struct ast_aoc_decoded *decoded, struct aoc_ie_data *ied)
+{
+ ied->pos = 0;
+
+ if (decoded->currency_amount) {
+ struct aoc_ie_currency ie = {
+ .amount = htonl(decoded->currency_amount),
+ .multiplier = decoded->multiplier, /* only one byte */
+ .name = { 0, },
+ };
+
+ if (!ast_strlen_zero(decoded->currency_name)) {
+ ast_copy_string(ie.name, decoded->currency_name, sizeof(ie.name));
+ }
+
+ aoc_append_ie(ied, AOC_IE_CURRENCY, (const void *) &ie, sizeof(ie));
+ }
+
+ if (decoded->unit_count) {
+ struct aoc_ie_unit ie = { 0 };
+ int i;
+
+ for (i = 0; i < decoded->unit_count; i++) {
+ ie.valid_amount = decoded->unit_list[i].valid_amount; /* only one byte */
+ ie.amount = htonl(decoded->unit_list[i].amount);
+ ie.valid_type = decoded->unit_list[i].valid_type; /* only one byte */
+ ie.type = decoded->unit_list[i].type; /* only one byte */
+ aoc_append_ie(ied, AOC_IE_UNIT, (const void *) &ie, sizeof(ie));
+ }
+ }
+
+ if (decoded->billing_id) {
+ struct aoc_ie_billing ie;
+ ie.id = decoded->billing_id; /* only one byte */
+ aoc_append_ie(ied, AOC_IE_BILLING, (const void *) &ie, sizeof(ie));
+ }
+
+ if (decoded->charging_association.charging_type != AST_AOC_CHARGING_ASSOCIATION_NA) {
+ struct aoc_ie_charging_association ie;
+ memset(&ie, 0, sizeof(ie));
+ ie.ca.charging_type = decoded->charging_association.charging_type; /* only one byte */
+ if (decoded->charging_association.charging_type == AST_AOC_CHARGING_ASSOCIATION_NUMBER) {
+ ie.ca.charge.number.plan = decoded->charging_association.charge.number.plan; /* only one byte */
+ ast_copy_string(ie.ca.charge.number.number,
+ decoded->charging_association.charge.number.number,
+ sizeof(ie.ca.charge.number.number));
+ } else if (decoded->charging_association.charging_type == AST_AOC_CHARGING_ASSOCIATION_ID) {
+ ie.ca.charge.id = htonl(decoded->charging_association.charge.id);
+ }
+ aoc_append_ie(ied, AOC_IE_CHARGING_ASSOCIATION, (const void *) &ie, sizeof(ie));
+ }
+
+ if (decoded->aoc_s_count) {
+ struct aoc_ie_charging_rate ie;
+ int i;
+ for (i = 0; i < decoded->aoc_s_count; i++) {
+ memset(&ie, 0, sizeof(ie));
+ aoc_create_ie_data_charging_rate(&decoded->aoc_s_entries[i], &ie);
+ aoc_append_ie(ied, AOC_IE_RATE, (const void *) &ie, sizeof(ie));
+ }
+ }
+
+ if (decoded->termination_request) {
+ aoc_append_ie(ied, AOC_IE_TERMINATION_REQUEST, NULL, 0);
+ }
+}
+
+struct ast_aoc_encoded *ast_aoc_encode(struct ast_aoc_decoded *decoded, size_t *out_size, struct ast_channel *chan)
+{
+ struct aoc_ie_data ied;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t size = 0;
+
+ if (!decoded || !out_size) {
+ return NULL;
+ }
+
+ *out_size = 0;
+
+ /* create information element buffer before allocating the payload,
+ * by doing this the exact size of the payload + the id data can be
+ * allocated all at once. */
+ aoc_create_ie_data(decoded, &ied);
+
+ size = sizeof(struct ast_aoc_encoded) + ied.pos;
+
+ if (!(encoded = ast_calloc(1, size))) {
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_encoded object during decode routine. \n");
+ return NULL;
+ }
+
+ /* -- Set ie data buffer */
+ if (ied.pos) {
+ /* this is safe because encoded was allocated to fit this perfectly */
+ memcpy(encoded->data, ied.buf, ied.pos);
+ encoded->datalen = htons(ied.pos);
+ }
+
+ /* --- Set Flags --- */
+ switch (decoded->msg_type) {
+ case AST_AOC_S:
+ encoded->flags = AST_AOC_ENCODED_TYPE_S;
+ break;
+ case AST_AOC_D:
+ encoded->flags = AST_AOC_ENCODED_TYPE_D;
+ break;
+ case AST_AOC_E:
+ encoded->flags = AST_AOC_ENCODED_TYPE_E;
+ break;
+ case AST_AOC_REQUEST:
+ encoded->flags = AST_AOC_ENCODED_TYPE_REQUEST;
+ default:
+ break;
+ }
+
+ /* if it is type request, set the types requested, else set charge type */
+ if (decoded->msg_type == AST_AOC_REQUEST) {
+ if (decoded->request_flag & AST_AOC_REQUEST_S) {
+ encoded->flags |= AST_AOC_ENCODED_REQUEST_S;
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_D) {
+ encoded->flags |= AST_AOC_ENCODED_REQUEST_D;
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_E) {
+ encoded->flags |= AST_AOC_ENCODED_REQUEST_E;
+ }
+ } else if ((decoded->msg_type == AST_AOC_D) || (decoded->msg_type == AST_AOC_E)) {
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_UNIT:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_UNIT;
+ break;
+ case AST_AOC_CHARGE_CURRENCY:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_CURRENCY;
+ break;
+ case AST_AOC_CHARGE_FREE:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_FREE;
+ case AST_AOC_CHARGE_NA:
+ default:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_NA;
+ break;
+ }
+
+ if (decoded->total_type == AST_AOC_SUBTOTAL) {
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_SUBTOTAL;
+ }
+ }
+
+ /* --- Set Version Number --- */
+ encoded->version = AST_AOC_ENCODE_VERSION;
+
+ /* set the output size */
+ *out_size = size;
+
+ if (aoc_debug_enabled) {
+ aoc_display_decoded_debug(decoded, 0, chan);
+ }
+
+ return encoded;
+}
+
+static int aoc_s_add_entry(struct ast_aoc_decoded *decoded, struct ast_aoc_s_entry *entry)
+{
+ if (decoded->aoc_s_count >= ARRAY_LEN(decoded->aoc_s_entries)) {
+ return -1;
+ }
+
+ decoded->aoc_s_entries[decoded->aoc_s_count] = *entry;
+ decoded->aoc_s_count++;
+
+ return 0;
+}
+
+
+unsigned int ast_aoc_s_get_count(struct ast_aoc_decoded *decoded)
+{
+ return decoded->aoc_s_count;
+}
+
+const struct ast_aoc_s_entry *ast_aoc_s_get_rate_info(struct ast_aoc_decoded *decoded, unsigned int entry_number)
+{
+ if (entry_number >= decoded->aoc_s_count) {
+ return NULL;
+ }
+
+ return (const struct ast_aoc_s_entry *) &decoded->aoc_s_entries[entry_number];
+}
+
+int ast_aoc_s_add_rate_duration(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ unsigned int amount,
+ enum ast_aoc_currency_multiplier multiplier,
+ const char *currency_name,
+ unsigned long time,
+ enum ast_aoc_time_scale time_scale,
+ unsigned long granularity_time,
+ enum ast_aoc_time_scale granularity_time_scale,
+ int step_function)
+{
+
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_DURATION;
+ entry.rate.duration.amount = amount;
+ entry.rate.duration.multiplier = multiplier;
+ entry.rate.duration.time = time;
+ entry.rate.duration.time_scale = time_scale;
+ entry.rate.duration.granularity_time = granularity_time;
+ entry.rate.duration.granularity_time_scale = granularity_time_scale;
+ entry.rate.duration.charging_type = step_function ? 1 : 0;
+
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(entry.rate.duration.currency_name, currency_name, sizeof(entry.rate.duration.currency_name));
+ }
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_flat(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ unsigned int amount,
+ enum ast_aoc_currency_multiplier multiplier,
+ const char *currency_name)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_FLAT;
+ entry.rate.flat.amount = amount;
+ entry.rate.flat.multiplier = multiplier;
+
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(entry.rate.flat.currency_name, currency_name, sizeof(entry.rate.flat.currency_name));
+ }
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+
+int ast_aoc_s_add_rate_volume(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ enum ast_aoc_volume_unit volume_unit,
+ unsigned int amount,
+ enum ast_aoc_currency_multiplier multiplier,
+ const char *currency_name)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_VOLUME;
+ entry.rate.volume.multiplier = multiplier;
+ entry.rate.volume.amount = amount;
+ entry.rate.volume.volume_unit = volume_unit;
+
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(entry.rate.volume.currency_name, currency_name, sizeof(entry.rate.volume.currency_name));
+ }
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_special_charge_code(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ unsigned int code)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_SPECIAL_CODE;
+ entry.rate.special_code = code;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_free(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ int from_beginning)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = from_beginning ? AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING : AST_AOC_RATE_TYPE_FREE;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_na(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_NA;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_special_arrangement(struct ast_aoc_decoded *decoded,
+ unsigned int code)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT;
+ entry.rate_type = AST_AOC_RATE_TYPE_SPECIAL_CODE;
+ entry.rate.special_code = code;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+enum ast_aoc_type ast_aoc_get_msg_type(struct ast_aoc_decoded *decoded)
+{
+ return decoded->msg_type;
+}
+
+enum ast_aoc_charge_type ast_aoc_get_charge_type(struct ast_aoc_decoded *decoded)
+{
+ return decoded->charge_type;
+}
+
+enum ast_aoc_request ast_aoc_get_request(struct ast_aoc_decoded *decoded)
+{
+ return decoded->request_flag;
+}
+
+int ast_aoc_set_total_type(struct ast_aoc_decoded *decoded,
+ const enum ast_aoc_total_type type)
+{
+ decoded->total_type = type;
+ return 0;
+}
+
+enum ast_aoc_total_type ast_aoc_get_total_type(struct ast_aoc_decoded *decoded)
+{
+ return decoded->total_type;
+}
+
+int ast_aoc_set_currency_info(struct ast_aoc_decoded *decoded,
+ const unsigned int amount,
+ const enum ast_aoc_currency_multiplier multiplier,
+ const char *name)
+{
+
+ if (!ast_strlen_zero(name)) {
+ ast_copy_string(decoded->currency_name, name, sizeof(decoded->currency_name));
+ }
+
+ decoded->currency_amount = amount;
+
+ if (multiplier && (multiplier < AST_AOC_MULT_NUM_ENTRIES)) {
+ decoded->multiplier = multiplier;
+ } else {
+ decoded->multiplier = AST_AOC_MULT_ONE;
+ }
+
+ return 0;
+}
+
+unsigned int ast_aoc_get_currency_amount(struct ast_aoc_decoded *decoded)
+{
+ return decoded->currency_amount;
+}
+
+enum ast_aoc_currency_multiplier ast_aoc_get_currency_multiplier(struct ast_aoc_decoded *decoded)
+{
+ return decoded->multiplier;
+}
+
+const char *ast_aoc_get_currency_multiplier_decimal(struct ast_aoc_decoded *decoded)
+{
+ switch (decoded->multiplier) {
+ case AST_AOC_MULT_ONETHOUSANDTH:
+ return "0.001";
+ case AST_AOC_MULT_ONEHUNDREDTH:
+ return "0.01";
+ case AST_AOC_MULT_ONETENTH:
+ return "0.1";
+ case AST_AOC_MULT_ONE:
+ return "1.0";
+ case AST_AOC_MULT_TEN:
+ return "10.0";
+ case AST_AOC_MULT_HUNDRED:
+ return "100.0";
+ case AST_AOC_MULT_THOUSAND:
+ return "1000.0";
+ default:
+ return "1.0";
+ }
+}
+
+const char *ast_aoc_get_currency_name(struct ast_aoc_decoded *decoded)
+{
+ return decoded->currency_name;
+}
+
+int ast_aoc_add_unit_entry(struct ast_aoc_decoded *decoded,
+ const unsigned int amount_is_present,
+ const unsigned int amount,
+ const unsigned int type_is_present,
+ const unsigned int type)
+{
+ if ((decoded->msg_type == AST_AOC_REQUEST) ||
+ (decoded->unit_count >= ARRAY_LEN(decoded->unit_list))) {
+ return -1;
+ }
+
+ if (!amount_is_present && !type_is_present) {
+ return -1;
+ }
+
+ decoded->unit_list[decoded->unit_count].valid_amount = amount_is_present;
+ if (amount_is_present) {
+ decoded->unit_list[decoded->unit_count].amount = amount;
+ } else {
+ decoded->unit_list[decoded->unit_count].amount = 0;
+ }
+
+ decoded->unit_list[decoded->unit_count].valid_type = type_is_present;
+ if (type_is_present) {
+ decoded->unit_list[decoded->unit_count].type = type;
+ } else {
+ decoded->unit_list[decoded->unit_count].type = 0;
+ }
+ decoded->unit_count++;
+
+ return 0;
+}
+
+const struct ast_aoc_unit_entry *ast_aoc_get_unit_info(struct ast_aoc_decoded *decoded, unsigned int entry_number)
+{
+ if (entry_number >= decoded->unit_count) {
+ return NULL;
+ }
+
+ return (const struct ast_aoc_unit_entry *) &decoded->unit_list[entry_number];
+}
+
+unsigned int ast_aoc_get_unit_count(struct ast_aoc_decoded *decoded)
+{
+ return decoded->unit_count;
+}
+
+int ast_aoc_set_billing_id(struct ast_aoc_decoded *decoded, const enum ast_aoc_billing_id id)
+{
+ if ((id >= AST_AOC_BILLING_NUM_ENTRIES) || (id < AST_AOC_BILLING_NA)) {
+ return -1;
+ }
+
+ decoded->billing_id = id;
+
+ return 0;
+}
+
+enum ast_aoc_billing_id ast_aoc_get_billing_id(struct ast_aoc_decoded *decoded)
+{
+ return decoded->billing_id;
+}
+
+int ast_aoc_set_association_id(struct ast_aoc_decoded *decoded, const int id)
+{
+ if (decoded->msg_type != AST_AOC_E) {
+ return -1;
+ }
+ memset(&decoded->charging_association, 0, sizeof(decoded->charging_association));
+ decoded->charging_association.charging_type = AST_AOC_CHARGING_ASSOCIATION_ID;
+ decoded->charging_association.charge.id = id;
+ return 0;
+}
+
+const struct ast_aoc_charging_association *ast_aoc_get_association_info(struct ast_aoc_decoded *decoded)
+{
+ return &decoded->charging_association;
+}
+
+int ast_aoc_set_association_number(struct ast_aoc_decoded *decoded, const char *num, uint8_t plan)
+{
+ if ((decoded->msg_type != AST_AOC_E) || ast_strlen_zero(num)) {
+ return -1;
+ }
+ memset(&decoded->charging_association, 0, sizeof(decoded->charging_association));
+ decoded->charging_association.charging_type = AST_AOC_CHARGING_ASSOCIATION_NUMBER;
+ decoded->charging_association.charge.number.plan = plan;
+ ast_copy_string(decoded->charging_association.charge.number.number, num, sizeof(decoded->charging_association.charge.number.number));
+
+ return 0;
+}
+
+int ast_aoc_set_termination_request(struct ast_aoc_decoded *decoded)
+{
+ if (decoded->msg_type != AST_AOC_REQUEST) {
+ return -1;
+ }
+ decoded->termination_request = 1;
+
+ return 0;
+}
+
+int ast_aoc_get_termination_request(struct ast_aoc_decoded *decoded)
+{
+ return decoded->termination_request;
+}
+
+/*!
+ * \internal
+ * \brief Convert AST_AOC_VOLUME_UNIT to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_volume_unit_str(enum ast_aoc_volume_unit value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_VOLUME_UNIT_OCTET:
+ str = "Octet";
+ break;
+ case AST_AOC_VOLUME_UNIT_SEGMENT:
+ str = "Segment";
+ break;
+ case AST_AOC_VOLUME_UNIT_MESSAGE:
+ str = "Message";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert ast_aoc_charged_item to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_charged_item_str(enum ast_aoc_s_charged_item value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_CHARGED_ITEM_NA:
+ str = "NotAvailable";
+ break;
+ case AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT:
+ str = "SpecialArrangement";
+ break;
+ case AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION:
+ str = "BasicCommunication";
+ break;
+ case AST_AOC_CHARGED_ITEM_CALL_ATTEMPT:
+ str = "CallAttempt";
+ break;
+ case AST_AOC_CHARGED_ITEM_CALL_SETUP:
+ str = "CallSetup";
+ break;
+ case AST_AOC_CHARGED_ITEM_USER_USER_INFO:
+ str = "UserUserInfo";
+ break;
+ case AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE:
+ str = "SupplementaryService";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert ast_aoc_total_type to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_type_of_totaling_str(enum ast_aoc_total_type value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_SUBTOTAL:
+ str = "SubTotal";
+ break;
+ case AST_AOC_TOTAL:
+ str = "Total";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert ast_aoc_rate_type to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_rate_type_str(enum ast_aoc_s_rate_type value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_RATE_TYPE_NA:
+ str = "NotAvailable";
+ break;
+ case AST_AOC_RATE_TYPE_FREE:
+ str = "Free";
+ break;
+ case AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING:
+ str = "FreeFromBeginning";
+ break;
+ case AST_AOC_RATE_TYPE_DURATION:
+ str = "Duration";
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ str = "Flat";
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ str = "Volume";
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ str = "SpecialCode";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert AST_AOC_TIME_SCALE to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_scale_str(enum ast_aoc_time_scale value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_TIME_SCALE_HUNDREDTH_SECOND:
+ str = "OneHundredthSecond";
+ break;
+ case AST_AOC_TIME_SCALE_TENTH_SECOND:
+ str = "OneTenthSecond";
+ break;
+ case AST_AOC_TIME_SCALE_SECOND:
+ str = "Second";
+ break;
+ case AST_AOC_TIME_SCALE_TEN_SECOND:
+ str = "TenSeconds";
+ break;
+ case AST_AOC_TIME_SCALE_MINUTE:
+ str = "Minute";
+ break;
+ case AST_AOC_TIME_SCALE_HOUR:
+ str = "Hour";
+ break;
+ case AST_AOC_TIME_SCALE_DAY:
+ str = "Day";
+ break;
+ }
+ return str;
+}
+
+static const char *aoc_charge_type_str(enum ast_aoc_charge_type value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_CHARGE_NA:
+ str = "NotAvailable";
+ break;
+ case AST_AOC_CHARGE_FREE:
+ str = "Free";
+ break;
+ case AST_AOC_CHARGE_CURRENCY:
+ str = "Currency";
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ str = "Units";
+ break;
+ }
+
+ return str;
+}
+
+static const char *aoc_multiplier_str(enum ast_aoc_currency_multiplier mult)
+{
+ switch (mult) {
+ case AST_AOC_MULT_ONETHOUSANDTH:
+ return "1/1000";
+ case AST_AOC_MULT_ONEHUNDREDTH:
+ return "1/100";
+ case AST_AOC_MULT_ONETENTH:
+ return "1/10";
+ case AST_AOC_MULT_ONE:
+ return "1";
+ case AST_AOC_MULT_TEN:
+ return "10";
+ case AST_AOC_MULT_HUNDRED:
+ return "100";
+ case AST_AOC_MULT_THOUSAND:
+ return "1000";
+ case AST_AOC_MULT_NUM_ENTRIES:
+ break;
+ }
+ return "1";
+}
+
+static const char *aoc_billingid_str(enum ast_aoc_billing_id billing_id)
+{
+ switch (billing_id) {
+ case AST_AOC_BILLING_NORMAL:
+ return "Normal";
+ case AST_AOC_BILLING_REVERSE_CHARGE:
+ return "Reverse";
+ case AST_AOC_BILLING_CREDIT_CARD:
+ return "CreditCard";
+ case AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL:
+ return "CallForwardingUnconditional";
+ case AST_AOC_BILLING_CALL_FWD_BUSY:
+ return "CallForwardingBusy";
+ case AST_AOC_BILLING_CALL_FWD_NO_REPLY:
+ return "CallForwardingNoReply";
+ case AST_AOC_BILLING_CALL_DEFLECTION:
+ return "CallDeflection";
+ case AST_AOC_BILLING_CALL_TRANSFER:
+ return "CallTransfer";
+ case AST_AOC_BILLING_NA:
+ return "NotAvailable";
+ case AST_AOC_BILLING_NUM_ENTRIES:
+ break;
+ }
+ return "NotAvailable";
+}
+
+int ast_aoc_test_encode_decode_match(struct ast_aoc_decoded *decoded)
+{
+ struct ast_aoc_decoded *new_decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t size;
+ int res = 0;
+
+ if (!(encoded = ast_aoc_encode(decoded, &size, NULL))) {
+ return -1;
+ }
+
+ if (!(new_decoded = ast_aoc_decode(encoded, size, NULL))) {
+ ast_free(encoded);
+ return -1;
+ }
+
+ if (memcmp(new_decoded, decoded, sizeof(struct ast_aoc_decoded))) {
+ res = -1;
+ }
+
+ ast_aoc_destroy_decoded(new_decoded);
+ ast_aoc_destroy_encoded(encoded);
+ return res;
+}
+
+static char *aoc_cli_debug_enable(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "aoc set debug";
+ e->usage =
+ "Usage: 'aoc set debug on' to enable aoc debug, 'aoc set debug off' to disable debug.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ case CLI_HANDLER:
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ } else if(ast_true(a->argv[3])) {
+ ast_cli(a->fd, "aoc debug enabled\n");
+ aoc_debug_enabled = 1;
+ } else if (ast_false(a->argv[3])) {
+ ast_cli(a->fd, "aoc debug disabled\n");
+ aoc_debug_enabled = 0;
+ } else {
+ return CLI_SHOWUSAGE;
+ }
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \internal
+ * \brief Append the time structure to the event message string.
+ * \since 1.8
+ *
+ * \param msg Event message string being built.
+ * \param prefix Prefix to add to the amount lines.
+ * \param name Name of the time structure to convert.
+ * \param time Data to convert.
+ * \param scale Data to convert.
+ *
+ * \return Nothing
+ */
+static void aoc_time_str(struct ast_str **msg, const char *prefix, const char *name, unsigned long time, enum ast_aoc_time_scale scale)
+{
+ ast_str_append(msg, 0, "%s/%s/Length: %lu\r\n", prefix, name, time);
+ ast_str_append(msg, 0, "%s/%s/Scale: %s\r\n", prefix, name,
+ aoc_scale_str(scale));
+}
+
+/*!
+ * \internal
+ * \brief Append the amount structure to the event message string.
+ * \since 1.8
+ *
+ * \param msg Event message string being built.
+ * \param prefix Prefix to add to the amount lines.
+ * \param amount Data to convert.
+ * \param multipler to convert
+ *
+ * \return Nothing
+ */
+static void aoc_amount_str(struct ast_str **msg, const char *prefix, unsigned int amount, enum ast_aoc_currency_multiplier mult)
+{
+ static const char name[] = "Amount";
+
+ ast_str_append(msg, 0, "%s/%s/Cost: %u\r\n", prefix, name, amount);
+ ast_str_append(msg, 0, "%s/%s/Multiplier: %s\r\n", prefix, name,
+ aoc_multiplier_str(mult));
+}
+
+static void aoc_request_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg)
+{
+ if (chan) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", chan->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", chan->uniqueid);
+ }
+
+ if (decoded->request_flag) {
+ ast_str_append(msg, 0, "AOCRequest:");
+ if (decoded->request_flag & AST_AOC_REQUEST_S) {
+ ast_str_append(msg, 0, "S");
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_D) {
+ ast_str_append(msg, 0, "D");
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_E) {
+ ast_str_append(msg, 0, "E");
+ }
+ ast_str_append(msg, 0, "\r\n");
+
+ } else {
+ ast_str_append(msg, 0, "AOCRequest: NONE\r\n");
+ }
+}
+
+static void aoc_s_event(const struct ast_aoc_decoded *decoded, struct ast_channel *owner, struct ast_str **msg)
+{
+ const char *rate_str;
+ char prefix[32];
+ int idx;
+
+ if (owner) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", owner->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", owner->uniqueid);
+ }
+
+ ast_str_append(msg, 0, "NumberRates: %d\r\n", decoded->aoc_s_count);
+ for (idx = 0; idx < decoded->aoc_s_count; ++idx) {
+ snprintf(prefix, sizeof(prefix), "Rate(%d)", idx);
+
+ ast_str_append(msg, 0, "%s/Chargeable: %s\r\n", prefix,
+ aoc_charged_item_str(decoded->aoc_s_entries[idx].charged_item));
+ if (decoded->aoc_s_entries[idx].charged_item == AST_AOC_CHARGED_ITEM_NA) {
+ continue;
+ }
+ rate_str = aoc_rate_type_str(decoded->aoc_s_entries[idx].rate_type);
+ ast_str_append(msg, 0, "%s/Type: %s\r\n", prefix, rate_str);
+ switch (decoded->aoc_s_entries[idx].rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ strcat(prefix, "/");
+ strcat(prefix, rate_str);
+ ast_str_append(msg, 0, "%s/Currency: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.duration.currency_name);
+ aoc_amount_str(msg, prefix,
+ decoded->aoc_s_entries[idx].rate.duration.amount,
+ decoded->aoc_s_entries[idx].rate.duration.multiplier);
+ ast_str_append(msg, 0, "%s/ChargingType: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.duration.charging_type ?
+ "StepFunction" : "ContinuousCharging");
+ aoc_time_str(msg, prefix, "Time",
+ decoded->aoc_s_entries[idx].rate.duration.time,
+ decoded->aoc_s_entries[idx].rate.duration.time_scale);
+ if (decoded->aoc_s_entries[idx].rate.duration.granularity_time) {
+ aoc_time_str(msg, prefix, "Granularity",
+ decoded->aoc_s_entries[idx].rate.duration.granularity_time,
+ decoded->aoc_s_entries[idx].rate.duration.granularity_time_scale);
+ }
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ strcat(prefix, "/");
+ strcat(prefix, rate_str);
+ ast_str_append(msg, 0, "%s/Currency: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.flat.currency_name);
+ aoc_amount_str(msg, prefix,
+ decoded->aoc_s_entries[idx].rate.flat.amount,
+ decoded->aoc_s_entries[idx].rate.flat.multiplier);
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ strcat(prefix, "/");
+ strcat(prefix, rate_str);
+ ast_str_append(msg, 0, "%s/Currency: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.volume.currency_name);
+ aoc_amount_str(msg, prefix,
+ decoded->aoc_s_entries[idx].rate.volume.amount,
+ decoded->aoc_s_entries[idx].rate.volume.multiplier);
+ ast_str_append(msg, 0, "%s/Unit: %s\r\n", prefix,
+ aoc_volume_unit_str(decoded->aoc_s_entries[idx].rate.volume.volume_unit));
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ ast_str_append(msg, 0, "%s/%s: %d\r\n", prefix, rate_str,
+ decoded->aoc_s_entries[idx].rate.special_code);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void aoc_d_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg)
+{
+ const char *charge_str;
+ int idx;
+ char prefix[32];
+
+ if (chan) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", chan->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", chan->uniqueid);
+ }
+
+ charge_str = aoc_charge_type_str(decoded->charge_type);
+ ast_str_append(msg, 0, "Type: %s\r\n", charge_str);
+
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "BillingID: %s\r\n",
+ aoc_billingid_str(decoded->billing_id));
+ ast_str_append(msg, 0, "TypeOfCharging: %s\r\n",
+ aoc_type_of_totaling_str(decoded->total_type));
+ break;
+ default:
+ break;
+ }
+
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ ast_str_append(msg, 0, "%s: %s\r\n", charge_str,
+ decoded->currency_name);
+ aoc_amount_str(msg, charge_str,
+ decoded->currency_amount,
+ decoded->multiplier);
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "%s/NumberItems: %d\r\n", charge_str,
+ decoded->unit_count);
+ for (idx = 0; idx < decoded->unit_count; ++idx) {
+ snprintf(prefix, sizeof(prefix), "%s/Item(%d)", charge_str, idx);
+ if (decoded->unit_list[idx].valid_amount) {
+ ast_str_append(msg, 0, "%s/NumberOf: %u\r\n", prefix,
+ decoded->unit_list[idx].amount);
+ }
+ if (decoded->unit_list[idx].valid_type) {
+ ast_str_append(msg, 0, "%s/TypeOf: %d\r\n", prefix,
+ decoded->unit_list[idx].type);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void aoc_e_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg)
+{
+ const char *charge_str;
+ int idx;
+ char prefix[32];
+
+ if (chan) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", chan->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", chan->uniqueid);
+ }
+
+ charge_str = "ChargingAssociation";
+
+ switch (decoded->charging_association.charging_type) {
+ case AST_AOC_CHARGING_ASSOCIATION_NUMBER:
+ snprintf(prefix, sizeof(prefix), "%s/Number", charge_str);
+ ast_str_append(msg, 0, "%s: %s\r\n", prefix,
+ decoded->charging_association.charge.number.number);
+ ast_str_append(msg, 0, "%s/Plan: %d\r\n", prefix,
+ decoded->charging_association.charge.number.plan);
+ break;
+ case AST_AOC_CHARGING_ASSOCIATION_ID:
+ ast_str_append(msg, 0, "%s/ID: %d\r\n", charge_str, decoded->charging_association.charge.id);
+ break;
+ case AST_AOC_CHARGING_ASSOCIATION_NA:
+ default:
+ break;
+ }
+
+ charge_str = aoc_charge_type_str(decoded->charge_type);
+ ast_str_append(msg, 0, "Type: %s\r\n", charge_str);
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "BillingID: %s\r\n",
+ aoc_billingid_str(decoded->billing_id));
+ break;
+ default:
+ break;
+ }
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ ast_str_append(msg, 0, "%s: %s\r\n", charge_str,
+ decoded->currency_name);
+ aoc_amount_str(msg, charge_str,
+ decoded->currency_amount,
+ decoded->multiplier);
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "%s/NumberItems: %d\r\n", charge_str,
+ decoded->unit_count);
+ for (idx = 0; idx < decoded->unit_count; ++idx) {
+ snprintf(prefix, sizeof(prefix), "%s/Item(%d)", charge_str, idx);
+ if (decoded->unit_list[idx].valid_amount) {
+ ast_str_append(msg, 0, "%s/NumberOf: %u\r\n", prefix,
+ decoded->unit_list[idx].amount);
+ }
+ if (decoded->unit_list[idx].valid_type) {
+ ast_str_append(msg, 0, "%s/TypeOf: %d\r\n", prefix,
+ decoded->unit_list[idx].type);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+int ast_aoc_manager_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan)
+{
+ struct ast_str *msg;
+
+ if (!decoded || !(msg = ast_str_create(1024))) {
+ return -1;
+ }
+
+ switch (decoded->msg_type) {
+ case AST_AOC_S:
+ if (chan) {
+ aoc_s_event(decoded, chan, &msg);
+ ast_manager_event(chan, EVENT_FLAG_AOC, "AOC-S", "%s", ast_str_buffer(msg));
+ }
+ break;
+ case AST_AOC_D:
+ if (chan) {
+ aoc_d_event(decoded, chan, &msg);
+ ast_manager_event(chan, EVENT_FLAG_AOC, "AOC-D", "%s", ast_str_buffer(msg));
+ }
+ break;
+ case AST_AOC_E:
+ {
+ struct ast_channel *chans[1];
+ aoc_e_event(decoded, chan, &msg);
+ chans[0] = chan;
+ ast_manager_event_multichan(EVENT_FLAG_AOC, "AOC-E", chan ? 1 : 0, chans, "%s", ast_str_buffer(msg));
+ }
+ break;
+ default:
+ /* events for AST_AOC_REQUEST are not generated here */
+ break;
+ }
+
+ ast_free(msg);
+ return 0;
+}
+
+int ast_aoc_decoded2str(const struct ast_aoc_decoded *decoded, struct ast_str **msg)
+{
+ if (!decoded || !msg) {
+ return -1;
+ }
+
+ switch (decoded->msg_type) {
+ case AST_AOC_S:
+ ast_str_append(msg, 0, "AOC-S\r\n");
+ aoc_s_event(decoded, NULL, msg);
+ break;
+ case AST_AOC_D:
+ ast_str_append(msg, 0, "AOC-D\r\n");
+ aoc_d_event(decoded, NULL, msg);
+ break;
+ case AST_AOC_E:
+ ast_str_append(msg, 0, "AOC-E\r\n");
+ aoc_e_event(decoded, NULL, msg);
+ break;
+ case AST_AOC_REQUEST:
+ ast_str_append(msg, 0, "AOC-Request\r\n");
+ aoc_request_event(decoded, NULL, msg);
+ break;
+ }
+
+ return 0;
+}
+
+static void aoc_display_decoded_debug(const struct ast_aoc_decoded *decoded, int decoding, struct ast_channel *chan)
+{
+ struct ast_str *msg;
+
+ if (!decoded || !(msg = ast_str_create(1024))) {
+ return;
+ }
+
+ if (decoding) {
+ ast_str_append(&msg, 0, "---- DECODED AOC MSG ----\r\n");
+ } else {
+ ast_str_append(&msg, 0, "---- ENCODED AOC MSG ----\r\n");
+ }
+ if (chan) {
+ ast_str_append(&msg, 0, "CHANNEL: %s\r\n", chan->name);
+ }
+
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+ ast_free(msg);
+ return;
+ }
+
+ ast_verb(1, "%s\r\n", ast_str_buffer(msg));
+ ast_free(msg);
+}
+
+static struct ast_cli_entry aoc_cli[] = {
+ AST_CLI_DEFINE(aoc_cli_debug_enable, "enable cli debugging of AOC messages"),
+};
+
+int ast_aoc_cli_init(void)
+{
+ return ast_cli_register_multiple(aoc_cli, ARRAY_LEN(aoc_cli));
+}
diff --git a/main/asterisk.c b/main/asterisk.c
index 4b09d1e6a..1e13729c1 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -142,6 +142,7 @@ int daemon(int, int); /* defined in libresolv of all places */
#include "asterisk/poll-compat.h"
#include "asterisk/ccss.h"
#include "asterisk/test.h"
+#include "asterisk/aoc.h"
#include "../defaults.h"
@@ -3602,6 +3603,8 @@ int main(int argc, char *argv[])
}
#endif
+ ast_aoc_cli_init();
+
ast_makesocket();
sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP);
diff --git a/main/channel.c b/main/channel.c
index 999e39ba6..3057fae9d 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -3796,6 +3796,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con
case _XXX_AST_CONTROL_T38:
case AST_CONTROL_CC:
case AST_CONTROL_READ_ACTION:
+ case AST_CONTROL_AOC:
break;
case AST_CONTROL_CONGESTION:
@@ -3941,6 +3942,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
case AST_CONTROL_REDIRECTING:
case AST_CONTROL_CC:
case AST_CONTROL_READ_ACTION:
+ case AST_CONTROL_AOC:
/* Nothing left to do for these. */
res = 0;
break;
@@ -6003,6 +6005,9 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct
int bridge_exit = 0;
switch (f->subclass.integer) {
+ case AST_CONTROL_AOC:
+ ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
+ break;
case AST_CONTROL_REDIRECTING:
if (ast_channel_redirecting_macro(who, other, f, other == c0, 1)) {
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
diff --git a/main/features.c b/main/features.c
index 72a626e45..6c85241db 100644
--- a/main/features.c
+++ b/main/features.c
@@ -3208,6 +3208,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
}
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
break;
+ case AST_CONTROL_AOC:
case AST_CONTROL_HOLD:
case AST_CONTROL_UNHOLD:
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
diff --git a/main/manager.c b/main/manager.c
index 43be08e60..b0b2d3771 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -74,6 +74,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/features.h"
#include "asterisk/security_events.h"
+#include "asterisk/aoc.h"
/*** DOCUMENTATION
<manager name="Ping" language="en_US">
@@ -694,6 +695,112 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
For success returns, the module revision number is included.</para>
</description>
</manager>
+ <manager name="AOCMessage" language="en_US">
+ <synopsis>
+ Generate an Advice of Charge message on a channel.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Channel" required="true">
+ <para>Channel name to generate the AOC message on.</para>
+ </parameter>
+ <parameter name="ChannelPrefix">
+ <para>Partial channel prefix. By using this option one can match the beginning part
+ of a channel name without having to put the entire name in. For example
+ if a channel name is SIP/snom-00000001 and this value is set to SIP/snom, then
+ that channel matches and the message will be sent. Note however that only
+ the first matched channel has the message sent on it. </para>
+ </parameter>
+ <parameter name="MsgType" required="true">
+ <para>Defines what type of AOC message to create, AOC-D or AOC-E</para>
+ <enumlist>
+ <enum name="D" />
+ <enum name="E" />
+ </enumlist>
+ </parameter>
+ <parameter name="ChargeType" required="true">
+ <para>Defines what kind of charge this message represents.</para>
+ <enumlist>
+ <enum name="NA" />
+ <enum name="FREE" />
+ <enum name="Currency" />
+ <enum name="Unit" />
+ </enumlist>
+ </parameter>
+ <parameter name="UnitAmount(0)">
+ <para>This represents the amount of units charged. The ETSI AOC standard specifies that
+ this value along with the optional UnitType value are entries in a list. To accommodate this
+ these values take an index value starting at 0 which can be used to generate this list of
+ unit entries. For Example, If two unit entires were required this could be achieved by setting the
+ paramter UnitAmount(0)=1234 and UnitAmount(1)=5678. Note that UnitAmount at index 0 is
+ required when ChargeType=Unit, all other entries in the list are optional.
+ </para>
+ </parameter>
+ <parameter name="UnitType(0)">
+ <para>Defines the type of unit. ETSI AOC standard specifies this as an integer
+ value between 1 and 16, but this value is left open to accept any positive
+ integer. Like the UnitAmount parameter, this value represents a list entry
+ and has an index parameter that starts at 0.
+ </para>
+ </parameter>
+ <parameter name="CurrencyName">
+ <para>Specifies the currency's name. Note that this value is truncated after 10 characters.</para>
+ </parameter>
+ <parameter name="CurrencyAmount">
+ <para>Specifies the charge unit amount as a positive integer. This value is required
+ when ChargeType==Currency.</para>
+ </parameter>
+ <parameter name="CurrencyMultiplier">
+ <para>Specifies the currency multiplier. This value is required when ChargeType==Currency.</para>
+ <enumlist>
+ <enum name="OneThousandth" />
+ <enum name="OneHundredth" />
+ <enum name="OneTenth" />
+ <enum name="One" />
+ <enum name="Ten" />
+ <enum name="Hundred" />
+ <enum name="Thousand" />
+ </enumlist>
+ </parameter>
+ <parameter name="TotalType" default="Total">
+ <para>Defines what kind of AOC-D total is represented.</para>
+ <enumlist>
+ <enum name="Total" />
+ <enum name="SubTotal" />
+ </enumlist>
+ </parameter>
+ <parameter name="AOCBillingId">
+ <para>Represents a billing ID associated with an AOC-D or AOC-E message. Note
+ that only the first 3 items of the enum are valid AOC-D billing IDs</para>
+ <enumlist>
+ <enum name="Normal" />
+ <enum name="ReverseCharge" />
+ <enum name="CreditCard" />
+ <enum name="CallFwdUnconditional" />
+ <enum name="CallFwdBusy" />
+ <enum name="CallFwdNoReply" />
+ <enum name="CallDeflection" />
+ <enum name="CallTransfer" />
+ </enumlist>
+ </parameter>
+ <parameter name="ChargingAssociationId">
+ <para>Charging association identifier. This is optional for AOC-E and can be
+ set to any value between -32768 and 32767</para>
+ </parameter>
+ <parameter name="ChargingAssociationNumber">
+ <para>Represents the charging association party number. This value is optional
+ for AOC-E.</para>
+ </parameter>
+ <parameter name="ChargingAssociationPlan">
+ <para>Integer representing the charging plan associated with the ChargingAssociationNumber.
+ The value is bits 7 through 1 of the Q.931 octet containing the type-of-number and
+ numbering-plan-identification fields.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Generates an AOC-D or AOC-E message on a channel.</para>
+ </description>
+ </manager>
***/
enum error_type {
@@ -3396,6 +3503,236 @@ static void *fast_originate(void *data)
return NULL;
}
+static int aocmessage_get_unit_entry(const struct message *m, struct ast_aoc_unit_entry *entry, unsigned int entry_num)
+{
+ const char *unitamount;
+ const char *unittype;
+ struct ast_str *str = ast_str_alloca(32);
+
+ memset(entry, 0, sizeof(*entry));
+
+ ast_str_set(&str, 0, "UnitAmount(%u)", entry_num);
+ unitamount = astman_get_header(m, ast_str_buffer(str));
+
+ ast_str_set(&str, 0, "UnitType(%u)", entry_num);
+ unittype = astman_get_header(m, ast_str_buffer(str));
+
+ if (!ast_strlen_zero(unitamount) && (sscanf(unitamount, "%30u", &entry->amount) == 1)) {
+ entry->valid_amount = 1;
+ }
+
+ if (!ast_strlen_zero(unittype) && sscanf(unittype, "%30u", &entry->type) == 1) {
+ entry->valid_type = 1;
+ }
+
+ return 0;
+}
+
+static int action_aocmessage(struct mansession *s, const struct message *m)
+{
+ const char *channel = astman_get_header(m, "Channel");
+ const char *pchannel = astman_get_header(m, "ChannelPrefix");
+ const char *msgtype = astman_get_header(m, "MsgType");
+ const char *chargetype = astman_get_header(m, "ChargeType");
+ const char *currencyname = astman_get_header(m, "CurrencyName");
+ const char *currencyamount = astman_get_header(m, "CurrencyAmount");
+ const char *mult = astman_get_header(m, "CurrencyMultiplier");
+ const char *totaltype = astman_get_header(m, "TotalType");
+ const char *aocbillingid = astman_get_header(m, "AOCBillingId");
+ const char *association_id= astman_get_header(m, "ChargingAssociationId");
+ const char *association_num = astman_get_header(m, "ChargingAssociationNumber");
+ const char *association_plan = astman_get_header(m, "ChargingAssociationPlan");
+
+ enum ast_aoc_type _msgtype;
+ enum ast_aoc_charge_type _chargetype;
+ enum ast_aoc_currency_multiplier _mult = AST_AOC_MULT_ONE;
+ enum ast_aoc_total_type _totaltype = AST_AOC_TOTAL;
+ enum ast_aoc_billing_id _billingid = AST_AOC_BILLING_NA;
+ unsigned int _currencyamount = 0;
+ int _association_id = 0;
+ unsigned int _association_plan = 0;
+ struct ast_channel *chan = NULL;
+
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t encoded_size = 0;
+
+ if (ast_strlen_zero(channel) && ast_strlen_zero(pchannel)) {
+ astman_send_error(s, m, "Channel and PartialChannel are not specified. Specify at least one of these.");
+ goto aocmessage_cleanup;
+ }
+
+ if (!(chan = ast_channel_get_by_name(channel)) && !ast_strlen_zero(pchannel)) {
+ chan = ast_channel_get_by_name_prefix(pchannel, strlen(pchannel));
+ }
+
+ if (!chan) {
+ astman_send_error(s, m, "No such channel");
+ goto aocmessage_cleanup;
+ }
+
+ if (ast_strlen_zero(msgtype) || (strcasecmp(msgtype, "d") && strcasecmp(msgtype, "e"))) {
+ astman_send_error(s, m, "Invalid MsgType");
+ goto aocmessage_cleanup;
+ }
+
+ if (ast_strlen_zero(chargetype)) {
+ astman_send_error(s, m, "ChargeType not specified");
+ goto aocmessage_cleanup;
+ }
+
+ _msgtype = strcasecmp(msgtype, "d") ? AST_AOC_E : AST_AOC_D;
+
+ if (!strcasecmp(chargetype, "NA")) {
+ _chargetype = AST_AOC_CHARGE_NA;
+ } else if (!strcasecmp(chargetype, "Free")) {
+ _chargetype = AST_AOC_CHARGE_FREE;
+ } else if (!strcasecmp(chargetype, "Currency")) {
+ _chargetype = AST_AOC_CHARGE_CURRENCY;
+ } else if (!strcasecmp(chargetype, "Unit")) {
+ _chargetype = AST_AOC_CHARGE_UNIT;
+ } else {
+ astman_send_error(s, m, "Invalid ChargeType");
+ goto aocmessage_cleanup;
+ }
+
+ if (_chargetype == AST_AOC_CHARGE_CURRENCY) {
+
+ if (ast_strlen_zero(currencyamount) || (sscanf(currencyamount, "%30u", &_currencyamount) != 1)) {
+ astman_send_error(s, m, "Invalid CurrencyAmount, CurrencyAmount is a required when ChargeType is Currency");
+ goto aocmessage_cleanup;
+ }
+
+ if (ast_strlen_zero(mult)) {
+ astman_send_error(s, m, "ChargeMultiplier unspecified, ChargeMultiplier is required when ChargeType is Currency.");
+ goto aocmessage_cleanup;
+ } else if (!strcasecmp(mult, "onethousandth")) {
+ _mult = AST_AOC_MULT_ONETHOUSANDTH;
+ } else if (!strcasecmp(mult, "onehundredth")) {
+ _mult = AST_AOC_MULT_ONEHUNDREDTH;
+ } else if (!strcasecmp(mult, "onetenth")) {
+ _mult = AST_AOC_MULT_ONETENTH;
+ } else if (!strcasecmp(mult, "one")) {
+ _mult = AST_AOC_MULT_ONE;
+ } else if (!strcasecmp(mult, "ten")) {
+ _mult = AST_AOC_MULT_TEN;
+ } else if (!strcasecmp(mult, "hundred")) {
+ _mult = AST_AOC_MULT_HUNDRED;
+ } else if (!strcasecmp(mult, "thousand")) {
+ _mult = AST_AOC_MULT_THOUSAND;
+ } else {
+ astman_send_error(s, m, "Invalid ChargeMultiplier");
+ goto aocmessage_cleanup;
+ }
+ }
+
+ /* create decoded object and start setting values */
+ if (!(decoded = ast_aoc_create(_msgtype, _chargetype, 0))) {
+ astman_send_error(s, m, "Message Creation Failed");
+ goto aocmessage_cleanup;
+ }
+
+ if (_msgtype == AST_AOC_D) {
+ if (!ast_strlen_zero(totaltype) && !strcasecmp(totaltype, "subtotal")) {
+ _totaltype = AST_AOC_SUBTOTAL;
+ }
+
+ if (ast_strlen_zero(aocbillingid)) {
+ /* ignore this is optional */
+ } else if (!strcasecmp(aocbillingid, "Normal")) {
+ _billingid = AST_AOC_BILLING_NORMAL;
+ } else if (!strcasecmp(aocbillingid, "ReverseCharge")) {
+ _billingid = AST_AOC_BILLING_REVERSE_CHARGE;
+ } else if (!strcasecmp(aocbillingid, "CreditCard")) {
+ _billingid = AST_AOC_BILLING_CREDIT_CARD;
+ } else {
+ astman_send_error(s, m, "Invalid AOC-D AOCBillingId");
+ goto aocmessage_cleanup;
+ }
+ } else {
+ if (ast_strlen_zero(aocbillingid)) {
+ /* ignore this is optional */
+ } else if (!strcasecmp(aocbillingid, "Normal")) {
+ _billingid = AST_AOC_BILLING_NORMAL;
+ } else if (!strcasecmp(aocbillingid, "ReverseCharge")) {
+ _billingid = AST_AOC_BILLING_REVERSE_CHARGE;
+ } else if (!strcasecmp(aocbillingid, "CreditCard")) {
+ _billingid = AST_AOC_BILLING_CREDIT_CARD;
+ } else if (!strcasecmp(aocbillingid, "CallFwdUnconditional")) {
+ _billingid = AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL;
+ } else if (!strcasecmp(aocbillingid, "CallFwdBusy")) {
+ _billingid = AST_AOC_BILLING_CALL_FWD_BUSY;
+ } else if (!strcasecmp(aocbillingid, "CallFwdNoReply")) {
+ _billingid = AST_AOC_BILLING_CALL_FWD_NO_REPLY;
+ } else if (!strcasecmp(aocbillingid, "CallDeflection")) {
+ _billingid = AST_AOC_BILLING_CALL_DEFLECTION;
+ } else if (!strcasecmp(aocbillingid, "CallTransfer")) {
+ _billingid = AST_AOC_BILLING_CALL_TRANSFER;
+ } else {
+ astman_send_error(s, m, "Invalid AOC-E AOCBillingId");
+ goto aocmessage_cleanup;
+ }
+
+ if (!ast_strlen_zero(association_id) && (sscanf(association_id, "%30d", &_association_id) != 1)) {
+ astman_send_error(s, m, "Invalid ChargingAssociationId");
+ goto aocmessage_cleanup;
+ }
+ if (!ast_strlen_zero(association_plan) && (sscanf(association_plan, "%30u", &_association_plan) != 1)) {
+ astman_send_error(s, m, "Invalid ChargingAssociationPlan");
+ goto aocmessage_cleanup;
+ }
+
+ if (_association_id) {
+ ast_aoc_set_association_id(decoded, _association_id);
+ } else if (!ast_strlen_zero(association_num)) {
+ ast_aoc_set_association_number(decoded, association_num, _association_plan);
+ }
+ }
+
+ if (_chargetype == AST_AOC_CHARGE_CURRENCY) {
+ ast_aoc_set_currency_info(decoded, _currencyamount, _mult, ast_strlen_zero(currencyname) ? NULL : currencyname);
+ } else if (_chargetype == AST_AOC_CHARGE_UNIT) {
+ struct ast_aoc_unit_entry entry;
+ int i;
+
+ /* multiple unit entries are possible, lets get them all */
+ for (i = 0; i < 32; i++) {
+ if (aocmessage_get_unit_entry(m, &entry, i)) {
+ break; /* that's the end then */
+ }
+
+ ast_aoc_add_unit_entry(decoded, entry.valid_amount, entry.amount, entry.valid_type, entry.type);
+ }
+
+ /* at least one unit entry is required */
+ if (!i) {
+ astman_send_error(s, m, "Invalid UnitAmount(0), At least one valid unit entry is required when ChargeType is set to Unit");
+ goto aocmessage_cleanup;
+ }
+
+ }
+
+ ast_aoc_set_billing_id(decoded, _billingid);
+ ast_aoc_set_total_type(decoded, _totaltype);
+
+
+ if ((encoded = ast_aoc_encode(decoded, &encoded_size, NULL)) && !ast_indicate_data(chan, AST_CONTROL_AOC, encoded, encoded_size)) {
+ astman_send_ack(s, m, "AOC Message successfully queued on channel");
+ } else {
+ astman_send_error(s, m, "Error encoding AOC message, could not queue onto channel");
+ }
+
+aocmessage_cleanup:
+
+ ast_aoc_destroy_decoded(decoded);
+ ast_aoc_destroy_encoded(encoded);
+
+ if (chan) {
+ chan = ast_channel_unref(chan);
+ }
+ return 0;
+}
+
static int action_originate(struct mansession *s, const struct message *m)
{
const char *name = astman_get_header(m, "Channel");
@@ -5665,6 +6002,7 @@ static int __init_manager(int reload)
ast_manager_register_xml("CoreShowChannels", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coreshowchannels);
ast_manager_register_xml("ModuleLoad", EVENT_FLAG_SYSTEM, manager_moduleload);
ast_manager_register_xml("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
+ ast_manager_register_xml("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);
ast_cli_register_multiple(cli_manager, ARRAY_LEN(cli_manager));
ast_extension_state_add(NULL, NULL, manager_state_cb, NULL);