From 245c5d9eb8a3742b31fe08e4d674100fe8292cc0 Mon Sep 17 00:00:00 2001 From: rmudgett Date: Wed, 2 Jun 2010 18:10:15 +0000 Subject: 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: http://svn.digium.com/svn/asterisk/trunk@267096 f38db490-d61c-443f-a65b-d21fe96a405b --- main/aoc.c | 1607 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ main/asterisk.c | 3 + main/channel.c | 5 + main/features.c | 1 + main/manager.c | 338 ++++++++++++ 5 files changed, 1954 insertions(+) create mode 100644 main/aoc.c (limited to 'main') 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 + * + * 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 + */ + +#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 @@ -694,6 +695,112 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") For success returns, the module revision number is included. + + + Generate an Advice of Charge message on a channel. + + + + + Channel name to generate the AOC message on. + + + 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. + + + Defines what type of AOC message to create, AOC-D or AOC-E + + + + + + + Defines what kind of charge this message represents. + + + + + + + + + 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. + + + + 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. + + + + Specifies the currency's name. Note that this value is truncated after 10 characters. + + + Specifies the charge unit amount as a positive integer. This value is required + when ChargeType==Currency. + + + Specifies the currency multiplier. This value is required when ChargeType==Currency. + + + + + + + + + + + + Defines what kind of AOC-D total is represented. + + + + + + + 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 + + + + + + + + + + + + + Charging association identifier. This is optional for AOC-E and can be + set to any value between -32768 and 32767 + + + Represents the charging association party number. This value is optional + for AOC-E. + + + 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. + + + + Generates an AOC-D or AOC-E message on a channel. + + ***/ 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); -- cgit v1.2.3