diff options
author | Richard Mudgett <rmudgett@digium.com> | 2010-06-02 18:10:15 +0000 |
---|---|---|
committer | Richard Mudgett <rmudgett@digium.com> | 2010-06-02 18:10:15 +0000 |
commit | afd4454c4446ee1889616d09541171fc6355bbf7 (patch) | |
tree | 985bf496a795d25e80f0f4948c0f68cbadb0f7ac /main/aoc.c | |
parent | 266db9fa8c43f9e3c8c55e2f5bd44e432a7b91ee (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/aoc.c')
-rw-r--r-- | main/aoc.c | 1607 |
1 files changed, 1607 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)); +} |