aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/app_dial.c30
-rw-r--r--apps/app_queue.c30
-rw-r--r--channels/chan_dahdi.c19
-rw-r--r--channels/chan_sip.c101
-rw-r--r--channels/sig_pri.c1343
-rw-r--r--channels/sig_pri.h17
-rw-r--r--channels/sip/include/sip.h10
-rw-r--r--configs/chan_dahdi.conf.sample18
-rw-r--r--configs/manager.conf.sample3
-rw-r--r--configs/sip.conf.sample6
-rw-r--r--doc/advice_of_charge.txt180
-rw-r--r--include/asterisk/aoc.h584
-rw-r--r--include/asterisk/frame.h1
-rw-r--r--main/aoc.c1607
-rw-r--r--main/asterisk.c3
-rw-r--r--main/channel.c5
-rw-r--r--main/features.c1
-rw-r--r--main/manager.c338
-rw-r--r--tests/test_aoc.c690
19 files changed, 4553 insertions, 433 deletions
diff --git a/apps/app_dial.c b/apps/app_dial.c
index 1422a4e02..d813c0068 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/global_datastores.h"
#include "asterisk/dsp.h"
#include "asterisk/cel.h"
+#include "asterisk/aoc.h"
#include "asterisk/ccss.h"
#include "asterisk/indications.h"
@@ -638,6 +639,7 @@ struct chanlist {
struct ast_party_connected_line connected;
/*! TRUE if an AST_CONTROL_CONNECTED_LINE update was saved to the connected element. */
unsigned int pending_connected_update:1;
+ struct ast_aoc_decoded *aoc_s_rate_list;
};
static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str *featurecode);
@@ -645,6 +647,7 @@ static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str
static void chanlist_free(struct chanlist *outgoing)
{
ast_party_connected_line_free(&outgoing->connected);
+ ast_aoc_destroy_decoded(outgoing->aoc_s_rate_list);
ast_free(outgoing);
}
@@ -1053,6 +1056,14 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
ast_party_connected_line_free(&connected_caller);
}
}
+ if (o->aoc_s_rate_list) {
+ size_t encoded_size;
+ struct ast_aoc_encoded *encoded;
+ if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
+ ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
+ ast_aoc_destroy_encoded(encoded);
+ }
+ }
peer = c;
ast_copy_flags64(peerflags, o,
OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
@@ -1115,6 +1126,14 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
ast_party_connected_line_free(&connected_caller);
}
}
+ if (o->aoc_s_rate_list) {
+ size_t encoded_size;
+ struct ast_aoc_encoded *encoded;
+ if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
+ ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
+ ast_aoc_destroy_encoded(encoded);
+ }
+ }
peer = c;
if (peer->cdr) {
peer->cdr->answer = ast_tvnow();
@@ -1230,6 +1249,17 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
}
}
break;
+ case AST_CONTROL_AOC:
+ {
+ struct ast_aoc_decoded *decoded = ast_aoc_decode(f->data.ptr, f->datalen, o->chan);
+ if (decoded && (ast_aoc_get_msg_type(decoded) == AST_AOC_S)) {
+ ast_aoc_destroy_decoded(o->aoc_s_rate_list);
+ o->aoc_s_rate_list = decoded;
+ } else {
+ ast_aoc_destroy_decoded(decoded);
+ }
+ }
+ break;
case AST_CONTROL_REDIRECTING:
if (ast_test_flag64(peerflags, OPT_IGNORE_CONNECTEDLINE)) {
ast_verb(3, "Redirecting update to %s prevented.\n", in->name);
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 68146ddea..1fdd97c5a 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -94,6 +94,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/strings.h"
#include "asterisk/global_datastores.h"
#include "asterisk/taskprocessor.h"
+#include "asterisk/aoc.h"
#include "asterisk/callerid.h"
#include "asterisk/cel.h"
#include "asterisk/data.h"
@@ -820,6 +821,7 @@ struct callattempt {
unsigned int pending_connected_update:1;
/*! TRUE if caller id is not available for connected line */
unsigned int dial_callerid_absent:1;
+ struct ast_aoc_decoded *aoc_s_rate_list;
};
@@ -2654,6 +2656,7 @@ static void hangupcalls(struct callattempt *outgoing, struct ast_channel *except
}
oo = outgoing;
outgoing = outgoing->q_next;
+ ast_aoc_destroy_decoded(oo->aoc_s_rate_list);
callattempt_free(oo);
}
}
@@ -3335,6 +3338,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
ast_party_connected_line_free(&connected_caller);
}
}
+ if (o->aoc_s_rate_list) {
+ size_t encoded_size;
+ struct ast_aoc_encoded *encoded;
+ if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
+ ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
+ ast_aoc_destroy_encoded(encoded);
+ }
+ }
peer = o;
}
} else if (o->chan && (o->chan == winner)) {
@@ -3454,6 +3465,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
ast_party_connected_line_free(&connected_caller);
}
}
+ if (o->aoc_s_rate_list) {
+ size_t encoded_size;
+ struct ast_aoc_encoded *encoded;
+ if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
+ ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
+ ast_aoc_destroy_encoded(encoded);
+ }
+ }
peer = o;
}
break;
@@ -3523,6 +3542,17 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
}
}
break;
+ case AST_CONTROL_AOC:
+ {
+ struct ast_aoc_decoded *decoded = ast_aoc_decode(f->data.ptr, f->datalen, o->chan);
+ if (decoded && (ast_aoc_get_msg_type(decoded) == AST_AOC_S)) {
+ ast_aoc_destroy_decoded(o->aoc_s_rate_list);
+ o->aoc_s_rate_list = decoded;
+ } else {
+ ast_aoc_destroy_decoded(decoded);
+ }
+ }
+ break;
case AST_CONTROL_REDIRECTING:
if (!update_connectedline) {
ast_verb(3, "Redirecting update to %s prevented\n", inchan_name);
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 2d28c6fb0..926daff73 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -11779,6 +11779,10 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
#endif /* defined(HAVE_PRI_CCSS) */
pris[span].pri.transfer = conf->chan.transfer;
pris[span].pri.facilityenable = conf->pri.pri.facilityenable;
+#if defined(HAVE_PRI_AOC_EVENTS)
+ pris[span].pri.aoc_passthrough_flag = conf->pri.pri.aoc_passthrough_flag;
+ pris[span].pri.aoce_delayhangup = conf->pri.pri.aoce_delayhangup;
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
ast_copy_string(pris[span].pri.msn_list, conf->pri.pri.msn_list, sizeof(pris[span].pri.msn_list));
ast_copy_string(pris[span].pri.idledial, conf->pri.pri.idledial, sizeof(pris[span].pri.idledial));
ast_copy_string(pris[span].pri.idleext, conf->pri.pri.idleext, sizeof(pris[span].pri.idleext));
@@ -17195,6 +17199,21 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
#endif /* PRI_GETSET_TIMERS */
} else if (!strcasecmp(v->name, "facilityenable")) {
confp->pri.pri.facilityenable = ast_true(v->value);
+#if defined(HAVE_PRI_AOC_EVENTS)
+ } else if (!strcasecmp(v->name, "aoc_enable")) {
+ confp->pri.pri.aoc_passthrough_flag = 0;
+ if (strchr(v->value, 's') || strchr(v->value, 'S')) {
+ confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_S;
+ }
+ if (strchr(v->value, 'd') || strchr(v->value, 'D')) {
+ confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_D;
+ }
+ if (strchr(v->value, 'e') || strchr(v->value, 'E')) {
+ confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_E;
+ }
+ } else if (!strcasecmp(v->name, "aoce_delayhangup")) {
+ confp->pri.pri.aoce_delayhangup = ast_true(v->value);
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_CALL_HOLD)
} else if (!strcasecmp(v->name, "hold_disconnect_transfer")) {
confp->pri.pri.hold_disconnect_transfer = ast_true(v->value);
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index eed364578..6c4cba72a 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -261,6 +261,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/event.h"
#include "asterisk/stun.h"
#include "asterisk/cel.h"
+#include "asterisk/aoc.h"
#include "sip/include/sip.h"
#include "sip/include/globals.h"
#include "sip/include/config_parser.h"
@@ -804,7 +805,7 @@ static int apeerobjs = 0; /*!< Autocreated peer objects */
static int regobjs = 0; /*!< Registry objects */
/* }@ */
-static struct ast_flags global_flags[2] = {{0}}; /*!< global SIP_ flags */
+static struct ast_flags global_flags[3] = {{0}}; /*!< global SIP_ flags */
static int global_t38_maxdatagram; /*!< global T.38 FaxMaxDatagram override */
static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */
@@ -1275,6 +1276,7 @@ static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqn
static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri);
static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri);
static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
+static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded);
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
static int transmit_info_with_vidupdate(struct sip_pvt *p);
static int transmit_message_with_text(struct sip_pvt *p, const char *text);
@@ -4702,6 +4704,7 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
dialog->capability = peer->capability;
dialog->prefs = peer->prefs;
if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
@@ -6249,6 +6252,41 @@ static int sip_indicate(struct ast_channel *ast, int condition, const void *data
case AST_CONTROL_REDIRECTING:
update_redirecting(p, data, datalen);
break;
+ case AST_CONTROL_AOC:
+ {
+ struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, ast);
+ if (!decoded) {
+ ast_log(LOG_ERROR, "Error decoding indicated AOC data\n");
+ res = -1;
+ break;
+ }
+ switch (ast_aoc_get_msg_type(decoded)) {
+ case AST_AOC_REQUEST:
+ if (ast_aoc_get_termination_request(decoded)) {
+ /* TODO, once there is a way to get AOC-E on hangup, attempt that here
+ * before hanging up the channel.*/
+
+ /* The other side has already initiated the hangup. This frame
+ * just says they are waiting to get AOC-E before completely tearing
+ * the call down. Since SIP does not support this at the moment go
+ * ahead and terminate the call here to avoid an unnecessary timeout. */
+ ast_log(LOG_DEBUG, "AOC-E termination request received on %s. This is not yet supported on sip. Continue with hangup \n", p->owner->name);
+ ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
+ }
+ break;
+ case AST_AOC_D:
+ case AST_AOC_E:
+ if (ast_test_flag(&p->flags[2], SIP_PAGE3_SNOM_AOC)) {
+ transmit_info_with_aoc(p, decoded);
+ }
+ break;
+ case AST_AOC_S: /* S not supported yet */
+ default:
+ break;
+ }
+ ast_aoc_destroy_decoded(decoded);
+ }
+ break;
case -1:
res = -1;
break;
@@ -6888,6 +6926,7 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin,
/* Copy global flags to this PVT at setup. */
ast_copy_flags(&p->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY);
p->do_history = recordhistory;
@@ -11856,6 +11895,53 @@ static int transmit_refer(struct sip_pvt *p, const char *dest)
*/
}
+/*! \brief Send SIP INFO advice of charge message */
+static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded)
+{
+ struct sip_request req;
+ struct ast_str *str = ast_str_alloca(512);
+ const struct ast_aoc_unit_entry *unit_entry = ast_aoc_get_unit_info(decoded, 0);
+ enum ast_aoc_charge_type charging = ast_aoc_get_charge_type(decoded);
+
+ reqprep(&req, p, SIP_INFO, 0, 1);
+
+ if (ast_aoc_get_msg_type(decoded) == AST_AOC_D) {
+ ast_str_append(&str, 0, "type=active;");
+ } else if (ast_aoc_get_msg_type(decoded) == AST_AOC_E) {
+ ast_str_append(&str, 0, "type=terminated;");
+ } else {
+ /* unsupported message type */
+ return -1;
+ }
+
+ switch (charging) {
+ case AST_AOC_CHARGE_FREE:
+ ast_str_append(&str, 0, "free-of-charge;");
+ break;
+ case AST_AOC_CHARGE_CURRENCY:
+ ast_str_append(&str, 0, "charging;");
+ ast_str_append(&str, 0, "charging-info=currency;");
+ ast_str_append(&str, 0, "amount=%u;", ast_aoc_get_currency_amount(decoded));
+ ast_str_append(&str, 0, "multiplier=%s;", ast_aoc_get_currency_multiplier_decimal(decoded));
+ if (!ast_strlen_zero(ast_aoc_get_currency_name(decoded))) {
+ ast_str_append(&str, 0, "currency=%s;", ast_aoc_get_currency_name(decoded));
+ }
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(&str, 0, "charging;");
+ ast_str_append(&str, 0, "charging-info=pulse;");
+ if (unit_entry) {
+ ast_str_append(&str, 0, "recorded-units=%u;", unit_entry->amount);
+ }
+ break;
+ default:
+ ast_str_append(&str, 0, "not-available;");
+ };
+
+ add_header(&req, "AOC", ast_str_buffer(str));
+
+ return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+}
/*! \brief Send SIP INFO dtmf message, see Cisco documentation on cisco.com */
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration)
@@ -14037,6 +14123,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
/* Take the peer */
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) && p->udptl) {
p->t38_maxdatagram = peer->t38_maxdatagram;
@@ -14081,6 +14168,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable, req->ignore))) {
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
/* If we have a call limit, set flag */
if (peer->call_limit)
ast_set_flag(&p->flags[0], SIP_CALL_LIMIT);
@@ -24000,6 +24088,7 @@ static int sip_poke_peer(struct sip_peer *peer, int force)
copy_socket_data(&p->socket, &peer->socket);
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
/* Send OPTIONs to peer's fullcontact */
if (!ast_strlen_zero(peer->fullcontact))
@@ -24757,6 +24846,7 @@ static void set_peer_defaults(struct sip_peer *peer)
peer->type = SIP_TYPE_PEER;
ast_copy_flags(&peer->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ ast_copy_flags(&peer->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY);
ast_string_field_set(peer, context, sip_cfg.default_context);
ast_string_field_set(peer, subscribecontext, sip_cfg.default_subscribecontext);
ast_string_field_set(peer, language, default_language);
@@ -24863,8 +24953,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
int format = 0; /* Ama flags */
int timerb_set = 0, timert1_set = 0;
time_t regseconds = 0;
- struct ast_flags peerflags[2] = {{(0)}};
- struct ast_flags mask[2] = {{(0)}};
+ struct ast_flags peerflags[3] = {{(0)}};
+ struct ast_flags mask[3] = {{(0)}};
char callback[256] = "";
struct sip_peer tmp_peer;
const char *srvlookup = NULL;
@@ -25255,6 +25345,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
ast_string_field_set(peer, unsolicited_mailbox, v->value);
} else if (!strcasecmp(v->name, "use_q850_reason")) {
ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON);
+ } else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
+ ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);
}
}
@@ -25424,6 +25516,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
ast_copy_flags(&peer->flags[0], &peerflags[0], mask[0].flags);
ast_copy_flags(&peer->flags[1], &peerflags[1], mask[1].flags);
+ ast_copy_flags(&peer->flags[2], &peerflags[2], mask[2].flags);
if (ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)) {
sip_cfg.allowsubscribe = TRUE; /* No global ban any more */
}
@@ -26163,6 +26256,8 @@ static int reload_config(enum channelreloadreason reason)
}
} else if (!strcasecmp(v->name, "use_q850_reason")) {
ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON);
+ } else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
+ ast_set2_flag(&global_flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);
}
}
diff --git a/channels/sig_pri.c b/channels/sig_pri.c
index 44f5d2384..2fda97716 100644
--- a/channels/sig_pri.c
+++ b/channels/sig_pri.c
@@ -46,6 +46,7 @@
#include "asterisk/cli.h"
#include "asterisk/transcap.h"
#include "asterisk/features.h"
+#include "asterisk/aoc.h"
#include "sig_pri.h"
#ifndef PRI_EVENT_FACILITY
@@ -1086,6 +1087,18 @@ static int pri_fixup_principle(struct sig_pri_pri *pri, int principle, q931_call
new_chan->setup_ack = old_chan->setup_ack;
new_chan->outgoing = old_chan->outgoing;
new_chan->digital = old_chan->digital;
+#if defined(HAVE_PRI_AOC_EVENTS)
+ new_chan->aoc_s_request_invoke_id = old_chan->aoc_s_request_invoke_id;
+ new_chan->aoc_s_request_invoke_id_valid = old_chan->aoc_s_request_invoke_id_valid;
+ new_chan->holding_aoce = old_chan->holding_aoce;
+ new_chan->waiting_for_aoce = old_chan->waiting_for_aoce;
+ new_chan->aoc_e = old_chan->aoc_e;
+
+ old_chan->holding_aoce = 0;
+ old_chan->aoc_s_request_invoke_id_valid = 0;
+ old_chan->waiting_for_aoce = 0;
+ memset(&old_chan->aoc_e, 0, sizeof(&old_chan->aoc_e));
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
old_chan->alerting = 0;
old_chan->alreadyhungup = 0;
old_chan->isidlecall = 0;
@@ -2057,645 +2070,934 @@ static void sig_pri_cc_link_canceled(struct sig_pri_pri *pri, long cc_id, int is
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_CHARGED_ITEM to string.
+ * \brief Convert ast_aoc_charged_item to PRI_AOC_CHARGED_ITEM .
* \since 1.8
*
* \param value Value to convert to string.
*
- * \return String equivalent.
+ * \return PRI_AOC_CHARGED_ITEM
*/
-static const char *sig_pri_aoc_charged_item_str(enum PRI_AOC_CHARGED_ITEM value)
+static enum PRI_AOC_CHARGED_ITEM sig_pri_aoc_charged_item_to_pri(enum PRI_AOC_CHARGED_ITEM value)
{
- const char *str;
+ switch (value) {
+ case AST_AOC_CHARGED_ITEM_NA:
+ return PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE;
+ case AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT:
+ return PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT;
+ case AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION:
+ return PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION;
+ case AST_AOC_CHARGED_ITEM_CALL_ATTEMPT:
+ return PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT;
+ case AST_AOC_CHARGED_ITEM_CALL_SETUP:
+ return PRI_AOC_CHARGED_ITEM_CALL_SETUP;
+ case AST_AOC_CHARGED_ITEM_USER_USER_INFO:
+ return PRI_AOC_CHARGED_ITEM_USER_USER_INFO;
+ case AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE:
+ return PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE;
+ }
+ return PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE;
+}
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+#if defined(HAVE_PRI_AOC_EVENTS)
+/*!
+ * \internal
+ * \brief Convert PRI_AOC_CHARGED_ITEM to ast_aoc_charged_item.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return ast_aoc_charged_item
+ */
+static enum ast_aoc_s_charged_item sig_pri_aoc_charged_item_to_ast(enum PRI_AOC_CHARGED_ITEM value)
+{
switch (value) {
- default:
case PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE:
- str = "NotAvailable";
- break;
+ return AST_AOC_CHARGED_ITEM_NA;
case PRI_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT:
- str = "SpecialArrangement";
- break;
+ return AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT;
case PRI_AOC_CHARGED_ITEM_BASIC_COMMUNICATION:
- str = "BasicCommunication";
- break;
+ return AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION;
case PRI_AOC_CHARGED_ITEM_CALL_ATTEMPT:
- str = "CallAttempt";
- break;
+ return AST_AOC_CHARGED_ITEM_CALL_ATTEMPT;
case PRI_AOC_CHARGED_ITEM_CALL_SETUP:
- str = "CallSetup";
- break;
+ return AST_AOC_CHARGED_ITEM_CALL_SETUP;
case PRI_AOC_CHARGED_ITEM_USER_USER_INFO:
- str = "UserUserInfo";
- break;
+ return AST_AOC_CHARGED_ITEM_USER_USER_INFO;
case PRI_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE:
- str = "SupplementaryService";
- break;
+ return AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE;
}
- return str;
+ return AST_AOC_CHARGED_ITEM_NA;
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_RATE_TYPE to string.
+ * \brief Convert AST_AOC_MULTIPLER to PRI_AOC_MULTIPLIER.
* \since 1.8
*
- * \param value Value to convert to string.
- *
- * \return String equivalent.
+ * \return pri enum equivalent.
*/
-static const char *sig_pri_aoc_rate_type_str(enum PRI_AOC_RATE_TYPE value)
+static int sig_pri_aoc_multiplier_from_ast(enum ast_aoc_currency_multiplier mult)
{
- const char *str;
-
- switch (value) {
+ switch (mult) {
+ case AST_AOC_MULT_ONETHOUSANDTH:
+ return PRI_AOC_MULTIPLIER_THOUSANDTH;
+ case AST_AOC_MULT_ONEHUNDREDTH:
+ return PRI_AOC_MULTIPLIER_HUNDREDTH;
+ case AST_AOC_MULT_ONETENTH:
+ return PRI_AOC_MULTIPLIER_TENTH;
+ case AST_AOC_MULT_ONE:
+ return PRI_AOC_MULTIPLIER_ONE;
+ case AST_AOC_MULT_TEN:
+ return PRI_AOC_MULTIPLIER_TEN;
+ case AST_AOC_MULT_HUNDRED:
+ return PRI_AOC_MULTIPLIER_HUNDRED;
+ case AST_AOC_MULT_THOUSAND:
+ return PRI_AOC_MULTIPLIER_THOUSAND;
default:
- case PRI_AOC_RATE_TYPE_NOT_AVAILABLE:
- str = "NotAvailable";
- break;
- case PRI_AOC_RATE_TYPE_FREE:
- str = "Free";
- break;
- case PRI_AOC_RATE_TYPE_FREE_FROM_BEGINNING:
- str = "FreeFromBeginning";
- break;
- case PRI_AOC_RATE_TYPE_DURATION:
- str = "Duration";
- break;
- case PRI_AOC_RATE_TYPE_FLAT:
- str = "Flat";
- break;
- case PRI_AOC_RATE_TYPE_VOLUME:
- str = "Volume";
- break;
- case PRI_AOC_RATE_TYPE_SPECIAL_CODE:
- str = "SpecialCode";
- break;
+ return PRI_AOC_MULTIPLIER_ONE;
}
- return str;
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_VOLUME_UNIT to string.
+ * \brief Convert PRI_AOC_MULTIPLIER to AST_AOC_MULTIPLIER
* \since 1.8
*
- * \param value Value to convert to string.
- *
- * \return String equivalent.
+ * \return ast enum equivalent.
*/
-static const char *sig_pri_aoc_volume_unit_str(enum PRI_AOC_VOLUME_UNIT value)
+static int sig_pri_aoc_multiplier_from_pri(const int mult)
{
- const char *str;
-
- switch (value) {
+ switch (mult) {
+ case PRI_AOC_MULTIPLIER_THOUSANDTH:
+ return AST_AOC_MULT_ONETHOUSANDTH;
+ case PRI_AOC_MULTIPLIER_HUNDREDTH:
+ return AST_AOC_MULT_ONEHUNDREDTH;
+ case PRI_AOC_MULTIPLIER_TENTH:
+ return AST_AOC_MULT_ONETENTH;
+ case PRI_AOC_MULTIPLIER_ONE:
+ return AST_AOC_MULT_ONE;
+ case PRI_AOC_MULTIPLIER_TEN:
+ return AST_AOC_MULT_TEN;
+ case PRI_AOC_MULTIPLIER_HUNDRED:
+ return AST_AOC_MULT_HUNDRED;
+ case PRI_AOC_MULTIPLIER_THOUSAND:
+ return AST_AOC_MULT_THOUSAND;
default:
- case PRI_AOC_VOLUME_UNIT_OCTET:
- str = "Octet";
- break;
- case PRI_AOC_VOLUME_UNIT_SEGMENT:
- str = "Segment";
- break;
- case PRI_AOC_VOLUME_UNIT_MESSAGE:
- str = "Message";
- break;
+ return AST_AOC_MULT_ONE;
}
- return str;
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_MULTIPLIER to string.
+ * \brief Convert ast_aoc_time_scale representation to PRI_AOC_TIME_SCALE
* \since 1.8
*
- * \param value Value to convert to string.
+ * \param value Value to convert to ast representation
*
- * \return String equivalent.
+ * \return PRI_AOC_TIME_SCALE
*/
-static const char *sig_pri_aoc_multiplier_str(enum PRI_AOC_MULTIPLIER value)
+static enum PRI_AOC_TIME_SCALE sig_pri_aoc_scale_to_pri(enum ast_aoc_time_scale value)
{
- const char *str;
-
switch (value) {
default:
- case PRI_AOC_MULTIPLIER_THOUSANDTH:
- str = "1/1000";
- break;
- case PRI_AOC_MULTIPLIER_HUNDREDTH:
- str = "1/100";
- break;
- case PRI_AOC_MULTIPLIER_TENTH:
- str = "1/10";
- break;
- case PRI_AOC_MULTIPLIER_ONE:
- str = "1";
- break;
- case PRI_AOC_MULTIPLIER_TEN:
- str = "10";
- break;
- case PRI_AOC_MULTIPLIER_HUNDRED:
- str = "100";
- break;
- case PRI_AOC_MULTIPLIER_THOUSAND:
- str = "1000";
- break;
+ case AST_AOC_TIME_SCALE_HUNDREDTH_SECOND:
+ return PRI_AOC_TIME_SCALE_HUNDREDTH_SECOND;
+ case AST_AOC_TIME_SCALE_TENTH_SECOND:
+ return PRI_AOC_TIME_SCALE_TENTH_SECOND;
+ case AST_AOC_TIME_SCALE_SECOND:
+ return PRI_AOC_TIME_SCALE_SECOND;
+ case AST_AOC_TIME_SCALE_TEN_SECOND:
+ return PRI_AOC_TIME_SCALE_TEN_SECOND;
+ case AST_AOC_TIME_SCALE_MINUTE:
+ return PRI_AOC_TIME_SCALE_MINUTE;
+ case AST_AOC_TIME_SCALE_HOUR:
+ return PRI_AOC_TIME_SCALE_HOUR;
+ case AST_AOC_TIME_SCALE_DAY:
+ return PRI_AOC_TIME_SCALE_DAY;
}
- return str;
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_TIME_SCALE to string.
+ * \brief Convert PRI_AOC_TIME_SCALE to ast aoc representation
* \since 1.8
*
- * \param value Value to convert to string.
+ * \param value Value to convert to ast representation
*
- * \return String equivalent.
+ * \return ast aoc time scale
*/
-static const char *sig_pri_aoc_scale_str(enum PRI_AOC_TIME_SCALE value)
+static enum ast_aoc_time_scale sig_pri_aoc_scale_to_ast(enum PRI_AOC_TIME_SCALE value)
{
- const char *str;
-
switch (value) {
default:
case PRI_AOC_TIME_SCALE_HUNDREDTH_SECOND:
- str = "OneHundredthSecond";
- break;
+ return AST_AOC_TIME_SCALE_HUNDREDTH_SECOND;
case PRI_AOC_TIME_SCALE_TENTH_SECOND:
- str = "OneTenthSecond";
- break;
+ return AST_AOC_TIME_SCALE_TENTH_SECOND;
case PRI_AOC_TIME_SCALE_SECOND:
- str = "Second";
- break;
+ return AST_AOC_TIME_SCALE_SECOND;
case PRI_AOC_TIME_SCALE_TEN_SECOND:
- str = "TenSeconds";
- break;
+ return AST_AOC_TIME_SCALE_TEN_SECOND;
case PRI_AOC_TIME_SCALE_MINUTE:
- str = "Minute";
- break;
+ return AST_AOC_TIME_SCALE_MINUTE;
case PRI_AOC_TIME_SCALE_HOUR:
- str = "Hour";
- break;
+ return AST_AOC_TIME_SCALE_HOUR;
case PRI_AOC_TIME_SCALE_DAY:
- str = "Day";
- break;
+ return AST_AOC_TIME_SCALE_DAY;
}
- return str;
+ return AST_AOC_TIME_SCALE_HUNDREDTH_SECOND;
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_DE_CHARGE to string.
+ * \brief Handle AOC-S control frame
* \since 1.8
*
- * \param value Value to convert to string.
+ * \param aoc_s AOC-S event parameters.
+ * \param owner Asterisk channel associated with the call.
+ * \param passthrough indicating if this message should be queued on the ast channel
*
- * \return String equivalent.
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri private is locked
+ * \note Assumes the owner channel lock is already obtained.
+ *
+ * \return Nothing
*/
-static const char *sig_pri_aoc_de_charge_str(enum PRI_AOC_DE_CHARGE value)
+static void sig_pri_aoc_s_from_pri(const struct pri_subcmd_aoc_s *aoc_s, struct ast_channel *owner, int passthrough)
{
- const char *str;
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t encoded_size = 0;
+ int idx;
- switch (value) {
- default:
- case PRI_AOC_DE_CHARGE_NOT_AVAILABLE:
- str = "NotAvailable";
- break;
- case PRI_AOC_DE_CHARGE_FREE:
- str = "Free";
- break;
- case PRI_AOC_DE_CHARGE_CURRENCY:
- str = "Currency";
- break;
- case PRI_AOC_DE_CHARGE_UNITS:
- str = "Units";
- break;
+ if (!owner || !aoc_s) {
+ return;
+ }
+
+ if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) {
+ return;
}
- return str;
+
+ for (idx = 0; idx < aoc_s->num_items; ++idx) {
+ enum ast_aoc_s_charged_item charged_item;
+
+ charged_item = sig_pri_aoc_charged_item_to_ast(aoc_s->item[idx].chargeable);
+ if (charged_item == AST_AOC_CHARGED_ITEM_NA) {
+ /* Delete the unknown charged item from the list. */
+ continue;
+ }
+ switch (aoc_s->item[idx].rate_type) {
+ case PRI_AOC_RATE_TYPE_DURATION:
+ ast_aoc_s_add_rate_duration(decoded,
+ charged_item,
+ aoc_s->item[idx].rate.duration.amount.cost,
+ sig_pri_aoc_multiplier_from_pri(aoc_s->item[idx].rate.duration.amount.multiplier),
+ aoc_s->item[idx].rate.duration.currency,
+ aoc_s->item[idx].rate.duration.time.length,
+ sig_pri_aoc_scale_to_ast(aoc_s->item[idx].rate.duration.time.scale),
+ aoc_s->item[idx].rate.duration.granularity.length,
+ sig_pri_aoc_scale_to_ast(aoc_s->item[idx].rate.duration.granularity.scale),
+ aoc_s->item[idx].rate.duration.charging_type);
+ break;
+ case PRI_AOC_RATE_TYPE_FLAT:
+ ast_aoc_s_add_rate_flat(decoded,
+ charged_item,
+ aoc_s->item[idx].rate.flat.amount.cost,
+ sig_pri_aoc_multiplier_from_pri(aoc_s->item[idx].rate.flat.amount.multiplier),
+ aoc_s->item[idx].rate.flat.currency);
+ break;
+ case PRI_AOC_RATE_TYPE_VOLUME:
+ ast_aoc_s_add_rate_volume(decoded,
+ charged_item,
+ aoc_s->item[idx].rate.volume.unit,
+ aoc_s->item[idx].rate.volume.amount.cost,
+ sig_pri_aoc_multiplier_from_pri(aoc_s->item[idx].rate.volume.amount.multiplier),
+ aoc_s->item[idx].rate.volume.currency);
+ break;
+ case PRI_AOC_RATE_TYPE_SPECIAL_CODE:
+ ast_aoc_s_add_rate_special_charge_code(decoded,
+ charged_item,
+ aoc_s->item[idx].rate.special);
+ break;
+ case PRI_AOC_RATE_TYPE_FREE:
+ ast_aoc_s_add_rate_free(decoded, charged_item, 0);
+ break;
+ case PRI_AOC_RATE_TYPE_FREE_FROM_BEGINNING:
+ ast_aoc_s_add_rate_free(decoded, charged_item, 1);
+ break;
+ default:
+ ast_aoc_s_add_rate_na(decoded, charged_item);
+ break;
+ }
+ }
+
+ if (passthrough && (encoded = ast_aoc_encode(decoded, &encoded_size, owner))) {
+ ast_queue_control_data(owner, AST_CONTROL_AOC, encoded, encoded_size);
+ }
+
+ ast_aoc_manager_event(decoded, owner);
+
+ ast_aoc_destroy_decoded(decoded);
+ ast_aoc_destroy_encoded(encoded);
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_D_BILLING_ID to string.
+ * \brief Generate AOC Request Response
* \since 1.8
*
- * \param value Value to convert to string.
+ * \param aoc_request
*
- * \return String equivalent.
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri private is locked
+ * \note Assumes the owner channel lock is already obtained.
+ *
+ * \return Nothing
*/
-static const char *sig_pri_aoc_d_billing_id_str(enum PRI_AOC_D_BILLING_ID value)
+static void sig_pri_aoc_request_from_pri(const struct pri_subcmd_aoc_request *aoc_request, struct sig_pri_chan *pvt, q931_call *call)
{
- const char *str;
+ int request;
- switch (value) {
+ if (!aoc_request) {
+ return;
+ }
+
+ request = aoc_request->charging_request;
+
+ if (request & PRI_AOC_REQUEST_S) {
+ if (pvt->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S) {
+ /* An AOC-S response must come from the other side, so save off this invoke_id
+ * and see if an AOC-S message comes in before the call is answered. */
+ pvt->aoc_s_request_invoke_id = aoc_request->invoke_id;
+ pvt->aoc_s_request_invoke_id_valid = 1;
+
+ } else {
+ pri_aoc_s_request_response_send(pvt->pri->pri,
+ call,
+ aoc_request->invoke_id,
+ NULL);
+ }
+ }
+
+ if (request & PRI_AOC_REQUEST_D) {
+ if (pvt->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D) {
+ pri_aoc_de_request_response_send(pvt->pri->pri,
+ call,
+ PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS,
+ aoc_request->invoke_id);
+ } else {
+ pri_aoc_de_request_response_send(pvt->pri->pri,
+ call,
+ PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE,
+ aoc_request->invoke_id);
+ }
+ }
+
+ if (request & PRI_AOC_REQUEST_E) {
+ if (pvt->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E) {
+ pri_aoc_de_request_response_send(pvt->pri->pri,
+ call,
+ PRI_AOC_REQ_RSP_CHARGING_INFO_FOLLOWS,
+ aoc_request->invoke_id);
+ } else {
+ pri_aoc_de_request_response_send(pvt->pri->pri,
+ call,
+ PRI_AOC_REQ_RSP_ERROR_NOT_AVAILABLE,
+ aoc_request->invoke_id);
+ }
+ }
+}
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
+#if defined(HAVE_PRI_AOC_EVENTS)
+/*!
+ * \internal
+ * \brief Generate AOC-D AST_CONTROL_AOC frame
+ * \since 1.8
+ *
+ * \param aoc_e AOC-D event parameters.
+ * \param owner Asterisk channel associated with the call.
+ * \param passthrough indicating if this message should be queued on the ast channel
+ *
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri private is locked
+ * \note Assumes the owner channel lock is already obtained.
+ *
+ * \return Nothing
+ */
+static void sig_pri_aoc_d_from_pri(const struct pri_subcmd_aoc_d *aoc_d, struct ast_channel *owner, int passthrough)
+{
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t encoded_size = 0;
+ enum ast_aoc_charge_type type;
+
+ if (!owner || !aoc_d) {
+ return;
+ }
+
+ switch (aoc_d->charge) {
+ case PRI_AOC_DE_CHARGE_CURRENCY:
+ type = AST_AOC_CHARGE_CURRENCY;
+ break;
+ case PRI_AOC_DE_CHARGE_UNITS:
+ type = AST_AOC_CHARGE_UNIT;
+ break;
+ case PRI_AOC_DE_CHARGE_FREE:
+ type = AST_AOC_CHARGE_FREE;
+ break;
default:
- case PRI_AOC_D_BILLING_ID_NOT_AVAILABLE:
- str = "NotAvailable";
+ type = AST_AOC_CHARGE_NA;
break;
+ }
+
+ if (!(decoded = ast_aoc_create(AST_AOC_D, type, 0))) {
+ return;
+ }
+
+ switch (aoc_d->billing_accumulation) {
+ default:
+ ast_debug(1, "AOC-D billing accumulation has unknown value: %d\n",
+ aoc_d->billing_accumulation);
+ /* Fall through */
+ case 0:/* subTotal */
+ ast_aoc_set_total_type(decoded, AST_AOC_SUBTOTAL);
+ break;
+ case 1:/* total */
+ ast_aoc_set_total_type(decoded, AST_AOC_TOTAL);
+ break;
+ }
+
+ switch (aoc_d->billing_id) {
case PRI_AOC_D_BILLING_ID_NORMAL:
- str = "Normal";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NORMAL);
break;
case PRI_AOC_D_BILLING_ID_REVERSE:
- str = "Reverse";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_REVERSE_CHARGE);
break;
case PRI_AOC_D_BILLING_ID_CREDIT_CARD:
- str = "CreditCard";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CREDIT_CARD);
+ break;
+ case PRI_AOC_D_BILLING_ID_NOT_AVAILABLE:
+ default:
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NA);
break;
}
- return str;
+
+ switch (aoc_d->charge) {
+ case PRI_AOC_DE_CHARGE_CURRENCY:
+ ast_aoc_set_currency_info(decoded,
+ aoc_d->recorded.money.amount.cost,
+ sig_pri_aoc_multiplier_from_pri(aoc_d->recorded.money.amount.multiplier),
+ aoc_d->recorded.money.currency);
+ break;
+ case PRI_AOC_DE_CHARGE_UNITS:
+ {
+ int i;
+ for (i = 0; i < aoc_d->recorded.unit.num_items; ++i) {
+ /* if type or number are negative, then they are not present */
+ ast_aoc_add_unit_entry(decoded,
+ (aoc_d->recorded.unit.item[i].number >= 0 ? 1 : 0),
+ aoc_d->recorded.unit.item[i].number,
+ (aoc_d->recorded.unit.item[i].type >= 0 ? 1 : 0),
+ aoc_d->recorded.unit.item[i].type);
+ }
+ }
+ break;
+ }
+
+ if (passthrough && (encoded = ast_aoc_encode(decoded, &encoded_size, owner))) {
+ ast_queue_control_data(owner, AST_CONTROL_AOC, encoded, encoded_size);
+ }
+
+ ast_aoc_manager_event(decoded, owner);
+
+ ast_aoc_destroy_decoded(decoded);
+ ast_aoc_destroy_encoded(encoded);
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Convert PRI_AOC_E_BILLING_ID to string.
+ * \brief Generate AOC-E AST_CONTROL_AOC frame
* \since 1.8
*
- * \param value Value to convert to string.
+ * \param aoc_e AOC-E event parameters.
+ * \param owner Asterisk channel associated with the call.
+ * \param passthrough indicating if this message should be queued on the ast channel
+ *
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri private is locked
+ * \note Assumes the owner channel lock is already obtained.
+ * \note owner channel may be NULL. In that case, generate event only
*
- * \return String equivalent.
+ * \return Nothing
*/
-static const char *sig_pri_aoc_e_billing_id_str(enum PRI_AOC_E_BILLING_ID value)
+static void sig_pri_aoc_e_from_pri(const struct pri_subcmd_aoc_e *aoc_e, struct ast_channel *owner, int passthrough)
{
- const char *str;
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t encoded_size = 0;
+ enum ast_aoc_charge_type type;
- switch (value) {
+ if (!aoc_e) {
+ return;
+ }
+
+ switch (aoc_e->charge) {
+ case PRI_AOC_DE_CHARGE_CURRENCY:
+ type = AST_AOC_CHARGE_CURRENCY;
+ break;
+ case PRI_AOC_DE_CHARGE_UNITS:
+ type = AST_AOC_CHARGE_UNIT;
+ break;
+ case PRI_AOC_DE_CHARGE_FREE:
+ type = AST_AOC_CHARGE_FREE;
+ break;
+ default:
+ type = AST_AOC_CHARGE_NA;
+ break;
+ }
+
+ if (!(decoded = ast_aoc_create(AST_AOC_E, type, 0))) {
+ return;
+ }
+
+ switch (aoc_e->associated.charging_type) {
+ case PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER:
+ if (!aoc_e->associated.charge.number.valid) {
+ break;
+ }
+ ast_aoc_set_association_number(decoded, aoc_e->associated.charge.number.str, aoc_e->associated.charge.number.plan);
+ break;
+ case PRI_AOC_E_CHARGING_ASSOCIATION_ID:
+ ast_aoc_set_association_id(decoded, aoc_e->associated.charge.id);
+ break;
default:
- case PRI_AOC_E_BILLING_ID_NOT_AVAILABLE:
- str = "NotAvailable";
break;
+ }
+
+ switch (aoc_e->billing_id) {
case PRI_AOC_E_BILLING_ID_NORMAL:
- str = "Normal";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NORMAL);
break;
case PRI_AOC_E_BILLING_ID_REVERSE:
- str = "Reverse";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_REVERSE_CHARGE);
break;
case PRI_AOC_E_BILLING_ID_CREDIT_CARD:
- str = "CreditCard";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CREDIT_CARD);
break;
case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL:
- str = "CallForwardingUnconditional";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL);
break;
case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY:
- str = "CallForwardingBusy";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_FWD_BUSY);
break;
case PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY:
- str = "CallForwardingNoReply";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_FWD_NO_REPLY);
break;
case PRI_AOC_E_BILLING_ID_CALL_DEFLECTION:
- str = "CallDeflection";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_DEFLECTION);
break;
case PRI_AOC_E_BILLING_ID_CALL_TRANSFER:
- str = "CallTransfer";
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CALL_TRANSFER);
+ break;
+ case PRI_AOC_E_BILLING_ID_NOT_AVAILABLE:
+ default:
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NA);
break;
}
- return str;
-}
-#endif /* defined(HAVE_PRI_AOC_EVENTS) */
-#if defined(HAVE_PRI_AOC_EVENTS)
-/*!
- * \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.
- *
- * \return Nothing
- */
-static void sig_pri_aoc_amount(struct ast_str **msg, const char *prefix, const struct pri_aoc_amount *amount)
-{
- static const char name[] = "Amount";
+ switch (aoc_e->charge) {
+ case PRI_AOC_DE_CHARGE_CURRENCY:
+ ast_aoc_set_currency_info(decoded,
+ aoc_e->recorded.money.amount.cost,
+ sig_pri_aoc_multiplier_from_pri(aoc_e->recorded.money.amount.multiplier),
+ aoc_e->recorded.money.currency);
+ break;
+ case PRI_AOC_DE_CHARGE_UNITS:
+ {
+ int i;
+ for (i = 0; i < aoc_e->recorded.unit.num_items; ++i) {
+ /* if type or number are negative, then they are not present */
+ ast_aoc_add_unit_entry(decoded,
+ (aoc_e->recorded.unit.item[i].number >= 0 ? 1 : 0),
+ aoc_e->recorded.unit.item[i].number,
+ (aoc_e->recorded.unit.item[i].type >= 0 ? 1 : 0),
+ aoc_e->recorded.unit.item[i].type);
+ }
+ }
+ }
- ast_str_append(msg, 0, "%s/%s/Cost: %ld\r\n", prefix, name, amount->cost);
- ast_str_append(msg, 0, "%s/%s/Multiplier: %s\r\n", prefix, name,
- sig_pri_aoc_multiplier_str(amount->multiplier));
-}
-#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+ if (passthrough && owner && (encoded = ast_aoc_encode(decoded, &encoded_size, owner))) {
+ ast_queue_control_data(owner, AST_CONTROL_AOC, encoded, encoded_size);
+ }
-#if defined(HAVE_PRI_AOC_EVENTS)
-/*!
- * \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.
- *
- * \return Nothing
- */
-static void sig_pri_aoc_time(struct ast_str **msg, const char *prefix, const char *name, const struct pri_aoc_time *time)
-{
- ast_str_append(msg, 0, "%s/%s/Length: %ld\r\n", prefix, name, time->length);
- ast_str_append(msg, 0, "%s/%s/Scale: %s\r\n", prefix, name,
- sig_pri_aoc_scale_str(time->scale));
+ ast_aoc_manager_event(decoded, owner);
+
+ ast_aoc_destroy_decoded(decoded);
+ ast_aoc_destroy_encoded(encoded);
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Handle the AOC-S event.
- * \since 1.8
- *
- * \param aoc_s AOC-S event parameters.
- * \param owner Asterisk channel associated with the call.
+ * \brief send an AOC-S message on the current call
*
- * \note Assumes the pri->lock is already obtained.
- * \note Assumes the owner channel lock is already obtained.
+ * \param pvt sig_pri private channel structure.
+ * \param generic decoded ast AOC message
*
* \return Nothing
+ *
+ * \note Assumes that the PRI lock is already obtained.
*/
-static void sig_pri_aoc_s_event(const struct pri_subcmd_aoc_s *aoc_s, struct ast_channel *owner)
+static void sig_pri_aoc_s_from_ast(struct sig_pri_chan *pvt, struct ast_aoc_decoded *decoded)
{
- struct ast_str *msg;
- const char *rate_str;
- char prefix[32];
+ struct pri_subcmd_aoc_s aoc_s = { 0, };
+ const struct ast_aoc_s_entry *entry;
int idx;
- msg = ast_str_create(4096);
- if (!msg) {
- return;
- }
-
- 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", aoc_s->num_items);
- for (idx = 0; idx < aoc_s->num_items; ++idx) {
- snprintf(prefix, sizeof(prefix), "Rate(%d)", idx);
-
- ast_str_append(&msg, 0, "%s/Chargeable: %s\r\n", prefix,
- sig_pri_aoc_charged_item_str(aoc_s->item[idx].chargeable));
- if (aoc_s->item[idx].chargeable == PRI_AOC_CHARGED_ITEM_NOT_AVAILABLE) {
- continue;
+ for (idx = 0; idx < ast_aoc_s_get_count(decoded); idx++) {
+ if (!(entry = ast_aoc_s_get_rate_info(decoded, idx))) {
+ break;
}
- rate_str = sig_pri_aoc_rate_type_str(aoc_s->item[idx].rate_type);
- ast_str_append(&msg, 0, "%s/Type: %s\r\n", prefix, rate_str);
- switch (aoc_s->item[idx].rate_type) {
- case PRI_AOC_RATE_TYPE_DURATION:
- strcat(prefix, "/");
- strcat(prefix, rate_str);
- ast_str_append(&msg, 0, "%s/Currency: %s\r\n", prefix,
- aoc_s->item[idx].rate.duration.currency);
- sig_pri_aoc_amount(&msg, prefix, &aoc_s->item[idx].rate.duration.amount);
- ast_str_append(&msg, 0, "%s/ChargingType: %s\r\n", prefix,
- aoc_s->item[idx].rate.duration.charging_type
- ? "StepFunction" : "ContinuousCharging");
- sig_pri_aoc_time(&msg, prefix, "Time", &aoc_s->item[idx].rate.duration.time);
- if (aoc_s->item[idx].rate.duration.granularity.length) {
- sig_pri_aoc_time(&msg, prefix, "Granularity",
- &aoc_s->item[idx].rate.duration.granularity);
+
+ aoc_s.item[idx].chargeable = sig_pri_aoc_charged_item_to_pri(entry->charged_item);
+
+ switch (entry->rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_DURATION;
+ aoc_s.item[idx].rate.duration.amount.cost = entry->rate.duration.amount;
+ aoc_s.item[idx].rate.duration.amount.multiplier =
+ sig_pri_aoc_multiplier_from_ast(entry->rate.duration.multiplier);
+ aoc_s.item[idx].rate.duration.time.length = entry->rate.duration.time;
+ aoc_s.item[idx].rate.duration.time.scale =
+ sig_pri_aoc_scale_to_pri(entry->rate.duration.time_scale);
+ aoc_s.item[idx].rate.duration.granularity.length = entry->rate.duration.granularity_time;
+ aoc_s.item[idx].rate.duration.granularity.scale =
+ sig_pri_aoc_scale_to_pri(entry->rate.duration.granularity_time_scale);
+ aoc_s.item[idx].rate.duration.charging_type = entry->rate.duration.charging_type;
+
+ if (!ast_strlen_zero(entry->rate.duration.currency_name)) {
+ ast_copy_string(aoc_s.item[idx].rate.duration.currency,
+ entry->rate.duration.currency_name,
+ sizeof(aoc_s.item[idx].rate.duration.currency));
}
break;
- case PRI_AOC_RATE_TYPE_FLAT:
- strcat(prefix, "/");
- strcat(prefix, rate_str);
- ast_str_append(&msg, 0, "%s/Currency: %s\r\n", prefix,
- aoc_s->item[idx].rate.flat.currency);
- sig_pri_aoc_amount(&msg, prefix, &aoc_s->item[idx].rate.flat.amount);
+ case AST_AOC_RATE_TYPE_FLAT:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_FLAT;
+ aoc_s.item[idx].rate.flat.amount.cost = entry->rate.flat.amount;
+ aoc_s.item[idx].rate.flat.amount.multiplier =
+ sig_pri_aoc_multiplier_from_ast(entry->rate.flat.multiplier);
+
+ if (!ast_strlen_zero(entry->rate.flat.currency_name)) {
+ ast_copy_string(aoc_s.item[idx].rate.flat.currency,
+ entry->rate.flat.currency_name,
+ sizeof(aoc_s.item[idx].rate.flat.currency));
+ }
break;
- case PRI_AOC_RATE_TYPE_VOLUME:
- strcat(prefix, "/");
- strcat(prefix, rate_str);
- ast_str_append(&msg, 0, "%s/Currency: %s\r\n", prefix,
- aoc_s->item[idx].rate.volume.currency);
- sig_pri_aoc_amount(&msg, prefix, &aoc_s->item[idx].rate.volume.amount);
- ast_str_append(&msg, 0, "%s/Unit: %s\r\n", prefix,
- sig_pri_aoc_volume_unit_str(aoc_s->item[idx].rate.volume.unit));
+ case AST_AOC_RATE_TYPE_VOLUME:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_VOLUME;
+ aoc_s.item[idx].rate.volume.unit = entry->rate.volume.volume_unit;
+ aoc_s.item[idx].rate.volume.amount.cost = entry->rate.volume.amount;
+ aoc_s.item[idx].rate.volume.amount.multiplier =
+ sig_pri_aoc_multiplier_from_ast(entry->rate.volume.multiplier);
+
+ if (!ast_strlen_zero(entry->rate.volume.currency_name)) {
+ ast_copy_string(aoc_s.item[idx].rate.volume.currency,
+ entry->rate.volume.currency_name,
+ sizeof(aoc_s.item[idx].rate.volume.currency));
+ }
break;
- case PRI_AOC_RATE_TYPE_SPECIAL_CODE:
- ast_str_append(&msg, 0, "%s/%s: %d\r\n", prefix, rate_str,
- aoc_s->item[idx].rate.special);
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_SPECIAL_CODE;
+ aoc_s.item[idx].rate.special = entry->rate.special_code;
+ break;
+ case AST_AOC_RATE_TYPE_FREE:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_FREE;
+ break;
+ case AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_FREE_FROM_BEGINNING;
break;
default:
+ case AST_AOC_RATE_TYPE_NA:
+ aoc_s.item[idx].rate_type = PRI_AOC_RATE_TYPE_NOT_AVAILABLE;
break;
}
}
+ aoc_s.num_items = idx;
- ast_manager_event(owner, EVENT_FLAG_AOC, "AOC-S", "%s", ast_str_buffer(msg));
- ast_free(msg);
+ /* if this rate should be sent as a response to an AOC-S request we will
+ * have an aoc_s_request_invoke_id associated with this pvt */
+ if (pvt->aoc_s_request_invoke_id_valid) {
+ pri_aoc_s_request_response_send(pvt->pri->pri, pvt->call, pvt->aoc_s_request_invoke_id, &aoc_s);
+ pvt->aoc_s_request_invoke_id_valid = 0;
+ } else {
+ pri_aoc_s_send(pvt->pri->pri, pvt->call, &aoc_s);
+ }
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Handle the AOC-D event.
- * \since 1.8
- *
- * \param aoc_d AOC-D event parameters.
- * \param owner Asterisk channel associated with the call.
+ * \brief send an AOC-D message on the current call
*
- * \note Assumes the pri->lock is already obtained.
- * \note Assumes the owner channel lock is already obtained.
+ * \param pvt sig_pri private channel structure.
+ * \param generic decoded ast AOC message
*
* \return Nothing
+ *
+ * \note Assumes that the PRI lock is already obtained.
*/
-static void sig_pri_aoc_d_event(const struct pri_subcmd_aoc_d *aoc_d, struct ast_channel *owner)
+static void sig_pri_aoc_d_from_ast(struct sig_pri_chan *pvt, struct ast_aoc_decoded *decoded)
{
- struct ast_str *msg;
- const char *charge_str;
- int idx;
- int num_items;
- char prefix[32];
+ struct pri_subcmd_aoc_d aoc_d = { 0, };
- msg = ast_str_create(4096);
- if (!msg) {
- return;
- }
+ aoc_d.billing_accumulation = (ast_aoc_get_total_type(decoded) == AST_AOC_TOTAL) ? 1 : 0;
- ast_str_append(&msg, 0, "Channel: %s\r\n", owner->name);
- ast_str_append(&msg, 0, "UniqueID: %s\r\n", owner->uniqueid);
-
- charge_str = sig_pri_aoc_de_charge_str(aoc_d->charge);
- ast_str_append(&msg, 0, "Type: %s\r\n", charge_str);
- switch (aoc_d->charge) {
- case PRI_AOC_DE_CHARGE_CURRENCY:
- case PRI_AOC_DE_CHARGE_UNITS:
- ast_str_append(&msg, 0, "BillingID: %s\r\n",
- sig_pri_aoc_d_billing_id_str(aoc_d->billing_id));
- ast_str_append(&msg, 0, "TypeOfCharging: %s\r\n",
- aoc_d->billing_accumulation ? "Total" : "SubTotal");
+ switch (ast_aoc_get_billing_id(decoded)) {
+ case AST_AOC_BILLING_NORMAL:
+ aoc_d.billing_id = PRI_AOC_D_BILLING_ID_NORMAL;
+ break;
+ case AST_AOC_BILLING_REVERSE_CHARGE:
+ aoc_d.billing_id = PRI_AOC_D_BILLING_ID_REVERSE;
break;
+ case AST_AOC_BILLING_CREDIT_CARD:
+ aoc_d.billing_id = PRI_AOC_D_BILLING_ID_CREDIT_CARD;
+ break;
+ case AST_AOC_BILLING_NA:
default:
+ aoc_d.billing_id = PRI_AOC_D_BILLING_ID_NOT_AVAILABLE;
break;
}
- switch (aoc_d->charge) {
- case PRI_AOC_DE_CHARGE_CURRENCY:
- ast_str_append(&msg, 0, "%s: %s\r\n", charge_str,
- aoc_d->recorded.money.currency);
- sig_pri_aoc_amount(&msg, charge_str, &aoc_d->recorded.money.amount);
+
+ switch (ast_aoc_get_charge_type(decoded)) {
+ case AST_AOC_CHARGE_FREE:
+ aoc_d.charge = PRI_AOC_DE_CHARGE_FREE;
break;
- case PRI_AOC_DE_CHARGE_UNITS:
- num_items = 0;
- for (idx = 0; idx < aoc_d->recorded.unit.num_items; ++idx) {
- if (0 <= aoc_d->recorded.unit.item[idx].number
- || 0 <= aoc_d->recorded.unit.item[idx].type) {
- /* Something is available at this index location so keep it. */
- ++num_items;
+ case AST_AOC_CHARGE_CURRENCY:
+ {
+ const char *currency_name = ast_aoc_get_currency_name(decoded);
+ aoc_d.charge = PRI_AOC_DE_CHARGE_CURRENCY;
+ aoc_d.recorded.money.amount.cost = ast_aoc_get_currency_amount(decoded);
+ aoc_d.recorded.money.amount.multiplier = sig_pri_aoc_multiplier_from_ast(ast_aoc_get_currency_multiplier(decoded));
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(aoc_d.recorded.money.currency, currency_name, sizeof(aoc_d.recorded.money.currency));
}
}
- ast_str_append(&msg, 0, "%s/NumberItems: %d\r\n", charge_str, num_items);
- num_items = 0;
- for (idx = 0; idx < aoc_d->recorded.unit.num_items; ++idx) {
- if (aoc_d->recorded.unit.item[idx].number < 0
- && aoc_d->recorded.unit.item[idx].type < 0) {
- /* Nothing is available at this index location so skip it. */
- continue;
- }
- snprintf(prefix, sizeof(prefix), "%s/Item(%d)", charge_str, num_items);
- ++num_items;
-
- if (0 <= aoc_d->recorded.unit.item[idx].number) {
- /* Number of units recorded is available */
- ast_str_append(&msg, 0, "%s/NumberOf: %ld\r\n", prefix,
- aoc_d->recorded.unit.item[idx].number);
- }
- if (0 <= aoc_d->recorded.unit.item[idx].type) {
- /* Type of units recorded is available */
- ast_str_append(&msg, 0, "%s/TypeOf: %d\r\n", prefix,
- aoc_d->recorded.unit.item[idx].type);
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ {
+ const struct ast_aoc_unit_entry *entry;
+ int i;
+ aoc_d.charge = PRI_AOC_DE_CHARGE_UNITS;
+ for (i = 0; i < ast_aoc_get_unit_count(decoded); i++) {
+ if ((entry = ast_aoc_get_unit_info(decoded, i)) && i < ARRAY_LEN(aoc_d.recorded.unit.item)) {
+ if (entry->valid_amount) {
+ aoc_d.recorded.unit.item[i].number = entry->amount;
+ } else {
+ aoc_d.recorded.unit.item[i].number = -1;
+ }
+ if (entry->valid_type) {
+ aoc_d.recorded.unit.item[i].type = entry->type;
+ } else {
+ aoc_d.recorded.unit.item[i].type = -1;
+ }
+ aoc_d.recorded.unit.num_items++;
+ } else {
+ break;
+ }
}
}
break;
+ case AST_AOC_CHARGE_NA:
default:
+ aoc_d.charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE;
break;
}
- ast_manager_event(owner, EVENT_FLAG_AOC, "AOC-D", "%s", ast_str_buffer(msg));
- ast_free(msg);
+ pri_aoc_d_send(pvt->pri->pri, pvt->call, &aoc_d);
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_AOC_EVENTS)
/*!
* \internal
- * \brief Handle the AOC-E event.
- * \since 1.8
+ * \brief send an AOC-E message on the current call
*
- * \param aoc_e AOC-E event parameters.
- * \param owner Asterisk channel associated with the call.
- * NULL if the event is not associated with an existing call.
- *
- * \note Assumes the pri->lock is already obtained.
- * \note Assumes the owner channel lock is already obtained if associated.
+ * \param pvt sig_pri private channel structure.
+ * \param generic decoded ast AOC message
*
* \return Nothing
+ *
+ * \note Assumes that the PRI lock is already obtained.
*/
-static void sig_pri_aoc_e_event(const struct pri_subcmd_aoc_e *aoc_e, struct ast_channel *owner)
+static void sig_pri_aoc_e_from_ast(struct sig_pri_chan *pvt, struct ast_aoc_decoded *decoded)
{
- struct ast_channel *chans[1];
- struct ast_str *msg;
- const char *charge_str;
- int idx;
- int num_items;
- char prefix[32];
-
- msg = ast_str_create(4096);
- if (!msg) {
- return;
- }
-
- if (owner) {
- ast_str_append(&msg, 0, "Channel: %s\r\n", owner->name);
- ast_str_append(&msg, 0, "UniqueID: %s\r\n", owner->uniqueid);
- }
-
- /* If there is no owner then there should be a charging association. */
- charge_str = "ChargingAssociation";
- switch (aoc_e->associated.charging_type) {
- case PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER:
- if (!aoc_e->associated.charge.number.valid) {
- break;
- }
- snprintf(prefix, sizeof(prefix), "%s/Number", charge_str);
- ast_str_append(&msg, 0, "%s: %s\r\n", prefix,
- aoc_e->associated.charge.number.str);
- ast_str_append(&msg, 0, "%s/Plan: %d\r\n", prefix,
- aoc_e->associated.charge.number.plan);
+ struct pri_subcmd_aoc_e *aoc_e = &pvt->aoc_e;
+ const struct ast_aoc_charging_association *ca = ast_aoc_get_association_info(decoded);
+
+ memset(aoc_e, 0, sizeof(*aoc_e));
+ pvt->holding_aoce = 1;
+
+ switch (ca->charging_type) {
+ case AST_AOC_CHARGING_ASSOCIATION_NUMBER:
+ aoc_e->associated.charge.number.valid = 1;
+ ast_copy_string(aoc_e->associated.charge.number.str,
+ ca->charge.number.number,
+ sizeof(aoc_e->associated.charge.number.str));
+ aoc_e->associated.charge.number.plan = ca->charge.number.plan;
+ aoc_e->associated.charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_NUMBER;
break;
- case PRI_AOC_E_CHARGING_ASSOCIATION_ID:
- ast_str_append(&msg, 0, "%s/ID: %d\r\n", charge_str, aoc_e->associated.charge.id);
+ case AST_AOC_CHARGING_ASSOCIATION_ID:
+ aoc_e->associated.charge.id = ca->charge.id;
+ aoc_e->associated.charging_type = PRI_AOC_E_CHARGING_ASSOCIATION_ID;
break;
+ case AST_AOC_CHARGING_ASSOCIATION_NA:
default:
break;
}
- charge_str = sig_pri_aoc_de_charge_str(aoc_e->charge);
- ast_str_append(&msg, 0, "Type: %s\r\n", charge_str);
- switch (aoc_e->charge) {
- case PRI_AOC_DE_CHARGE_CURRENCY:
- case PRI_AOC_DE_CHARGE_UNITS:
- ast_str_append(&msg, 0, "BillingID: %s\r\n",
- sig_pri_aoc_e_billing_id_str(aoc_e->billing_id));
+ switch (ast_aoc_get_billing_id(decoded)) {
+ case AST_AOC_BILLING_NORMAL:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_NORMAL;
+ break;
+ case AST_AOC_BILLING_REVERSE_CHARGE:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_REVERSE;
+ break;
+ case AST_AOC_BILLING_CREDIT_CARD:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CREDIT_CARD;
+ break;
+ case AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_UNCONDITIONAL;
+ break;
+ case AST_AOC_BILLING_CALL_FWD_BUSY:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_BUSY;
break;
+ case AST_AOC_BILLING_CALL_FWD_NO_REPLY:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_FORWARDING_NO_REPLY;
+ break;
+ case AST_AOC_BILLING_CALL_DEFLECTION:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_DEFLECTION;
+ break;
+ case AST_AOC_BILLING_CALL_TRANSFER:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_CALL_TRANSFER;
+ break;
+ case AST_AOC_BILLING_NA:
default:
+ aoc_e->billing_id = PRI_AOC_E_BILLING_ID_NOT_AVAILABLE;
break;
}
- switch (aoc_e->charge) {
- case PRI_AOC_DE_CHARGE_CURRENCY:
- ast_str_append(&msg, 0, "%s: %s\r\n", charge_str,
- aoc_e->recorded.money.currency);
- sig_pri_aoc_amount(&msg, charge_str, &aoc_e->recorded.money.amount);
+
+ switch (ast_aoc_get_charge_type(decoded)) {
+ case AST_AOC_CHARGE_FREE:
+ aoc_e->charge = PRI_AOC_DE_CHARGE_FREE;
break;
- case PRI_AOC_DE_CHARGE_UNITS:
- num_items = 0;
- for (idx = 0; idx < aoc_e->recorded.unit.num_items; ++idx) {
- if (0 <= aoc_e->recorded.unit.item[idx].number
- || 0 <= aoc_e->recorded.unit.item[idx].type) {
- /* Something is available at this index location so keep it. */
- ++num_items;
+ case AST_AOC_CHARGE_CURRENCY:
+ {
+ const char *currency_name = ast_aoc_get_currency_name(decoded);
+ aoc_e->charge = PRI_AOC_DE_CHARGE_CURRENCY;
+ aoc_e->recorded.money.amount.cost = ast_aoc_get_currency_amount(decoded);
+ aoc_e->recorded.money.amount.multiplier = sig_pri_aoc_multiplier_from_ast(ast_aoc_get_currency_multiplier(decoded));
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(aoc_e->recorded.money.currency, currency_name, sizeof(aoc_e->recorded.money.currency));
}
}
- ast_str_append(&msg, 0, "%s/NumberItems: %d\r\n", charge_str, num_items);
- num_items = 0;
- for (idx = 0; idx < aoc_e->recorded.unit.num_items; ++idx) {
- if (aoc_e->recorded.unit.item[idx].number < 0
- && aoc_e->recorded.unit.item[idx].type < 0) {
- /* Nothing is available at this index location so skip it. */
- continue;
- }
- snprintf(prefix, sizeof(prefix), "%s/Item(%d)", charge_str, num_items);
- ++num_items;
-
- if (0 <= aoc_e->recorded.unit.item[idx].number) {
- /* Number of units recorded is available */
- ast_str_append(&msg, 0, "%s/NumberOf: %ld\r\n", prefix,
- aoc_e->recorded.unit.item[idx].number);
- }
- if (0 <= aoc_e->recorded.unit.item[idx].type) {
- /* Type of units recorded is available */
- ast_str_append(&msg, 0, "%s/TypeOf: %d\r\n", prefix,
- aoc_e->recorded.unit.item[idx].type);
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ {
+ const struct ast_aoc_unit_entry *entry;
+ int i;
+ aoc_e->charge = PRI_AOC_DE_CHARGE_UNITS;
+ for (i = 0; i < ast_aoc_get_unit_count(decoded); i++) {
+ if ((entry = ast_aoc_get_unit_info(decoded, i)) && i < ARRAY_LEN(aoc_e->recorded.unit.item)) {
+ if (entry->valid_amount) {
+ aoc_e->recorded.unit.item[i].number = entry->amount;
+ } else {
+ aoc_e->recorded.unit.item[i].number = -1;
+ }
+ if (entry->valid_type) {
+ aoc_e->recorded.unit.item[i].type = entry->type;
+ } else {
+ aoc_e->recorded.unit.item[i].type = -1;
+ }
+ aoc_e->recorded.unit.num_items++;
+ }
}
}
break;
+ case AST_AOC_CHARGE_NA:
default:
+ aoc_e->charge = PRI_AOC_DE_CHARGE_NOT_AVAILABLE;
break;
}
+}
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
+#if defined(HAVE_PRI_AOC_EVENTS)
+/*!
+ * \internal
+ * \brief send an AOC-E termination request on ast_channel and set
+ * hangup delay.
+ *
+ * \param sig_pri_chan private
+ * \param ms to delay hangup
+ *
+ * \note assumes pvt is locked
+ *
+ * \return Nothing
+ */
+static void sig_pri_send_aoce_termination_request(struct sig_pri_chan *pvt, unsigned int ms)
+{
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t encoded_size;
+ struct timeval whentohangup = { 0, };
+
+ if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, AST_AOC_REQUEST_E))) {
+ ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV);
+ goto cleanup_termination_request;
+ }
- chans[0] = owner;
- ast_manager_event_multichan(EVENT_FLAG_AOC, "AOC-E", owner ? 1 : 0, chans, "%s",
- ast_str_buffer(msg));
- ast_free(msg);
+ ast_aoc_set_termination_request(decoded);
+
+ if (!(encoded = ast_aoc_encode(decoded, &encoded_size, pvt->owner))) {
+ ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV);
+ goto cleanup_termination_request;
+ }
+
+ /* convert ms to timeval */
+ whentohangup.tv_usec = (ms % 1000) * 1000;
+ whentohangup.tv_sec = ms / 1000;
+
+ if (ast_queue_control_data(pvt->owner, AST_CONTROL_AOC, encoded, encoded_size)) {
+ ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV);
+ goto cleanup_termination_request;
+ }
+
+ pvt->waiting_for_aoce = 1;
+ ast_channel_setwhentohangup_tv(pvt->owner, whentohangup);
+ ast_log(LOG_DEBUG, "Delaying hangup on %s for aoc-e msg\n", pvt->owner->name);
+
+cleanup_termination_request:
+ ast_aoc_destroy_decoded(decoded);
+ ast_aoc_destroy_encoded(encoded);
}
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
@@ -2916,7 +3218,8 @@ static void sig_pri_handle_cis_subcmds(struct sig_pri_pri *pri, int event_id,
#endif /* defined(HAVE_PRI_CCSS) */
#if defined(HAVE_PRI_AOC_EVENTS)
case PRI_SUBCMD_AOC_E:
- sig_pri_aoc_e_event(&subcmd->u.aoc_e, NULL);
+ /* Queue AST_CONTROL_AOC frame */
+ sig_pri_aoc_e_from_pri(&subcmd->u.aoc_e, NULL, 0);
break;
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
default:
@@ -2928,6 +3231,43 @@ static void sig_pri_handle_cis_subcmds(struct sig_pri_pri *pri, int event_id,
}
}
+#if defined(HAVE_PRI_AOC_EVENTS)
+/*!
+ * \internal
+ * \brief detect if AOC-S subcmd is present.
+ * \since 1.8
+ *
+ * \param subcmds Subcommands to process if any. (Could be NULL).
+ *
+ * \note Knowing whether or not an AOC-E subcmd is present on certain
+ * PRI hangup events is necessary to determine what method to use to hangup
+ * the ast_channel. If an AOC-E subcmd just came in, then a new AOC-E was queued
+ * on the ast_channel. If a soft hangup is used, the AOC-E msg will never make it
+ * across the bridge, but if a AST_CONTROL_HANGUP frame is queued behind it
+ * we can ensure the AOC-E frame makes it to it's destination before the hangup
+ * frame is read.
+ *
+ *
+ * \retval 0 AOC-E is not present in subcmd list
+ * \retval 1 AOC-E is present in subcmd list
+ */
+static int detect_aoc_e_subcmd(const struct pri_subcommands *subcmds)
+{
+ int i;
+
+ if (!subcmds) {
+ return 0;
+ }
+ for (i = 0; i < subcmds->counter_subcmd; ++i) {
+ const struct pri_subcommand *subcmd = &subcmds->subcmd[i];
+ if (subcmd->cmd == PRI_SUBCMD_AOC_E) {
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
/*!
* \internal
* \brief Handle the call associated PRI subcommand events.
@@ -3179,7 +3519,7 @@ static void sig_pri_handle_subcmds(struct sig_pri_pri *pri, int chanpos, int eve
sig_pri_lock_owner(pri, chanpos);
owner = pri->pvts[chanpos]->owner;
if (owner) {
- sig_pri_aoc_s_event(&subcmd->u.aoc_s, owner);
+ sig_pri_aoc_s_from_pri(&subcmd->u.aoc_s, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S));
ast_channel_unlock(owner);
}
break;
@@ -3189,7 +3529,8 @@ static void sig_pri_handle_subcmds(struct sig_pri_pri *pri, int chanpos, int eve
sig_pri_lock_owner(pri, chanpos);
owner = pri->pvts[chanpos]->owner;
if (owner) {
- sig_pri_aoc_d_event(&subcmd->u.aoc_d, owner);
+ /* Queue AST_CONTROL_AOC frame on channel */
+ sig_pri_aoc_d_from_pri(&subcmd->u.aoc_d, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D));
ast_channel_unlock(owner);
}
break;
@@ -3198,12 +3539,37 @@ static void sig_pri_handle_subcmds(struct sig_pri_pri *pri, int chanpos, int eve
case PRI_SUBCMD_AOC_E:
sig_pri_lock_owner(pri, chanpos);
owner = pri->pvts[chanpos]->owner;
- sig_pri_aoc_e_event(&subcmd->u.aoc_e, owner);
+ /* Queue AST_CONTROL_AOC frame */
+ sig_pri_aoc_e_from_pri(&subcmd->u.aoc_e, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E));
if (owner) {
ast_channel_unlock(owner);
}
break;
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+#if defined(HAVE_PRI_AOC_EVENTS)
+ case PRI_SUBCMD_AOC_CHARGING_REQ:
+ sig_pri_lock_owner(pri, chanpos);
+ owner = pri->pvts[chanpos]->owner;
+ if (owner) {
+ sig_pri_aoc_request_from_pri(&subcmd->u.aoc_request, pri->pvts[chanpos], call_rsp);
+ ast_channel_unlock(owner);
+ }
+ break;
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+#if defined(HAVE_PRI_AOC_EVENTS)
+ case PRI_SUBCMD_AOC_CHARGING_REQ_RSP:
+ /* An AOC request response may contain an AOC-S rate list. If this is the case handle this just like we
+ * would an incoming AOC-S msg */
+ if (subcmd->u.aoc_request_response.valid_aoc_s) {
+ sig_pri_lock_owner(pri, chanpos);
+ owner = pri->pvts[chanpos]->owner;
+ if (owner) {
+ sig_pri_aoc_s_from_pri(&subcmd->u.aoc_request_response.aoc_s, owner, (pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S));
+ ast_channel_unlock(owner);
+ }
+ }
+ break;
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
default:
ast_debug(2,
"Unknown call subcommand(%d) in %s event on channel %d/%d on span %d.\n",
@@ -4390,12 +4756,13 @@ static void *pri_dchannel(void *vpri)
break;
}
if (pri->pvts[chanpos]->owner) {
+ int do_hangup = 0;
/* Queue a BUSY instead of a hangup if our cause is appropriate */
pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause;
switch (pri->pvts[chanpos]->owner->_state) {
case AST_STATE_BUSY:
case AST_STATE_UP:
- pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ do_hangup = 1;
break;
default:
switch (e->hangup.cause) {
@@ -4411,11 +4778,25 @@ static void *pri_dchannel(void *vpri)
pri_queue_control(pri, chanpos, AST_CONTROL_CONGESTION);
break;
default:
- pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ do_hangup = 1;
break;
}
break;
}
+
+ if (do_hangup) {
+#if defined(HAVE_PRI_AOC_EVENTS)
+ if (detect_aoc_e_subcmd(e->hangup.subcmds)) {
+ /* If a AOC-E msg was sent during the release, we must use a
+ * AST_CONTROL_HANGUP frame to guarantee that frame gets read before hangup */
+ ast_queue_control(pri->pvts[chanpos]->owner, AST_CONTROL_HANGUP);
+ } else {
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+#else
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+ }
} else {
/*
* Continue hanging up the call even though
@@ -4499,6 +4880,7 @@ static void *pri_dchannel(void *vpri)
sig_pri_lock_private(pri->pvts[chanpos]);
}
#endif /* defined(HAVE_PRI_CALL_HOLD) */
+
switch (e->hangup.cause) {
case PRI_CAUSE_USER_BUSY:
case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION:
@@ -4508,11 +4890,13 @@ static void *pri_dchannel(void *vpri)
break;
}
if (pri->pvts[chanpos]->owner) {
+ int do_hangup = 0;
+
pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause;
switch (pri->pvts[chanpos]->owner->_state) {
case AST_STATE_BUSY:
case AST_STATE_UP:
- pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ do_hangup = 1;
break;
default:
switch (e->hangup.cause) {
@@ -4528,15 +4912,29 @@ static void *pri_dchannel(void *vpri)
pri_queue_control(pri, chanpos, AST_CONTROL_CONGESTION);
break;
default:
- pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ do_hangup = 1;
break;
}
break;
}
- ast_verb(3, "Channel %d/%d, span %d got hangup request, cause %d\n", PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span, e->hangup.cause);
- if (e->hangup.aoc_units > -1)
- ast_verb(3, "Channel %d/%d, span %d received AOC-E charging %d unit%s\n",
- pri->pvts[chanpos]->logicalspan, pri->pvts[chanpos]->prioffset, pri->span, (int)e->hangup.aoc_units, (e->hangup.aoc_units == 1) ? "" : "s");
+
+ if (do_hangup) {
+#if defined(HAVE_PRI_AOC_EVENTS)
+ if (!pri->pvts[chanpos]->holding_aoce && pri->aoce_delayhangup && ast_bridged_channel(pri->pvts[chanpos]->owner)) {
+ sig_pri_send_aoce_termination_request(pri->pvts[chanpos], pri_get_timer(pri->pri, PRI_TIMER_T305) / 2);
+ } else if (detect_aoc_e_subcmd(e->hangup.subcmds)) {
+ /* If a AOC-E msg was sent during the Disconnect, we must use a AST_CONTROL_HANGUP frame
+ * to guarantee that frame gets read before hangup */
+ ast_queue_control(pri->pvts[chanpos]->owner, AST_CONTROL_HANGUP);
+ } else {
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ }
+#else
+ pri->pvts[chanpos]->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+ }
+ ast_verb(3, "Channel %d/%d, span %d got hangup request, cause %d\n",
+ PRI_SPAN(e->hangup.channel), PRI_CHANNEL(e->hangup.channel), pri->span, e->hangup.cause);
} else {
/*
* Continue hanging up the call even though
@@ -4829,6 +5227,11 @@ int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast)
pri_call_set_useruser(p->call, useruser);
#endif
+#if defined(HAVE_PRI_AOC_EVENTS)
+ if (p->holding_aoce) {
+ pri_aoc_e_send(p->pri->pri, p->call, &p->aoc_e);
+ }
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
pri_hangup(p->pri->pri, p->call, -1);
p->call = NULL;
} else {
@@ -4845,6 +5248,13 @@ int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast)
if (atoi(cause))
icause = atoi(cause);
}
+
+#if defined(HAVE_PRI_AOC_EVENTS)
+ if (p->holding_aoce) {
+ pri_aoc_e_send(p->pri->pri, p->call, &p->aoc_e);
+ }
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
pri_hangup(p->pri->pri, p->call, icause);
}
}
@@ -4856,6 +5266,12 @@ int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast)
res = -1;
}
+#if defined(HAVE_PRI_AOC_EVENTS)
+ p->aoc_s_request_invoke_id_valid = 0;
+ p->holding_aoce = 0;
+ p->waiting_for_aoce = 0;
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
ast->tech_pvt = NULL;
return res;
}
@@ -4935,10 +5351,11 @@ void sig_pri_extract_called_num_subaddr(struct sig_pri_chan *p, const char *rdes
enum SIG_PRI_CALL_OPT_FLAGS {
OPT_KEYPAD = (1 << 0),
OPT_REVERSE_CHARGE = (1 << 1), /* Collect call */
+ OPT_AOC_REQUEST = (1 << 2), /* AOC Request */
};
enum SIG_PRI_CALL_OPT_ARGS {
OPT_ARG_KEYPAD = 0,
-
+ OPT_ARG_AOC_REQUEST,
/* note: this entry _MUST_ be the last one in the enum */
OPT_ARG_ARRAY_SIZE,
};
@@ -4946,6 +5363,7 @@ enum SIG_PRI_CALL_OPT_ARGS {
AST_APP_OPTIONS(sig_pri_call_opts, BEGIN_OPTIONS
AST_APP_OPTION_ARG('K', OPT_KEYPAD, OPT_ARG_KEYPAD),
AST_APP_OPTION('R', OPT_REVERSE_CHARGE),
+ AST_APP_OPTION_ARG('A', OPT_AOC_REQUEST, OPT_ARG_AOC_REQUEST),
END_OPTIONS);
/*! \note Parsing must remain in sync with sig_pri_extract_called_num_subaddr(). */
@@ -5149,6 +5567,21 @@ int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, char *rdest, i
}
c++;
}
+
+#if defined(HAVE_PRI_AOC_EVENTS)
+ if (ast_test_flag(&opts, OPT_AOC_REQUEST) && !ast_strlen_zero(opt_args[OPT_ARG_AOC_REQUEST])) {
+ if (strchr(opt_args[OPT_ARG_AOC_REQUEST], 's')) {
+ pri_sr_set_aoc_charging_request(sr, PRI_AOC_REQUEST_S);
+ }
+ if (strchr(opt_args[OPT_ARG_AOC_REQUEST], 'd')) {
+ pri_sr_set_aoc_charging_request(sr, PRI_AOC_REQUEST_D);
+ }
+ if (strchr(opt_args[OPT_ARG_AOC_REQUEST], 'e')) {
+ pri_sr_set_aoc_charging_request(sr, PRI_AOC_REQUEST_E);
+ }
+ }
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
#if defined(HAVE_PRI_SETUP_KEYPAD)
if (ast_test_flag(&opts, OPT_KEYPAD)
&& !ast_strlen_zero(opt_args[OPT_ARG_KEYPAD])) {
@@ -5474,6 +5907,53 @@ int sig_pri_indicate(struct sig_pri_chan *p, struct ast_channel *chan, int condi
pri_rel(p->pri);
}
break;
+ case AST_CONTROL_AOC:
+#if defined(HAVE_PRI_AOC_EVENTS)
+ {
+ struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, chan);
+ ast_debug(1, "Received AST_CONTROL_AOC on %s\n", chan->name);
+ if (decoded && p->pri && !pri_grab(p, p->pri)) {
+ switch (ast_aoc_get_msg_type(decoded)) {
+ case AST_AOC_S:
+ if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S) {
+ sig_pri_aoc_s_from_ast(p, decoded);
+ }
+ break;
+ case AST_AOC_D:
+ if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D) {
+ sig_pri_aoc_d_from_ast(p, decoded);
+ }
+ break;
+ case AST_AOC_E:
+ if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E) {
+ sig_pri_aoc_e_from_ast(p, decoded);
+ }
+ /* if hangup was delayed for this AOC-E msg, waiting_for_aoc
+ * will be set. A hangup is already occuring via a timeout during
+ * this delay. Instead of waiting for that timeout to occur, go ahead
+ * and initiate the softhangup since the delay is no longer necessary */
+ if (p->waiting_for_aoce) {
+ p->waiting_for_aoce = 0;
+ ast_log(LOG_DEBUG, "Received final AOC-E msg, continue with hangup on %s\n", chan->name);
+ ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV);
+ }
+ break;
+ case AST_AOC_REQUEST:
+ /* We do not pass through AOC requests, So unless this
+ * is an AOC termination request it will be ignored */
+ if (ast_aoc_get_termination_request(decoded)) {
+ pri_hangup(p->pri->pri, p->call, -1);
+ }
+ break;
+ default:
+ break;
+ }
+ pri_rel(p->pri);
+ }
+ ast_aoc_destroy_decoded(decoded);
+ }
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+ break;
}
return res;
@@ -5484,6 +5964,15 @@ int sig_pri_answer(struct sig_pri_chan *p, struct ast_channel *ast)
int res = 0;
/* Send a pri acknowledge */
if (!pri_grab(p, p->pri)) {
+#if defined(HAVE_PRI_AOC_EVENTS)
+ if (p->aoc_s_request_invoke_id_valid) {
+ /* if AOC-S was requested and the invoke id is still present on answer. That means
+ * no AOC-S rate list was provided, so send a NULL response which will indicate that
+ * AOC-S is not available */
+ pri_aoc_s_request_response_send(p->pri->pri, p->call, p->aoc_s_request_invoke_id, NULL);
+ p->aoc_s_request_invoke_id_valid = 0;
+ }
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
p->proceeding = 1;
sig_pri_set_dialing(p, 0);
res = pri_answer(p->pri->pri, p->call, 0, !p->digital);
diff --git a/channels/sig_pri.h b/channels/sig_pri.h
index efd3523e5..64c915b2a 100644
--- a/channels/sig_pri.h
+++ b/channels/sig_pri.h
@@ -31,6 +31,10 @@
#include <libpri.h>
#include <dahdi/user.h>
+#define SIG_PRI_AOC_GRANT_S (1 << 0)
+#define SIG_PRI_AOC_GRANT_D (1 << 1)
+#define SIG_PRI_AOC_GRANT_E (1 << 2)
+
#if defined(HAVE_PRI_CCSS)
/*! PRI debug message flags when normal PRI debugging is turned on at the command line. */
#define SIG_PRI_DEBUG_NORMAL \
@@ -192,6 +196,13 @@ struct sig_pri_chan {
char keypad_digits[AST_MAX_EXTENSION];
#endif /* defined(HAVE_PRI_SETUP_KEYPAD) */
+#if defined(HAVE_PRI_AOC_EVENTS)
+ struct pri_subcmd_aoc_e aoc_e;
+ int aoc_s_request_invoke_id; /*!< If an AOC-S request was present for the call, this is the invoke_id to use for the response */
+ unsigned int aoc_s_request_invoke_id_valid:1; /*!< This is set when the AOC-S invoke id is present */
+ unsigned int waiting_for_aoce:1; /*!< Delaying hangup for AOC-E msg. If this is set and AOC-E is recieved, continue with hangup before timeout period. */
+ unsigned int holding_aoce:1; /*!< received AOC-E msg from asterisk. holding for disconnect/release */
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
unsigned int inalarm:1;
unsigned int alerting:1; /*!< TRUE if channel is alerting/ringing */
unsigned int alreadyhungup:1; /*!< TRUE if the call has already gone/hungup */
@@ -243,6 +254,12 @@ struct sig_pri_pri {
int facilityenable; /*!< Enable facility IEs */
int dchan_logical_span[SIG_PRI_NUM_DCHANS]; /*!< Logical offset the DCHAN sits in */
int fds[SIG_PRI_NUM_DCHANS]; /*!< FD's for d-channels */
+
+#if defined(HAVE_PRI_AOC_EVENTS)
+ int aoc_passthrough_flag; /*!< Represents what AOC messages (S,D,E) are allowed to pass-through */
+ int aoce_delayhangup:1; /*!< defines whether the aoce_delayhangup option is enabled or not */
+#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+
#if defined(HAVE_PRI_SERVICE_MESSAGES)
unsigned int enable_service_message_support:1; /*!< enable SERVICE message support */
#endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index c52875436..0ee377c32 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -354,6 +354,12 @@
SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE | SIP_PAGE2_SYMMETRICRTP |\
SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT)
+
+#define SIP_PAGE3_SNOM_AOC (1 << 0) /*!< DPG: Allow snom aoc messages */
+
+#define SIP_PAGE3_FLAGS_TO_COPY \
+ (SIP_PAGE3_SNOM_AOC)
+
/*@}*/
/*----------------------------------------------------------*/
@@ -943,7 +949,7 @@ struct sip_pvt {
ast_group_t callgroup; /*!< Call group */
ast_group_t pickupgroup; /*!< Pickup group */
int lastinvite; /*!< Last Cseq of invite */
- struct ast_flags flags[2]; /*!< SIP_ flags */
+ struct ast_flags flags[3]; /*!< SIP_ flags */
/* boolean flags that don't belong in flags */
unsigned short do_history:1; /*!< Set if we want to record history */
@@ -1172,7 +1178,7 @@ struct sip_peer {
struct ast_codec_pref prefs; /*!< codec prefs */
int lastmsgssent;
unsigned int sipoptions; /*!< Supported SIP options */
- struct ast_flags flags[2]; /*!< SIP_ flags */
+ struct ast_flags flags[3]; /*!< SIP_ flags */
/*! Mailboxes that this peer cares about */
AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes;
diff --git a/configs/chan_dahdi.conf.sample b/configs/chan_dahdi.conf.sample
index ac4008d71..57b0fe0da 100644
--- a/configs/chan_dahdi.conf.sample
+++ b/configs/chan_dahdi.conf.sample
@@ -289,6 +289,24 @@
;
;facilityenable = yes
;
+
+; This option enables Advice of Charge pass-through between the ISDN PRI and
+; Asterisk. This option can be set to any combination of 's', 'd', and 'e' which
+; represent the different variants of Advice of Charge, AOC-S, AOC-D, and AOC-E.
+; Advice of Charge pass-through is currently only supported for ETSI. Since most
+; AOC messages are sent on facility messages, the 'facilityenable' option must
+; also be enabled to fully support AOC pass-through.
+;
+;aoc_enable=s,d,e
+;
+; When this option is enabled, a hangup initiated by the ISDN PRI side of the
+; asterisk channel will result in the channel delaying its hangup in an
+; attempt to receive the final AOC-E message from its bridge. The delay
+; period is configured as one half the T305 timer length. If the channel
+; is not bridged the hangup will occur immediatly without delay.
+;
+;aoce_delayhangup=yes
+
; pritimer cannot be changed on a reload.
;
; Signalling method. The default is "auto". Valid values:
diff --git a/configs/manager.conf.sample b/configs/manager.conf.sample
index 366685499..c6536bc82 100644
--- a/configs/manager.conf.sample
+++ b/configs/manager.conf.sample
@@ -107,7 +107,8 @@ bindaddr = 0.0.0.0
; originate - Permission to originate new calls. Write-only.
; agi - Output AGI commands executed. Input AGI command to execute.
; cc - Call Completion events. Read-only.
-; aoc - Advice Of Charge events. Read-only.
+; aoc - Permission to send Advice Of Charge messages and receive Advice
+; - Of Charge events.
;
;read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
;write = system,call,agent,user,config,command,reporting,originate
diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample
index a9ed6fad9..d8ae62da6 100644
--- a/configs/sip.conf.sample
+++ b/configs/sip.conf.sample
@@ -886,6 +886,12 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; destinations which do not have a prior
; account relationship with your server.
+;------------------------------ Advice of Charge CONFIGURATION --------------------------
+; snom_aoc_enabled = yes; ; This options turns on and off support for sending AOC-D and
+ ; AOC-E to snom endpoints. This option can be used both in the
+ ; peer and global scope. The default for this option is off.
+
+
;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of a
; SIP channel. Defaults to "no". An enabled jitterbuffer will
diff --git a/doc/advice_of_charge.txt b/doc/advice_of_charge.txt
new file mode 100644
index 000000000..bb090ec59
--- /dev/null
+++ b/doc/advice_of_charge.txt
@@ -0,0 +1,180 @@
+================
+Advice of Charge
+================
+
+Written by: David Vossel
+Initial version: 04-19-2010
+
+This document is designed to give an overview of how to configure and
+generate Advice of Charge along with a detailed explanation of how each
+option works.
+
+--------------------------------------
+| Terminology |
+--------------------------------------
+AOC: Advice of Charge
+
+AOC-S: Advice of Charge message sent at the beginning of a call during
+call setup. This message contains a list of rates associated with the
+call.
+
+AOC-D: Advice of Charge message sent during the call. This message
+is typically used to update the endpoint with the current call charge.
+
+AOC-E: Advice of Charge message sent at the end of a call. This
+message is used to indicate to the endpoint the final call charge.
+
+AMI: Asterisk Manager Interface. This interface is used to generate
+AOC messages and listen for AOC events.
+
+--------------------------------------
+| AOC in chan_dahdi |
+--------------------------------------
+----- LibPRI Support:
+ETSI, or euroisdn, is the only switchtype that LibPRI currently supports
+for AOC.
+
+----- Enable AOC Pass-through in chan_dahdi
+To enable AOC pass-through between the ISDN and Asterisk use the
+'aoc_enable' config option. This option allows for any combination
+of AOC-S, AOC-D, and AOC-E to be enabled or disabled.
+
+For example:
+aoc_enable=s,d,e ; enables pass-through of AOC-S, AOC-D, and AOC-E
+
+aoc_enable=s,d ; enables pass-through of AOC-S and AOC-D. Rejects
+ ; AOC-E and AOC-E request messages
+
+Since AOC messages are often transported on facility messages, the
+'facilityenable' option must be enabled as well to fully support AOC
+pass-through.
+
+----- Handling AOC-E in chan_dahdi
+Whenever a dahdi channel receives an AOC-E message from Asterisk, it
+stores that message to deliver it at the appropriate time during call
+termination. This means that if two AOC-E messages are received on the
+same call, the last one will override the first one and only one AOC-E
+message will be sent during call termination.
+
+There are some tricky situations involving the final AOC-E message. During
+a bridged call, if the endpoint receiving the AOC messages terminates
+the call before the endpoint delivering the AOC does, the final AOC-E
+message sent by the sending side during termination will never make it to
+the receiving end because Asterisk will have already torn down that channel.
+This is where the chan_dahdi.conf 'aoce_delayhangup' option comes into play.
+
+By enabling 'aoce_delayhangup', anytime a hangup is initiated by the
+ISDN side of an Asterisk channel, instead of hanging up the channel,
+the channel sends a unique internal AOC-E termination request to its bridge
+channel. This indicates it is about to hangup and wishes to receive the
+final AOC-E message from the bridged channel before completely tearing
+down. If the bridged channel knows what to do with this AOC-E termination
+request, it will do whatever is necessary to indicate to its endpoint that
+the call is being terminated without actually hanging up the Asterisk channel.
+This allows the final AOC-E message to come in and be sent across the bridge
+while both channels are still up. If the channel delaying its hangup for
+the final AOC-E message times out, the call will be torn down just as it
+normally would. In chan_dahdi the timeout period is 1/2 the T305 timer
+which by default is 15 seconds.
+
+'aoce_delayhangup' currently only works when both bridged channels are
+dahdi_channels. If a SIP channel receives an AOC-E termination request, it
+just responds by immediately hanging up the channel. Using this option when
+bridged to any channel technology besides SIP or DAHDI will result in the
+15 second timeout period before tearing down the call completely.
+
+----- Requesting AOC services
+AOC can be requested on a call by call basis using the DAHDI dialstring
+option, A(). The A() option takes in 's', 'd', and 'e' parameters which
+represent the three types of AOC messages, AOC-S, AOC-D, and AOC-E. By using
+this option Asterisk will indicate to the endpoint during call setup that it
+wishes to receive the specified forms of AOC during the call.
+
+Example Usage in extensions.conf
+exten => 1111,1,Dial(DAHDI/g1/1112/A(s,d,e) ; requests AOC-S, AOC-D, and AOC-E on
+ ; call setup
+exten => 1111,1,Dial(DAHDI/g1/1112/A(d,e) ; requests only AOC-D, and AOC-E on
+ ; call setup
+
+--------------------------------------
+| AOC in chan_sip |
+--------------------------------------
+Asterisk supports a very basic way of sending AOC on a SIP channel to Snom
+phones using an AOC specification designed by Snom. This support is limited
+to the sending of AOC-D and AOC-E pass-through messages. No support for
+AOC-E on call termination is present, so if the Snom endpoint receiving the
+AOC messages from Asterisk terminates the call, the channel will be torn
+down before the phone can receive the final AOC-E message.
+
+To enable passthrough of AOC messages via the snom specification, use
+the 'snom_aoc_enabled' option in sip.conf.
+
+--------------------------------------
+| Generate AOC Messages via AMI |
+--------------------------------------
+Asterisk supports a way to generate AOC messages on a channel via
+the AMI action AOCMessage. At the moment the AOCMessage action is limited
+to AOC-D and AOC-E message generation. There are some limitations
+involved with delivering the final AOC-E message as well. The AOCMessage
+action has its own detailed parameter documentation so this discussion will
+focus on higher level use. When generating AOC messages on a Dahdi channel
+first make sure the appropriate chan_dahdi.conf options are enabled. Without
+enabling 'aoc_enable' correctly for pass-through the AOC messages will never
+make it out the pri. The same goes with SIP, the 'snom_aoc_enabled' option
+must be configured before messages can successfully be set to the endpoint.
+
+----- AOC-D Message Generation
+AOC-D message generation can happen anytime throughout the call. This
+message type is very straight forward.
+
+Example: AOCMessage action generating AOC-D currency message with Success
+response.
+
+Action: AOCMessage
+Channel: DAHDI/i1/1111-1
+MsgType: d
+ChargeType: Currency
+CurrencyAmount: 16
+CurrencyName: USD
+CurrencyMultiplier: OneThousandth
+AOCBillingId: Normal
+ActionID: 1234
+
+Response: Success
+ActionID: 1234
+Message: AOC Message successfully queued on channel
+
+----- AOC-E Message Generation
+AOC-E messages are sent during call termination and represent the final charge
+total for the call. Since Asterisk call termination results in the channel
+being destroyed, it is currently not possible for the AOCMessage AMI action to
+be used to send the final AOC-E message on call hangup. There is however a
+work around for this issue that can be used for Dahdi channels. By default
+chan_dahdi saves any AOC-E message it receives from Asterisk during a call and
+waits to deliver that message during call termination. If multiple AOC-E messages
+are received from Asterisk on the same Dahdi channel, only the last message received
+is stored for delivery. This means that each new AOC-E message received on the
+channel overrides the previous one. Knowing this the final AOC-E message can be
+continually updated on a Dahdi channel until call termination occurs allowing
+the last update to be sent on hangup. This method is only as accurate as the
+intervals in which it is updated, but allows some form of AOC-E to be generated.
+
+Example: AOCMessage action generating AOC-E unit message with Success response.
+
+Action: AOCMessage
+Channel: DAHDI/i1/1111-1
+MsgType: e
+ChargeType: Unit
+UnitAmount(0): 111
+UnitType(0): 6
+UnitAmount(1): 222
+UnitType(1): 5
+UnitAmount(2): 333
+UnitType(3): 4
+UnitAmount(4): 444
+AOCBillingId: Normal
+ActionID: 1234
+
+Response: Success
+ActionID: 1234
+Message: AOC Message successfully queued on channel
diff --git a/include/asterisk/aoc.h b/include/asterisk/aoc.h
new file mode 100644
index 000000000..727362c1f
--- /dev/null
+++ b/include/asterisk/aoc.h
@@ -0,0 +1,584 @@
+/*
+ * 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 Advice of Charge encode and decode routines
+ *
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#ifndef _AST_AOC_H_
+#define _AST_AOC_H_
+
+#include "asterisk/channel.h"
+
+#define AOC_CURRENCY_NAME_SIZE (10 + 1)
+
+/*! \brief Defines the currency multiplier for an aoc message. */
+enum ast_aoc_currency_multiplier {
+ AST_AOC_MULT_ONETHOUSANDTH = 1,
+ AST_AOC_MULT_ONEHUNDREDTH,
+ AST_AOC_MULT_ONETENTH,
+ AST_AOC_MULT_ONE,
+ AST_AOC_MULT_TEN,
+ AST_AOC_MULT_HUNDRED,
+ AST_AOC_MULT_THOUSAND,
+ AST_AOC_MULT_NUM_ENTRIES, /* must remain the last item in enum, this is not a valid type */
+};
+
+/*!
+ * \brief Defines the billing id options for an aoc message.
+ * \note AOC-D is limited to NORMAL, REVERSE_CHARGE, and CREDIT_CARD.
+ */
+enum ast_aoc_billing_id {
+ AST_AOC_BILLING_NA = 0,
+ AST_AOC_BILLING_NORMAL,
+ AST_AOC_BILLING_REVERSE_CHARGE,
+ AST_AOC_BILLING_CREDIT_CARD,
+ AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL,
+ AST_AOC_BILLING_CALL_FWD_BUSY,
+ AST_AOC_BILLING_CALL_FWD_NO_REPLY,
+ AST_AOC_BILLING_CALL_DEFLECTION,
+ AST_AOC_BILLING_CALL_TRANSFER,
+ AST_AOC_BILLING_NUM_ENTRIES /* must remain the last item in enum, not a valid billing id */
+};
+
+enum ast_aoc_type {
+ AST_AOC_REQUEST = 0,
+ AST_AOC_S,
+ AST_AOC_D,
+ AST_AOC_E, /* aoc-e must remain the last item in this enum */
+};
+
+enum ast_aoc_charge_type {
+ AST_AOC_CHARGE_NA = 0,
+ AST_AOC_CHARGE_FREE,
+ AST_AOC_CHARGE_CURRENCY,
+ AST_AOC_CHARGE_UNIT, /* unit must remain the last item in enum */
+};
+
+enum ast_aoc_request {
+ AST_AOC_REQUEST_S = (1 << 0),
+ AST_AOC_REQUEST_D = (1 << 1),
+ AST_AOC_REQUEST_E = (1 << 2),
+};
+
+enum ast_aoc_total_type {
+ AST_AOC_TOTAL = 0,
+ AST_AOC_SUBTOTAL = 1,
+};
+
+enum ast_aoc_time_scale {
+ AST_AOC_TIME_SCALE_HUNDREDTH_SECOND,
+ AST_AOC_TIME_SCALE_TENTH_SECOND,
+ AST_AOC_TIME_SCALE_SECOND,
+ AST_AOC_TIME_SCALE_TEN_SECOND,
+ AST_AOC_TIME_SCALE_MINUTE,
+ AST_AOC_TIME_SCALE_HOUR,
+ AST_AOC_TIME_SCALE_DAY,
+};
+
+struct ast_aoc_time {
+ /*! LengthOfTimeUnit (Not valid if length is zero.) */
+ uint32_t length;
+ uint16_t scale;
+};
+
+struct ast_aoc_duration_rate {
+ uint32_t amount;
+ uint32_t time;
+ /*! Not present if the granularity time is zero. */
+ uint32_t granularity_time;
+
+ uint16_t multiplier;
+ uint16_t time_scale;
+ uint16_t granularity_time_scale;
+
+ /*! Name of currency involved. Null terminated. */
+ char currency_name[AOC_CURRENCY_NAME_SIZE];
+
+ /*!
+ * \brief Charging interval type
+ * \details
+ * continuousCharging(0),
+ * stepFunction(1)
+ */
+ uint8_t charging_type;
+};
+
+enum ast_aoc_volume_unit {
+ AST_AOC_VOLUME_UNIT_OCTET,
+ AST_AOC_VOLUME_UNIT_SEGMENT,
+ AST_AOC_VOLUME_UNIT_MESSAGE,
+};
+
+struct ast_aoc_volume_rate {
+ uint32_t amount;
+ uint16_t multiplier;
+ uint16_t volume_unit;
+ char currency_name[AOC_CURRENCY_NAME_SIZE];
+};
+
+struct ast_aoc_flat_rate {
+ uint32_t amount;
+ uint16_t multiplier;
+ /*! Name of currency involved. Null terminated. */
+ char currency_name[AOC_CURRENCY_NAME_SIZE];
+};
+
+enum ast_aoc_s_charged_item {
+ AST_AOC_CHARGED_ITEM_NA,
+ AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT,
+ AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION,
+ AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
+ AST_AOC_CHARGED_ITEM_CALL_SETUP,
+ AST_AOC_CHARGED_ITEM_USER_USER_INFO,
+ AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE,
+};
+
+enum ast_aoc_s_rate_type {
+ AST_AOC_RATE_TYPE_NA,
+ AST_AOC_RATE_TYPE_FREE,
+ AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING,
+ AST_AOC_RATE_TYPE_DURATION,
+ AST_AOC_RATE_TYPE_FLAT,
+ AST_AOC_RATE_TYPE_VOLUME,
+ AST_AOC_RATE_TYPE_SPECIAL_CODE,
+};
+
+struct ast_aoc_s_entry {
+ uint16_t charged_item;
+ uint16_t rate_type;
+
+ /*! \brief Charge rate being applied. */
+ union {
+ struct ast_aoc_duration_rate duration;
+ struct ast_aoc_flat_rate flat;
+ struct ast_aoc_volume_rate volume;
+ uint16_t special_code; /* 1...10 */
+ } rate;
+} __attribute__((packed));
+
+struct ast_aoc_unit_entry {
+ char valid_amount;
+ unsigned int amount;
+ char valid_type;
+ unsigned int type; /* 1 - 16 by ETSI standard */
+};
+
+enum AST_AOC_CHARGING_ASSOCIATION {
+ AST_AOC_CHARGING_ASSOCIATION_NA,
+ AST_AOC_CHARGING_ASSOCIATION_NUMBER,
+ AST_AOC_CHARGING_ASSOCIATION_ID,
+};
+struct ast_aoc_charging_association_number {
+ uint8_t plan;
+ char number[32];
+} __attribute__((packed));
+struct ast_aoc_charging_association {
+ union {
+ int32_t id;
+ struct ast_aoc_charging_association_number number;
+ } charge;
+ /*! \see enum AST_AOC_CHARGING_ASSOCIATION */
+ uint8_t charging_type;
+} __attribute__((packed));
+
+/*! \brief AOC Payload Header. Holds all the encoded AOC data to pass on the wire */
+struct ast_aoc_encoded;
+
+/*! \brief Decoded AOC data. This value is used to set all the values in an AOC message before encoding.*/
+struct ast_aoc_decoded;
+
+/*!
+ * \brief creates a ast_aoc_decode object of a specific message type
+ * \since 1.8
+ *
+ * \param msg_type AOC-D, AOC-E, or AOC Request
+ * \param charge_type this is ignored if message type is not AOC-D or AOC-E.
+ * \param requests flags. This defines the types of AOC requested. This
+ * field should only be set when the message type is AOC Request,
+ * the value is ignored otherwise.
+ *
+ * \retval heap allocated ast_aoc_decoded object ptr on success
+ * \retval NULL failure
+ */
+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);
+
+
+/*! \brief free an ast_aoc_decoded object */
+void *ast_aoc_destroy_decoded(struct ast_aoc_decoded *decoded);
+
+/*! \brief free an ast_aoc_encoded object */
+void *ast_aoc_destroy_encoded(struct ast_aoc_encoded *encoded);
+
+/*!
+ * \brief decodes an encoded aoc payload.
+ * \since 1.8
+ *
+ * \param encoded the encoded payload to decode.
+ * \param size total size of encoded payload
+ * \param chan ast channel, Optional for DEBUG output purposes
+ *
+ * \retval heap allocated ast_aoc_decoded object ptr on success
+ * \retval NULL failure
+ */
+struct ast_aoc_decoded *ast_aoc_decode(struct ast_aoc_encoded *encoded, size_t size, struct ast_channel *chan);
+
+/*!
+ * \brief encodes a decoded aoc structure so it can be passed on the wire
+ * \since 1.8
+ *
+ * \param decoded the decoded struct to be encoded
+ * \param out_size output parameter representing size of encoded data
+ * \param chan ast channel, Optional for DEBUG output purposes
+ *
+ * \retval pointer to encoded data
+ * \retval NULL failure
+ */
+struct ast_aoc_encoded *ast_aoc_encode(struct ast_aoc_decoded *decoded, size_t *out_size, struct ast_channel *chan);
+
+/*!
+ * \brief Sets the type of total for a AOC-D message
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ * \param type total type: TOTAL or SUBTOTAL
+ *
+ * \note If this value is not set, the default for the message is TOTAL
+ *
+ * \retval 0 success
+ */
+int ast_aoc_set_total_type(struct ast_aoc_decoded *decoded, const enum ast_aoc_total_type type);
+
+/*!
+ * \brief Sets the currency values for a AOC-D or AOC-E message
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ * \param amount currency amount REQUIRED
+ * \param multiplier currency multiplier REQUIRED, 0 or undefined value defaults to AST_AOC_MULT_ONE.
+ * \param name currency name OPTIONAL
+ *
+ * \retval 0 success
+ */
+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);
+
+/*!
+ * \brief Adds a unit entry into the list of units
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ * \param amount_is_present set this if the number of units is actually present.
+ * \param amount number of units
+ * \param type_is_present set this if the type value is present
+ * \param type unit type
+ *
+ * \note If neither the amount nor the type is present, the entry will
+ * not be added.
+ *
+ * \retval 0 success
+ */
+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);
+
+/*!
+ * \brief set the billing id for a AOC-D or AST_AOC_E message
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ * \param id billing id
+ *
+ * \retval 0 success
+ */
+int ast_aoc_set_billing_id(struct ast_aoc_decoded *decoded, const enum ast_aoc_billing_id id);
+
+/*!
+ * \brief set the charging association id for an AST_AOC_E message
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ * \param id charging association identifier
+ *
+ * \note If the association number was set, this will override that value. Only the id OR the
+ * number can be set at a time, not both.
+ *
+ * \retval 0 success
+ */
+int ast_aoc_set_association_id(struct ast_aoc_decoded *decoded, const int id);
+
+/*!
+ * \brief set the charging accociation number for an AOC-E message
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ * \param num charging association number
+ * \param plan charging association number plan and type-of-number fields
+ *
+ * \note If the association id was set, this will override that value. Only the id OR the
+ * number can be set at a time, not both.
+ *
+ * \retval 0 success
+ */
+int ast_aoc_set_association_number(struct ast_aoc_decoded *decoded, const char *num, uint8_t plan);
+
+/*!
+ * \brief Mark the AST_AOC_REQUEST message as a termination request.
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to set values on
+ *
+ * \note A termination request indicates that the call has terminated,
+ * but that the other side is waiting for a short period of time before
+ * hanging up so it can get the final AOC-E message.
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_aoc_set_termination_request(struct ast_aoc_decoded *decoded);
+
+/*!
+ * \brief Add AOC-S duration rate entry
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param charged_item ast_aoc_s_charged_item
+ * \param amount currency amount
+ * \param multiplier currency multiplier
+ * \param currency_name truncated after 10 characters
+ * \param time
+ * \param time_scale from ast_aoc_time_scale enum
+ * \param granularity_time (optional, set to 0 if not present);
+ * \param granularity_time_scale (optional, set to 0 if not present);
+ * \param step_function set to 1 if this is to use a step function, 0 if continuious
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+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);
+
+/*!
+ * \brief Add AOC-S flat rate entry
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param charged_item ast_aoc_s_charged_item
+ * \param amount currency amount
+ * \param multiplier currency multiplier
+ * \param currency_name truncated after 10 characters
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+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);
+
+/*!
+ * \brief Add AOC-S volume rate entry
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param charged_item ast_aoc_s_charged_item
+ * \param volume_unit from ast_aoc_volume_unit enum
+ * \param amount currency amount
+ * \param multiplier currency multiplier
+ * \param currency_name truncated after 10 characters
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+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);
+
+/*!
+ * \brief Add AOC-S special rate entry
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param charged_item ast_aoc_s_charged_item
+ * \param code special charging code
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+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);
+
+/*!
+ * \brief Add AOC-S indicating charge item is free
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param charged_item ast_aoc_s_charged_item
+ * \param from_beginning TRUE if the rate is free from beginning.
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_aoc_s_add_rate_free(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item, int from_beginning);
+
+/*!
+ * \brief Add AOC-S entry indicating charge item is not available
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param charged_item ast_aoc_s_charged_item
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_aoc_s_add_rate_na(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item);
+
+/*!
+ * \brief Add AOC-S special arrangement entry
+ * \since 1.8
+ *
+ * \param decoded aoc decoded object to add entry to
+ * \param code special arrangement code
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_aoc_s_add_special_arrangement(struct ast_aoc_decoded *decoded,
+ unsigned int code);
+
+/*!
+ * \brief Convert decoded aoc msg to string representation
+ * \since 1.8
+ *
+ * \param decoded ast_aoc_decoded struct to convert to string
+ * \param msg dynamic heap allocated ast_str object to store string representation in
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_aoc_decoded2str(const struct ast_aoc_decoded *decoded, struct ast_str **msg);
+
+/*! \brief generate AOC manager event for an AOC-S, AOC-D, or AOC-E msg */
+int ast_aoc_manager_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan);
+
+/*! \brief get the message type, AOC-D, AOC-E, or AOC Request */
+enum ast_aoc_type ast_aoc_get_msg_type(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the charging type for an AOC-D or AOC-E message */
+enum ast_aoc_charge_type ast_aoc_get_charge_type(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the types of AOC requested for when message type is AOC Request */
+enum ast_aoc_request ast_aoc_get_request(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the type of total for a AOC-D message */
+enum ast_aoc_total_type ast_aoc_get_total_type(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the currency amount for AOC-D and AOC-E messages*/
+unsigned int ast_aoc_get_currency_amount(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the number rates associated with an AOC-S message */
+unsigned int ast_aoc_s_get_count(struct ast_aoc_decoded *decoded);
+
+/*!
+ * \brief get a specific AOC-S rate entry.
+ * \since 1.8
+ *
+ * \note This can be used in conjunction with ast_aoc_s_get_count to create
+ * a unit entry iterator.
+ */
+const struct ast_aoc_s_entry *ast_aoc_s_get_rate_info(struct ast_aoc_decoded *decoded, unsigned int entry_number);
+
+/*! \brief get the number of unit entries for AOC-D and AOC-E messages*/
+unsigned int ast_aoc_get_unit_count(struct ast_aoc_decoded *decoded);
+
+/*!
+ * \brief get a specific unit entry.
+ * \since 1.8
+ *
+ * \note This can be used in conjunction with ast_aoc_get_unit_count to create
+ * a unit entry iterator.
+ */
+const struct ast_aoc_unit_entry *ast_aoc_get_unit_info(struct ast_aoc_decoded *decoded, unsigned int entry_number);
+
+/*! \brief get the currency multiplier for AOC-D and AOC-E messages */
+enum ast_aoc_currency_multiplier ast_aoc_get_currency_multiplier(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the currency multiplier for AOC-D and AOC-E messages in decimal format */
+const char *ast_aoc_get_currency_multiplier_decimal(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the currency name for AOC-D and AOC-E messages*/
+const char *ast_aoc_get_currency_name(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the billing id for AOC-D and AOC-E messages*/
+enum ast_aoc_billing_id ast_aoc_get_billing_id(struct ast_aoc_decoded *decoded);
+
+/*! \brief get the charging association info for AOC-E messages*/
+const struct ast_aoc_charging_association *ast_aoc_get_association_info(struct ast_aoc_decoded *decoded);
+
+/*!
+ * \brief get whether or not the AST_AOC_REQUEST message as a termination request.
+ * \since 1.8
+ *
+ * \note a termination request indicates that the call has terminated,
+ * but that the other side is waiting for a short period of time
+ * before hanging up so it can get the final AOC-E message.
+ *
+ * \param decoded ast_aoc_decoded struct to get values on
+ *
+ * \retval 0 not a termination request
+ * \retval 1 is a termination request
+ */
+int ast_aoc_get_termination_request(struct ast_aoc_decoded *decoded);
+
+/*!
+ * \brief test aoc encode decode routines.
+ * \since 1.8
+ *
+ * \note This function verifies that a decoded message matches itself after
+ * the encode decode routine.
+ */
+int ast_aoc_test_encode_decode_match(struct ast_aoc_decoded *decoded);
+
+/*! \brief enable aoc cli options */
+int ast_aoc_cli_init(void);
+
+#endif /* _AST_AOC_H_ */
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index 4198fad79..bfd92fa2d 100644
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -327,6 +327,7 @@ enum ast_control_frame_type {
AST_CONTROL_CC = 25, /*!< Indication that Call completion service is possible */
AST_CONTROL_SRCCHANGE = 26, /*!< Media source has changed and requires a new RTP SSRC */
AST_CONTROL_READ_ACTION = 27, /*!< Tell ast_read to take a specific action */
+ AST_CONTROL_AOC = 28, /*!< Advice of Charge with encoded generic AOC payload */
};
enum ast_frame_read_action {
diff --git a/main/aoc.c b/main/aoc.c
new file mode 100644
index 000000000..c7cc8cf2f
--- /dev/null
+++ b/main/aoc.c
@@ -0,0 +1,1607 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * David Vossel <dvossel@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief generic AOC payload generation encoding and decoding
+ *
+ * \author David Vossel <dvossel@digium.com>
+ */
+
+#include "asterisk.h"
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
+
+#include "asterisk/aoc.h"
+#include "asterisk/utils.h"
+#include "asterisk/strings.h"
+#include "asterisk/_private.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+
+/* Encoded Payload Flags */
+#define AST_AOC_ENCODED_TYPE_REQUEST (0 << 0)
+#define AST_AOC_ENCODED_TYPE_D (1 << 0)
+#define AST_AOC_ENCODED_TYPE_E (2 << 0)
+#define AST_AOC_ENCODED_TYPE_S (3 << 0)
+
+#define AST_AOC_ENCODED_REQUEST_S (1 << 2)
+#define AST_AOC_ENCODED_REQUEST_D (1 << 3)
+#define AST_AOC_ENCODED_REQUEST_E (1 << 4)
+
+#define AST_AOC_ENCODED_CHARGE_NA (0 << 5)
+#define AST_AOC_ENCODED_CHARGE_FREE (1 << 5)
+#define AST_AOC_ENCODED_CHARGE_CURRENCY (2 << 5)
+#define AST_AOC_ENCODED_CHARGE_UNIT (3 << 5)
+
+#define AST_AOC_ENCODED_CHARGE_SUBTOTAL (1 << 7)
+#define AST_AOC_ENCODED_CHARGE_TOTAL (0 << 7)
+
+#define AST_AOC_ENCODE_VERSION 1
+
+
+static char aoc_debug_enabled = 0;
+static void aoc_display_decoded_debug(const struct ast_aoc_decoded *decoded, int decoding, struct ast_channel *chan);
+static int aoc_s_add_entry(struct ast_aoc_decoded *decoded, struct ast_aoc_s_entry *entry);
+
+/* AOC Payload Header. Holds all the encoded AOC data to pass on the wire */
+struct ast_aoc_encoded {
+ uint8_t version;
+ uint8_t flags;
+ uint16_t datalen;
+ unsigned char data[0];
+};
+
+/* Decoded AOC data */
+struct ast_aoc_decoded {
+ enum ast_aoc_type msg_type;
+ enum ast_aoc_charge_type charge_type;
+ enum ast_aoc_request request_flag;
+ enum ast_aoc_total_type total_type;
+
+ /* currency information */
+ enum ast_aoc_currency_multiplier multiplier;
+ unsigned int currency_amount;
+ char currency_name[AOC_CURRENCY_NAME_SIZE];
+
+ /* unit information */
+ int unit_count;
+ struct ast_aoc_unit_entry unit_list[32];
+
+ /* Billing Id */
+ enum ast_aoc_billing_id billing_id;
+
+ /* Charging Association information */
+ struct ast_aoc_charging_association charging_association;
+
+ /* AOC-S charge information */
+ int aoc_s_count;
+ struct ast_aoc_s_entry aoc_s_entries[10];
+
+ /* Is this an AOC Termination Request */
+ char termination_request;
+};
+
+/*! \brief AOC Payload Information Elements */
+enum AOC_IE {
+ AOC_IE_CURRENCY = 1,
+ AOC_IE_UNIT = 2,
+ AOC_IE_BILLING = 3,
+ AOC_IE_CHARGING_ASSOCIATION = 4,
+ AOC_IE_RATE = 5,
+ AOC_IE_TERMINATION_REQUEST = 6,
+};
+
+/*! \brief AOC IE payload header */
+struct aoc_pl_ie_hdr {
+ uint8_t ie_id;
+ uint8_t datalen;
+ char data[0];
+} __attribute__((packed));
+
+struct aoc_ie_currency {
+ uint32_t amount;
+ uint8_t multiplier;
+ char name[AOC_CURRENCY_NAME_SIZE];
+} __attribute__((packed));
+
+struct aoc_ie_unit {
+ uint32_t amount;
+ uint8_t valid_type;
+ uint8_t valid_amount;
+ uint8_t type;
+} __attribute__((packed));
+
+struct aoc_ie_billing {
+ uint8_t id;
+} __attribute__((packed));
+
+struct aoc_ie_charging_association {
+ struct ast_aoc_charging_association ca;
+} __attribute__((packed));
+
+struct aoc_ie_charging_rate {
+ struct ast_aoc_s_entry entry;
+} __attribute__((packed));
+
+struct ast_aoc_decoded *ast_aoc_create(const enum ast_aoc_type msg_type,
+ const enum ast_aoc_charge_type charge_type,
+ const enum ast_aoc_request requests)
+{
+ struct ast_aoc_decoded *decoded = NULL;
+
+ /* verify input */
+ if (((unsigned int) charge_type > AST_AOC_CHARGE_UNIT) ||
+ ((unsigned int) msg_type > AST_AOC_E) ||
+ ((msg_type == AST_AOC_REQUEST) && !requests)) {
+
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_decoded object, invalid input\n");
+ return NULL;
+ }
+
+ if (!(decoded = ast_calloc(1, sizeof(struct ast_aoc_decoded)))) {
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_decoded object \n");
+ return NULL;
+ }
+
+ decoded->msg_type = msg_type;
+
+ if (msg_type == AST_AOC_REQUEST) {
+ decoded->request_flag = requests;
+ } else if ((msg_type == AST_AOC_D) || (msg_type == AST_AOC_E)) {
+ decoded->charge_type = charge_type;
+ }
+
+ return decoded;
+}
+
+void *ast_aoc_destroy_decoded(struct ast_aoc_decoded *decoded)
+{
+ ast_free(decoded);
+ return NULL;
+}
+
+void *ast_aoc_destroy_encoded(struct ast_aoc_encoded *encoded)
+{
+ ast_free(encoded);
+ return NULL;
+}
+
+static void aoc_parse_ie_charging_rate(struct ast_aoc_decoded *decoded, const struct aoc_ie_charging_rate *ie)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = ntohs(ie->entry.charged_item);
+ entry.rate_type = ntohs(ie->entry.rate_type);
+
+ switch (entry.rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ entry.rate.duration.multiplier = ntohs(ie->entry.rate.duration.multiplier);
+ entry.rate.duration.amount = ntohl(ie->entry.rate.duration.amount);
+ entry.rate.duration.time = ntohl(ie->entry.rate.duration.time);
+ entry.rate.duration.time_scale = ntohs(ie->entry.rate.duration.time_scale);
+ entry.rate.duration.granularity_time = ntohl(ie->entry.rate.duration.granularity_time);
+ entry.rate.duration.granularity_time_scale = ntohs(ie->entry.rate.duration.granularity_time_scale);
+ entry.rate.duration.charging_type = ie->entry.rate.duration.charging_type; /* only one byte */
+
+ if (!ast_strlen_zero(ie->entry.rate.duration.currency_name)) {
+ ast_copy_string(entry.rate.duration.currency_name,
+ ie->entry.rate.duration.currency_name,
+ sizeof(entry.rate.duration.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ entry.rate.flat.multiplier = ntohs(ie->entry.rate.flat.multiplier);
+ entry.rate.flat.amount = ntohl(ie->entry.rate.flat.amount);
+ if (!ast_strlen_zero(ie->entry.rate.flat.currency_name)) {
+ ast_copy_string(entry.rate.flat.currency_name,
+ ie->entry.rate.flat.currency_name,
+ sizeof(entry.rate.flat.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ entry.rate.volume.multiplier = ntohs(ie->entry.rate.volume.multiplier);
+ entry.rate.volume.amount = ntohl(ie->entry.rate.volume.amount);
+ entry.rate.volume.volume_unit = ntohs(ie->entry.rate.volume.volume_unit);
+ if (!ast_strlen_zero(ie->entry.rate.volume.currency_name)) {
+ ast_copy_string(entry.rate.volume.currency_name,
+ ie->entry.rate.volume.currency_name,
+ sizeof(entry.rate.volume.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ entry.rate.special_code = ntohs(ie->entry.rate.special_code);
+ break;
+ }
+
+ aoc_s_add_entry(decoded, &entry);
+}
+
+static int aoc_parse_ie(struct ast_aoc_decoded *decoded, unsigned char *data, unsigned int datalen)
+{
+ enum AOC_IE ie_id;
+ unsigned int len;
+
+ while (datalen >= 2) {
+ ie_id = data[0];
+ len = data[1];
+ if (len > datalen -2) {
+ ast_log(LOG_ERROR, "AOC information element length exceeds the total message size\n");
+ return -1;
+ }
+
+ switch(ie_id) {
+ case AOC_IE_CURRENCY:
+ if (len == sizeof(struct aoc_ie_currency)) {
+ struct aoc_ie_currency ie;
+ memcpy(&ie, data + 2, len);
+ decoded->currency_amount = ntohl(ie.amount);
+ decoded->multiplier = ie.multiplier; /* only one byte */
+ memcpy(decoded->currency_name, ie.name, sizeof(decoded->currency_name));
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid currency ie\n");
+ }
+ break;
+ case AOC_IE_UNIT:
+ if (len == sizeof(struct aoc_ie_unit)) {
+ struct aoc_ie_unit ie;
+ memcpy(&ie, data + 2, len);
+ ast_aoc_add_unit_entry(decoded, ie.valid_amount, ntohl(ie.amount), ie.valid_type, ie.type);
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid unit ie\n");
+ }
+ break;
+ case AOC_IE_BILLING:
+ if (len == sizeof(struct aoc_ie_billing)) {
+ struct aoc_ie_billing ie;
+ memcpy(&ie, data + 2, len);
+ decoded->billing_id = ie.id; /* only one byte */
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid billing ie\n");
+ }
+ break;
+ case AOC_IE_CHARGING_ASSOCIATION:
+ if (len == sizeof(struct aoc_ie_charging_association)) {
+ memcpy(&decoded->charging_association, data + 2, sizeof(decoded->charging_association));
+ /* everything in the charging_association struct is a single byte except for the id */
+ if (decoded->charging_association.charging_type == AST_AOC_CHARGING_ASSOCIATION_ID) {
+ decoded->charging_association.charge.id = ntohl(decoded->charging_association.charge.id);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid charging association ie\n");
+ }
+ break;
+ case AOC_IE_RATE:
+ if (len == sizeof(struct aoc_ie_charging_rate)) {
+ struct aoc_ie_charging_rate ie;
+ memcpy(&ie, data + 2, len);
+ aoc_parse_ie_charging_rate(decoded, &ie);
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid charging rate ie\n");
+ }
+ break;
+ case AOC_IE_TERMINATION_REQUEST:
+ if (len == 0) {
+ decoded->termination_request = 1;
+ } else {
+ ast_log(LOG_WARNING, "Recieved invalid termination request ie\n");
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Unknown AOC Information Element, ignoring.\n");
+ }
+
+ datalen -= (len + 2);
+ data += (len + 2);
+ }
+ return 0;
+}
+
+struct ast_aoc_decoded *ast_aoc_decode(struct ast_aoc_encoded *encoded, size_t size, struct ast_channel *chan)
+{
+ struct ast_aoc_decoded *decoded;
+
+ /* verify our encoded payload is actually large enough to hold all the ies */
+ if ((size - (sizeof(struct ast_aoc_encoded)) != ntohs(encoded->datalen))) {
+ ast_log(LOG_WARNING, "Corrupted aoc encoded object, can not decode\n");
+ return NULL;
+ }
+
+ if (!(decoded = ast_calloc(1, sizeof(struct ast_aoc_decoded)))) {
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_decoded object \n");
+ return NULL;
+ }
+
+ /* decode flags */
+
+ if ((encoded->flags & AST_AOC_ENCODED_TYPE_S) == AST_AOC_ENCODED_TYPE_S) {
+ decoded->msg_type = AST_AOC_S;
+ } else if (encoded->flags & AST_AOC_ENCODED_TYPE_E) {
+ decoded->msg_type = AST_AOC_E;
+ } else if (encoded->flags & AST_AOC_ENCODED_TYPE_D) {
+ decoded->msg_type = AST_AOC_D;
+ } else {
+ decoded->msg_type = AST_AOC_REQUEST;
+ }
+
+ if (decoded->msg_type == AST_AOC_REQUEST) {
+ if (encoded->flags & AST_AOC_ENCODED_REQUEST_S) {
+ decoded->request_flag |= AST_AOC_REQUEST_S;
+ }
+ if (encoded->flags & AST_AOC_ENCODED_REQUEST_D) {
+ decoded->request_flag |= AST_AOC_REQUEST_D;
+ }
+ if (encoded->flags & AST_AOC_ENCODED_REQUEST_E) {
+ decoded->request_flag |= AST_AOC_REQUEST_E;
+ }
+ } else if ((decoded->msg_type == AST_AOC_D) || (decoded->msg_type == AST_AOC_E)) {
+ if ((encoded->flags & AST_AOC_ENCODED_CHARGE_UNIT) == AST_AOC_ENCODED_CHARGE_UNIT) {
+ decoded->charge_type = AST_AOC_CHARGE_UNIT;
+ } else if ((encoded->flags & AST_AOC_ENCODED_CHARGE_CURRENCY) == AST_AOC_ENCODED_CHARGE_CURRENCY) {
+ decoded->charge_type = AST_AOC_CHARGE_CURRENCY;
+ } else if ((encoded->flags & AST_AOC_ENCODED_CHARGE_FREE) == AST_AOC_ENCODED_CHARGE_FREE) {
+ decoded->charge_type = AST_AOC_CHARGE_FREE;
+ } else {
+ decoded->charge_type = AST_AOC_CHARGE_NA;
+ }
+
+ if (encoded->flags & AST_AOC_ENCODED_CHARGE_SUBTOTAL) {
+ decoded->total_type = AST_AOC_SUBTOTAL;
+ }
+ }
+
+ /* decode information elements */
+ aoc_parse_ie(decoded, encoded->data, ntohs(encoded->datalen));
+
+ if (aoc_debug_enabled) {
+ aoc_display_decoded_debug(decoded, 1, chan);
+ }
+
+ return decoded;
+}
+
+struct aoc_ie_data {
+ unsigned char buf[1024];
+ int pos;
+};
+
+/*!
+ * \internal
+ * \brief append an AOC information element
+ * \note data is expected to already be in network byte order at this point
+ */
+static int aoc_append_ie(struct aoc_ie_data *ied, unsigned short ie_id, const void *data, unsigned short datalen)
+{
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ ast_log(LOG_WARNING, "Failure to append AOC information element, out of space \n");
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie_id;
+ ied->buf[ied->pos++] = datalen;
+ if (datalen) {
+ memcpy(ied->buf + ied->pos, data, datalen);
+ ied->pos += datalen;
+ }
+ return 0;
+}
+
+static void aoc_create_ie_data_charging_rate(const struct ast_aoc_s_entry *entry, struct aoc_ie_charging_rate *ie)
+{
+ ie->entry.charged_item = htons(entry->charged_item);
+ ie->entry.rate_type = htons(entry->rate_type);
+
+ switch (entry->rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ ie->entry.rate.duration.multiplier = htons(entry->rate.duration.multiplier);
+ ie->entry.rate.duration.amount = htonl(entry->rate.duration.amount);
+ ie->entry.rate.duration.time = htonl(entry->rate.duration.time);
+ ie->entry.rate.duration.time_scale = htons(entry->rate.duration.time_scale);
+ ie->entry.rate.duration.granularity_time = htonl(entry->rate.duration.granularity_time);
+ ie->entry.rate.duration.granularity_time_scale = htons(entry->rate.duration.granularity_time_scale);
+ ie->entry.rate.duration.charging_type = entry->rate.duration.charging_type; /* only one byte */
+
+ if (!ast_strlen_zero(entry->rate.duration.currency_name)) {
+ ast_copy_string(ie->entry.rate.duration.currency_name,
+ entry->rate.duration.currency_name,
+ sizeof(ie->entry.rate.duration.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ ie->entry.rate.flat.multiplier = htons(entry->rate.flat.multiplier);
+ ie->entry.rate.flat.amount = htonl(entry->rate.flat.amount);
+ if (!ast_strlen_zero(entry->rate.flat.currency_name)) {
+ ast_copy_string(ie->entry.rate.flat.currency_name,
+ entry->rate.flat.currency_name,
+ sizeof(ie->entry.rate.flat.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ ie->entry.rate.volume.multiplier = htons(entry->rate.volume.multiplier);
+ ie->entry.rate.volume.amount = htonl(entry->rate.volume.amount);
+ ie->entry.rate.volume.volume_unit = htons(entry->rate.volume.volume_unit);
+ if (!ast_strlen_zero(entry->rate.volume.currency_name)) {
+ ast_copy_string(ie->entry.rate.volume.currency_name,
+ entry->rate.volume.currency_name,
+ sizeof(ie->entry.rate.volume.currency_name));
+ }
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ ie->entry.rate.special_code = htons(entry->rate.special_code);
+ break;
+ }
+
+}
+static void aoc_create_ie_data(struct ast_aoc_decoded *decoded, struct aoc_ie_data *ied)
+{
+ ied->pos = 0;
+
+ if (decoded->currency_amount) {
+ struct aoc_ie_currency ie = {
+ .amount = htonl(decoded->currency_amount),
+ .multiplier = decoded->multiplier, /* only one byte */
+ .name = { 0, },
+ };
+
+ if (!ast_strlen_zero(decoded->currency_name)) {
+ ast_copy_string(ie.name, decoded->currency_name, sizeof(ie.name));
+ }
+
+ aoc_append_ie(ied, AOC_IE_CURRENCY, (const void *) &ie, sizeof(ie));
+ }
+
+ if (decoded->unit_count) {
+ struct aoc_ie_unit ie = { 0 };
+ int i;
+
+ for (i = 0; i < decoded->unit_count; i++) {
+ ie.valid_amount = decoded->unit_list[i].valid_amount; /* only one byte */
+ ie.amount = htonl(decoded->unit_list[i].amount);
+ ie.valid_type = decoded->unit_list[i].valid_type; /* only one byte */
+ ie.type = decoded->unit_list[i].type; /* only one byte */
+ aoc_append_ie(ied, AOC_IE_UNIT, (const void *) &ie, sizeof(ie));
+ }
+ }
+
+ if (decoded->billing_id) {
+ struct aoc_ie_billing ie;
+ ie.id = decoded->billing_id; /* only one byte */
+ aoc_append_ie(ied, AOC_IE_BILLING, (const void *) &ie, sizeof(ie));
+ }
+
+ if (decoded->charging_association.charging_type != AST_AOC_CHARGING_ASSOCIATION_NA) {
+ struct aoc_ie_charging_association ie;
+ memset(&ie, 0, sizeof(ie));
+ ie.ca.charging_type = decoded->charging_association.charging_type; /* only one byte */
+ if (decoded->charging_association.charging_type == AST_AOC_CHARGING_ASSOCIATION_NUMBER) {
+ ie.ca.charge.number.plan = decoded->charging_association.charge.number.plan; /* only one byte */
+ ast_copy_string(ie.ca.charge.number.number,
+ decoded->charging_association.charge.number.number,
+ sizeof(ie.ca.charge.number.number));
+ } else if (decoded->charging_association.charging_type == AST_AOC_CHARGING_ASSOCIATION_ID) {
+ ie.ca.charge.id = htonl(decoded->charging_association.charge.id);
+ }
+ aoc_append_ie(ied, AOC_IE_CHARGING_ASSOCIATION, (const void *) &ie, sizeof(ie));
+ }
+
+ if (decoded->aoc_s_count) {
+ struct aoc_ie_charging_rate ie;
+ int i;
+ for (i = 0; i < decoded->aoc_s_count; i++) {
+ memset(&ie, 0, sizeof(ie));
+ aoc_create_ie_data_charging_rate(&decoded->aoc_s_entries[i], &ie);
+ aoc_append_ie(ied, AOC_IE_RATE, (const void *) &ie, sizeof(ie));
+ }
+ }
+
+ if (decoded->termination_request) {
+ aoc_append_ie(ied, AOC_IE_TERMINATION_REQUEST, NULL, 0);
+ }
+}
+
+struct ast_aoc_encoded *ast_aoc_encode(struct ast_aoc_decoded *decoded, size_t *out_size, struct ast_channel *chan)
+{
+ struct aoc_ie_data ied;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t size = 0;
+
+ if (!decoded || !out_size) {
+ return NULL;
+ }
+
+ *out_size = 0;
+
+ /* create information element buffer before allocating the payload,
+ * by doing this the exact size of the payload + the id data can be
+ * allocated all at once. */
+ aoc_create_ie_data(decoded, &ied);
+
+ size = sizeof(struct ast_aoc_encoded) + ied.pos;
+
+ if (!(encoded = ast_calloc(1, size))) {
+ ast_log(LOG_WARNING, "Failed to create ast_aoc_encoded object during decode routine. \n");
+ return NULL;
+ }
+
+ /* -- Set ie data buffer */
+ if (ied.pos) {
+ /* this is safe because encoded was allocated to fit this perfectly */
+ memcpy(encoded->data, ied.buf, ied.pos);
+ encoded->datalen = htons(ied.pos);
+ }
+
+ /* --- Set Flags --- */
+ switch (decoded->msg_type) {
+ case AST_AOC_S:
+ encoded->flags = AST_AOC_ENCODED_TYPE_S;
+ break;
+ case AST_AOC_D:
+ encoded->flags = AST_AOC_ENCODED_TYPE_D;
+ break;
+ case AST_AOC_E:
+ encoded->flags = AST_AOC_ENCODED_TYPE_E;
+ break;
+ case AST_AOC_REQUEST:
+ encoded->flags = AST_AOC_ENCODED_TYPE_REQUEST;
+ default:
+ break;
+ }
+
+ /* if it is type request, set the types requested, else set charge type */
+ if (decoded->msg_type == AST_AOC_REQUEST) {
+ if (decoded->request_flag & AST_AOC_REQUEST_S) {
+ encoded->flags |= AST_AOC_ENCODED_REQUEST_S;
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_D) {
+ encoded->flags |= AST_AOC_ENCODED_REQUEST_D;
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_E) {
+ encoded->flags |= AST_AOC_ENCODED_REQUEST_E;
+ }
+ } else if ((decoded->msg_type == AST_AOC_D) || (decoded->msg_type == AST_AOC_E)) {
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_UNIT:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_UNIT;
+ break;
+ case AST_AOC_CHARGE_CURRENCY:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_CURRENCY;
+ break;
+ case AST_AOC_CHARGE_FREE:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_FREE;
+ case AST_AOC_CHARGE_NA:
+ default:
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_NA;
+ break;
+ }
+
+ if (decoded->total_type == AST_AOC_SUBTOTAL) {
+ encoded->flags |= AST_AOC_ENCODED_CHARGE_SUBTOTAL;
+ }
+ }
+
+ /* --- Set Version Number --- */
+ encoded->version = AST_AOC_ENCODE_VERSION;
+
+ /* set the output size */
+ *out_size = size;
+
+ if (aoc_debug_enabled) {
+ aoc_display_decoded_debug(decoded, 0, chan);
+ }
+
+ return encoded;
+}
+
+static int aoc_s_add_entry(struct ast_aoc_decoded *decoded, struct ast_aoc_s_entry *entry)
+{
+ if (decoded->aoc_s_count >= ARRAY_LEN(decoded->aoc_s_entries)) {
+ return -1;
+ }
+
+ decoded->aoc_s_entries[decoded->aoc_s_count] = *entry;
+ decoded->aoc_s_count++;
+
+ return 0;
+}
+
+
+unsigned int ast_aoc_s_get_count(struct ast_aoc_decoded *decoded)
+{
+ return decoded->aoc_s_count;
+}
+
+const struct ast_aoc_s_entry *ast_aoc_s_get_rate_info(struct ast_aoc_decoded *decoded, unsigned int entry_number)
+{
+ if (entry_number >= decoded->aoc_s_count) {
+ return NULL;
+ }
+
+ return (const struct ast_aoc_s_entry *) &decoded->aoc_s_entries[entry_number];
+}
+
+int ast_aoc_s_add_rate_duration(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ unsigned int amount,
+ enum ast_aoc_currency_multiplier multiplier,
+ const char *currency_name,
+ unsigned long time,
+ enum ast_aoc_time_scale time_scale,
+ unsigned long granularity_time,
+ enum ast_aoc_time_scale granularity_time_scale,
+ int step_function)
+{
+
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_DURATION;
+ entry.rate.duration.amount = amount;
+ entry.rate.duration.multiplier = multiplier;
+ entry.rate.duration.time = time;
+ entry.rate.duration.time_scale = time_scale;
+ entry.rate.duration.granularity_time = granularity_time;
+ entry.rate.duration.granularity_time_scale = granularity_time_scale;
+ entry.rate.duration.charging_type = step_function ? 1 : 0;
+
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(entry.rate.duration.currency_name, currency_name, sizeof(entry.rate.duration.currency_name));
+ }
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_flat(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ unsigned int amount,
+ enum ast_aoc_currency_multiplier multiplier,
+ const char *currency_name)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_FLAT;
+ entry.rate.flat.amount = amount;
+ entry.rate.flat.multiplier = multiplier;
+
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(entry.rate.flat.currency_name, currency_name, sizeof(entry.rate.flat.currency_name));
+ }
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+
+int ast_aoc_s_add_rate_volume(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ enum ast_aoc_volume_unit volume_unit,
+ unsigned int amount,
+ enum ast_aoc_currency_multiplier multiplier,
+ const char *currency_name)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_VOLUME;
+ entry.rate.volume.multiplier = multiplier;
+ entry.rate.volume.amount = amount;
+ entry.rate.volume.volume_unit = volume_unit;
+
+ if (!ast_strlen_zero(currency_name)) {
+ ast_copy_string(entry.rate.volume.currency_name, currency_name, sizeof(entry.rate.volume.currency_name));
+ }
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_special_charge_code(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ unsigned int code)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_SPECIAL_CODE;
+ entry.rate.special_code = code;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_free(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item,
+ int from_beginning)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = from_beginning ? AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING : AST_AOC_RATE_TYPE_FREE;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_rate_na(struct ast_aoc_decoded *decoded,
+ enum ast_aoc_s_charged_item charged_item)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = charged_item;
+ entry.rate_type = AST_AOC_RATE_TYPE_NA;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+int ast_aoc_s_add_special_arrangement(struct ast_aoc_decoded *decoded,
+ unsigned int code)
+{
+ struct ast_aoc_s_entry entry = { 0, };
+
+ entry.charged_item = AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT;
+ entry.rate_type = AST_AOC_RATE_TYPE_SPECIAL_CODE;
+ entry.rate.special_code = code;
+
+ return aoc_s_add_entry(decoded, &entry);
+}
+
+enum ast_aoc_type ast_aoc_get_msg_type(struct ast_aoc_decoded *decoded)
+{
+ return decoded->msg_type;
+}
+
+enum ast_aoc_charge_type ast_aoc_get_charge_type(struct ast_aoc_decoded *decoded)
+{
+ return decoded->charge_type;
+}
+
+enum ast_aoc_request ast_aoc_get_request(struct ast_aoc_decoded *decoded)
+{
+ return decoded->request_flag;
+}
+
+int ast_aoc_set_total_type(struct ast_aoc_decoded *decoded,
+ const enum ast_aoc_total_type type)
+{
+ decoded->total_type = type;
+ return 0;
+}
+
+enum ast_aoc_total_type ast_aoc_get_total_type(struct ast_aoc_decoded *decoded)
+{
+ return decoded->total_type;
+}
+
+int ast_aoc_set_currency_info(struct ast_aoc_decoded *decoded,
+ const unsigned int amount,
+ const enum ast_aoc_currency_multiplier multiplier,
+ const char *name)
+{
+
+ if (!ast_strlen_zero(name)) {
+ ast_copy_string(decoded->currency_name, name, sizeof(decoded->currency_name));
+ }
+
+ decoded->currency_amount = amount;
+
+ if (multiplier && (multiplier < AST_AOC_MULT_NUM_ENTRIES)) {
+ decoded->multiplier = multiplier;
+ } else {
+ decoded->multiplier = AST_AOC_MULT_ONE;
+ }
+
+ return 0;
+}
+
+unsigned int ast_aoc_get_currency_amount(struct ast_aoc_decoded *decoded)
+{
+ return decoded->currency_amount;
+}
+
+enum ast_aoc_currency_multiplier ast_aoc_get_currency_multiplier(struct ast_aoc_decoded *decoded)
+{
+ return decoded->multiplier;
+}
+
+const char *ast_aoc_get_currency_multiplier_decimal(struct ast_aoc_decoded *decoded)
+{
+ switch (decoded->multiplier) {
+ case AST_AOC_MULT_ONETHOUSANDTH:
+ return "0.001";
+ case AST_AOC_MULT_ONEHUNDREDTH:
+ return "0.01";
+ case AST_AOC_MULT_ONETENTH:
+ return "0.1";
+ case AST_AOC_MULT_ONE:
+ return "1.0";
+ case AST_AOC_MULT_TEN:
+ return "10.0";
+ case AST_AOC_MULT_HUNDRED:
+ return "100.0";
+ case AST_AOC_MULT_THOUSAND:
+ return "1000.0";
+ default:
+ return "1.0";
+ }
+}
+
+const char *ast_aoc_get_currency_name(struct ast_aoc_decoded *decoded)
+{
+ return decoded->currency_name;
+}
+
+int ast_aoc_add_unit_entry(struct ast_aoc_decoded *decoded,
+ const unsigned int amount_is_present,
+ const unsigned int amount,
+ const unsigned int type_is_present,
+ const unsigned int type)
+{
+ if ((decoded->msg_type == AST_AOC_REQUEST) ||
+ (decoded->unit_count >= ARRAY_LEN(decoded->unit_list))) {
+ return -1;
+ }
+
+ if (!amount_is_present && !type_is_present) {
+ return -1;
+ }
+
+ decoded->unit_list[decoded->unit_count].valid_amount = amount_is_present;
+ if (amount_is_present) {
+ decoded->unit_list[decoded->unit_count].amount = amount;
+ } else {
+ decoded->unit_list[decoded->unit_count].amount = 0;
+ }
+
+ decoded->unit_list[decoded->unit_count].valid_type = type_is_present;
+ if (type_is_present) {
+ decoded->unit_list[decoded->unit_count].type = type;
+ } else {
+ decoded->unit_list[decoded->unit_count].type = 0;
+ }
+ decoded->unit_count++;
+
+ return 0;
+}
+
+const struct ast_aoc_unit_entry *ast_aoc_get_unit_info(struct ast_aoc_decoded *decoded, unsigned int entry_number)
+{
+ if (entry_number >= decoded->unit_count) {
+ return NULL;
+ }
+
+ return (const struct ast_aoc_unit_entry *) &decoded->unit_list[entry_number];
+}
+
+unsigned int ast_aoc_get_unit_count(struct ast_aoc_decoded *decoded)
+{
+ return decoded->unit_count;
+}
+
+int ast_aoc_set_billing_id(struct ast_aoc_decoded *decoded, const enum ast_aoc_billing_id id)
+{
+ if ((id >= AST_AOC_BILLING_NUM_ENTRIES) || (id < AST_AOC_BILLING_NA)) {
+ return -1;
+ }
+
+ decoded->billing_id = id;
+
+ return 0;
+}
+
+enum ast_aoc_billing_id ast_aoc_get_billing_id(struct ast_aoc_decoded *decoded)
+{
+ return decoded->billing_id;
+}
+
+int ast_aoc_set_association_id(struct ast_aoc_decoded *decoded, const int id)
+{
+ if (decoded->msg_type != AST_AOC_E) {
+ return -1;
+ }
+ memset(&decoded->charging_association, 0, sizeof(decoded->charging_association));
+ decoded->charging_association.charging_type = AST_AOC_CHARGING_ASSOCIATION_ID;
+ decoded->charging_association.charge.id = id;
+ return 0;
+}
+
+const struct ast_aoc_charging_association *ast_aoc_get_association_info(struct ast_aoc_decoded *decoded)
+{
+ return &decoded->charging_association;
+}
+
+int ast_aoc_set_association_number(struct ast_aoc_decoded *decoded, const char *num, uint8_t plan)
+{
+ if ((decoded->msg_type != AST_AOC_E) || ast_strlen_zero(num)) {
+ return -1;
+ }
+ memset(&decoded->charging_association, 0, sizeof(decoded->charging_association));
+ decoded->charging_association.charging_type = AST_AOC_CHARGING_ASSOCIATION_NUMBER;
+ decoded->charging_association.charge.number.plan = plan;
+ ast_copy_string(decoded->charging_association.charge.number.number, num, sizeof(decoded->charging_association.charge.number.number));
+
+ return 0;
+}
+
+int ast_aoc_set_termination_request(struct ast_aoc_decoded *decoded)
+{
+ if (decoded->msg_type != AST_AOC_REQUEST) {
+ return -1;
+ }
+ decoded->termination_request = 1;
+
+ return 0;
+}
+
+int ast_aoc_get_termination_request(struct ast_aoc_decoded *decoded)
+{
+ return decoded->termination_request;
+}
+
+/*!
+ * \internal
+ * \brief Convert AST_AOC_VOLUME_UNIT to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_volume_unit_str(enum ast_aoc_volume_unit value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_VOLUME_UNIT_OCTET:
+ str = "Octet";
+ break;
+ case AST_AOC_VOLUME_UNIT_SEGMENT:
+ str = "Segment";
+ break;
+ case AST_AOC_VOLUME_UNIT_MESSAGE:
+ str = "Message";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert ast_aoc_charged_item to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_charged_item_str(enum ast_aoc_s_charged_item value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_CHARGED_ITEM_NA:
+ str = "NotAvailable";
+ break;
+ case AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT:
+ str = "SpecialArrangement";
+ break;
+ case AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION:
+ str = "BasicCommunication";
+ break;
+ case AST_AOC_CHARGED_ITEM_CALL_ATTEMPT:
+ str = "CallAttempt";
+ break;
+ case AST_AOC_CHARGED_ITEM_CALL_SETUP:
+ str = "CallSetup";
+ break;
+ case AST_AOC_CHARGED_ITEM_USER_USER_INFO:
+ str = "UserUserInfo";
+ break;
+ case AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE:
+ str = "SupplementaryService";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert ast_aoc_total_type to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_type_of_totaling_str(enum ast_aoc_total_type value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_SUBTOTAL:
+ str = "SubTotal";
+ break;
+ case AST_AOC_TOTAL:
+ str = "Total";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert ast_aoc_rate_type to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_rate_type_str(enum ast_aoc_s_rate_type value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_RATE_TYPE_NA:
+ str = "NotAvailable";
+ break;
+ case AST_AOC_RATE_TYPE_FREE:
+ str = "Free";
+ break;
+ case AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING:
+ str = "FreeFromBeginning";
+ break;
+ case AST_AOC_RATE_TYPE_DURATION:
+ str = "Duration";
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ str = "Flat";
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ str = "Volume";
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ str = "SpecialCode";
+ break;
+ }
+ return str;
+}
+
+/*!
+ * \internal
+ * \brief Convert AST_AOC_TIME_SCALE to string.
+ * \since 1.8
+ *
+ * \param value Value to convert to string.
+ *
+ * \return String equivalent.
+ */
+static const char *aoc_scale_str(enum ast_aoc_time_scale value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_TIME_SCALE_HUNDREDTH_SECOND:
+ str = "OneHundredthSecond";
+ break;
+ case AST_AOC_TIME_SCALE_TENTH_SECOND:
+ str = "OneTenthSecond";
+ break;
+ case AST_AOC_TIME_SCALE_SECOND:
+ str = "Second";
+ break;
+ case AST_AOC_TIME_SCALE_TEN_SECOND:
+ str = "TenSeconds";
+ break;
+ case AST_AOC_TIME_SCALE_MINUTE:
+ str = "Minute";
+ break;
+ case AST_AOC_TIME_SCALE_HOUR:
+ str = "Hour";
+ break;
+ case AST_AOC_TIME_SCALE_DAY:
+ str = "Day";
+ break;
+ }
+ return str;
+}
+
+static const char *aoc_charge_type_str(enum ast_aoc_charge_type value)
+{
+ const char *str;
+
+ switch (value) {
+ default:
+ case AST_AOC_CHARGE_NA:
+ str = "NotAvailable";
+ break;
+ case AST_AOC_CHARGE_FREE:
+ str = "Free";
+ break;
+ case AST_AOC_CHARGE_CURRENCY:
+ str = "Currency";
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ str = "Units";
+ break;
+ }
+
+ return str;
+}
+
+static const char *aoc_multiplier_str(enum ast_aoc_currency_multiplier mult)
+{
+ switch (mult) {
+ case AST_AOC_MULT_ONETHOUSANDTH:
+ return "1/1000";
+ case AST_AOC_MULT_ONEHUNDREDTH:
+ return "1/100";
+ case AST_AOC_MULT_ONETENTH:
+ return "1/10";
+ case AST_AOC_MULT_ONE:
+ return "1";
+ case AST_AOC_MULT_TEN:
+ return "10";
+ case AST_AOC_MULT_HUNDRED:
+ return "100";
+ case AST_AOC_MULT_THOUSAND:
+ return "1000";
+ case AST_AOC_MULT_NUM_ENTRIES:
+ break;
+ }
+ return "1";
+}
+
+static const char *aoc_billingid_str(enum ast_aoc_billing_id billing_id)
+{
+ switch (billing_id) {
+ case AST_AOC_BILLING_NORMAL:
+ return "Normal";
+ case AST_AOC_BILLING_REVERSE_CHARGE:
+ return "Reverse";
+ case AST_AOC_BILLING_CREDIT_CARD:
+ return "CreditCard";
+ case AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL:
+ return "CallForwardingUnconditional";
+ case AST_AOC_BILLING_CALL_FWD_BUSY:
+ return "CallForwardingBusy";
+ case AST_AOC_BILLING_CALL_FWD_NO_REPLY:
+ return "CallForwardingNoReply";
+ case AST_AOC_BILLING_CALL_DEFLECTION:
+ return "CallDeflection";
+ case AST_AOC_BILLING_CALL_TRANSFER:
+ return "CallTransfer";
+ case AST_AOC_BILLING_NA:
+ return "NotAvailable";
+ case AST_AOC_BILLING_NUM_ENTRIES:
+ break;
+ }
+ return "NotAvailable";
+}
+
+int ast_aoc_test_encode_decode_match(struct ast_aoc_decoded *decoded)
+{
+ struct ast_aoc_decoded *new_decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t size;
+ int res = 0;
+
+ if (!(encoded = ast_aoc_encode(decoded, &size, NULL))) {
+ return -1;
+ }
+
+ if (!(new_decoded = ast_aoc_decode(encoded, size, NULL))) {
+ ast_free(encoded);
+ return -1;
+ }
+
+ if (memcmp(new_decoded, decoded, sizeof(struct ast_aoc_decoded))) {
+ res = -1;
+ }
+
+ ast_aoc_destroy_decoded(new_decoded);
+ ast_aoc_destroy_encoded(encoded);
+ return res;
+}
+
+static char *aoc_cli_debug_enable(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "aoc set debug";
+ e->usage =
+ "Usage: 'aoc set debug on' to enable aoc debug, 'aoc set debug off' to disable debug.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ case CLI_HANDLER:
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ } else if(ast_true(a->argv[3])) {
+ ast_cli(a->fd, "aoc debug enabled\n");
+ aoc_debug_enabled = 1;
+ } else if (ast_false(a->argv[3])) {
+ ast_cli(a->fd, "aoc debug disabled\n");
+ aoc_debug_enabled = 0;
+ } else {
+ return CLI_SHOWUSAGE;
+ }
+ }
+
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \internal
+ * \brief Append the time structure to the event message string.
+ * \since 1.8
+ *
+ * \param msg Event message string being built.
+ * \param prefix Prefix to add to the amount lines.
+ * \param name Name of the time structure to convert.
+ * \param time Data to convert.
+ * \param scale Data to convert.
+ *
+ * \return Nothing
+ */
+static void aoc_time_str(struct ast_str **msg, const char *prefix, const char *name, unsigned long time, enum ast_aoc_time_scale scale)
+{
+ ast_str_append(msg, 0, "%s/%s/Length: %lu\r\n", prefix, name, time);
+ ast_str_append(msg, 0, "%s/%s/Scale: %s\r\n", prefix, name,
+ aoc_scale_str(scale));
+}
+
+/*!
+ * \internal
+ * \brief Append the amount structure to the event message string.
+ * \since 1.8
+ *
+ * \param msg Event message string being built.
+ * \param prefix Prefix to add to the amount lines.
+ * \param amount Data to convert.
+ * \param multipler to convert
+ *
+ * \return Nothing
+ */
+static void aoc_amount_str(struct ast_str **msg, const char *prefix, unsigned int amount, enum ast_aoc_currency_multiplier mult)
+{
+ static const char name[] = "Amount";
+
+ ast_str_append(msg, 0, "%s/%s/Cost: %u\r\n", prefix, name, amount);
+ ast_str_append(msg, 0, "%s/%s/Multiplier: %s\r\n", prefix, name,
+ aoc_multiplier_str(mult));
+}
+
+static void aoc_request_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg)
+{
+ if (chan) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", chan->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", chan->uniqueid);
+ }
+
+ if (decoded->request_flag) {
+ ast_str_append(msg, 0, "AOCRequest:");
+ if (decoded->request_flag & AST_AOC_REQUEST_S) {
+ ast_str_append(msg, 0, "S");
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_D) {
+ ast_str_append(msg, 0, "D");
+ }
+ if (decoded->request_flag & AST_AOC_REQUEST_E) {
+ ast_str_append(msg, 0, "E");
+ }
+ ast_str_append(msg, 0, "\r\n");
+
+ } else {
+ ast_str_append(msg, 0, "AOCRequest: NONE\r\n");
+ }
+}
+
+static void aoc_s_event(const struct ast_aoc_decoded *decoded, struct ast_channel *owner, struct ast_str **msg)
+{
+ const char *rate_str;
+ char prefix[32];
+ int idx;
+
+ if (owner) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", owner->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", owner->uniqueid);
+ }
+
+ ast_str_append(msg, 0, "NumberRates: %d\r\n", decoded->aoc_s_count);
+ for (idx = 0; idx < decoded->aoc_s_count; ++idx) {
+ snprintf(prefix, sizeof(prefix), "Rate(%d)", idx);
+
+ ast_str_append(msg, 0, "%s/Chargeable: %s\r\n", prefix,
+ aoc_charged_item_str(decoded->aoc_s_entries[idx].charged_item));
+ if (decoded->aoc_s_entries[idx].charged_item == AST_AOC_CHARGED_ITEM_NA) {
+ continue;
+ }
+ rate_str = aoc_rate_type_str(decoded->aoc_s_entries[idx].rate_type);
+ ast_str_append(msg, 0, "%s/Type: %s\r\n", prefix, rate_str);
+ switch (decoded->aoc_s_entries[idx].rate_type) {
+ case AST_AOC_RATE_TYPE_DURATION:
+ strcat(prefix, "/");
+ strcat(prefix, rate_str);
+ ast_str_append(msg, 0, "%s/Currency: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.duration.currency_name);
+ aoc_amount_str(msg, prefix,
+ decoded->aoc_s_entries[idx].rate.duration.amount,
+ decoded->aoc_s_entries[idx].rate.duration.multiplier);
+ ast_str_append(msg, 0, "%s/ChargingType: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.duration.charging_type ?
+ "StepFunction" : "ContinuousCharging");
+ aoc_time_str(msg, prefix, "Time",
+ decoded->aoc_s_entries[idx].rate.duration.time,
+ decoded->aoc_s_entries[idx].rate.duration.time_scale);
+ if (decoded->aoc_s_entries[idx].rate.duration.granularity_time) {
+ aoc_time_str(msg, prefix, "Granularity",
+ decoded->aoc_s_entries[idx].rate.duration.granularity_time,
+ decoded->aoc_s_entries[idx].rate.duration.granularity_time_scale);
+ }
+ break;
+ case AST_AOC_RATE_TYPE_FLAT:
+ strcat(prefix, "/");
+ strcat(prefix, rate_str);
+ ast_str_append(msg, 0, "%s/Currency: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.flat.currency_name);
+ aoc_amount_str(msg, prefix,
+ decoded->aoc_s_entries[idx].rate.flat.amount,
+ decoded->aoc_s_entries[idx].rate.flat.multiplier);
+ break;
+ case AST_AOC_RATE_TYPE_VOLUME:
+ strcat(prefix, "/");
+ strcat(prefix, rate_str);
+ ast_str_append(msg, 0, "%s/Currency: %s\r\n", prefix,
+ decoded->aoc_s_entries[idx].rate.volume.currency_name);
+ aoc_amount_str(msg, prefix,
+ decoded->aoc_s_entries[idx].rate.volume.amount,
+ decoded->aoc_s_entries[idx].rate.volume.multiplier);
+ ast_str_append(msg, 0, "%s/Unit: %s\r\n", prefix,
+ aoc_volume_unit_str(decoded->aoc_s_entries[idx].rate.volume.volume_unit));
+ break;
+ case AST_AOC_RATE_TYPE_SPECIAL_CODE:
+ ast_str_append(msg, 0, "%s/%s: %d\r\n", prefix, rate_str,
+ decoded->aoc_s_entries[idx].rate.special_code);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void aoc_d_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg)
+{
+ const char *charge_str;
+ int idx;
+ char prefix[32];
+
+ if (chan) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", chan->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", chan->uniqueid);
+ }
+
+ charge_str = aoc_charge_type_str(decoded->charge_type);
+ ast_str_append(msg, 0, "Type: %s\r\n", charge_str);
+
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "BillingID: %s\r\n",
+ aoc_billingid_str(decoded->billing_id));
+ ast_str_append(msg, 0, "TypeOfCharging: %s\r\n",
+ aoc_type_of_totaling_str(decoded->total_type));
+ break;
+ default:
+ break;
+ }
+
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ ast_str_append(msg, 0, "%s: %s\r\n", charge_str,
+ decoded->currency_name);
+ aoc_amount_str(msg, charge_str,
+ decoded->currency_amount,
+ decoded->multiplier);
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "%s/NumberItems: %d\r\n", charge_str,
+ decoded->unit_count);
+ for (idx = 0; idx < decoded->unit_count; ++idx) {
+ snprintf(prefix, sizeof(prefix), "%s/Item(%d)", charge_str, idx);
+ if (decoded->unit_list[idx].valid_amount) {
+ ast_str_append(msg, 0, "%s/NumberOf: %u\r\n", prefix,
+ decoded->unit_list[idx].amount);
+ }
+ if (decoded->unit_list[idx].valid_type) {
+ ast_str_append(msg, 0, "%s/TypeOf: %d\r\n", prefix,
+ decoded->unit_list[idx].type);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void aoc_e_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan, struct ast_str **msg)
+{
+ const char *charge_str;
+ int idx;
+ char prefix[32];
+
+ if (chan) {
+ ast_str_append(msg, 0, "Channel: %s\r\n", chan->name);
+ ast_str_append(msg, 0, "UniqueID: %s\r\n", chan->uniqueid);
+ }
+
+ charge_str = "ChargingAssociation";
+
+ switch (decoded->charging_association.charging_type) {
+ case AST_AOC_CHARGING_ASSOCIATION_NUMBER:
+ snprintf(prefix, sizeof(prefix), "%s/Number", charge_str);
+ ast_str_append(msg, 0, "%s: %s\r\n", prefix,
+ decoded->charging_association.charge.number.number);
+ ast_str_append(msg, 0, "%s/Plan: %d\r\n", prefix,
+ decoded->charging_association.charge.number.plan);
+ break;
+ case AST_AOC_CHARGING_ASSOCIATION_ID:
+ ast_str_append(msg, 0, "%s/ID: %d\r\n", charge_str, decoded->charging_association.charge.id);
+ break;
+ case AST_AOC_CHARGING_ASSOCIATION_NA:
+ default:
+ break;
+ }
+
+ charge_str = aoc_charge_type_str(decoded->charge_type);
+ ast_str_append(msg, 0, "Type: %s\r\n", charge_str);
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "BillingID: %s\r\n",
+ aoc_billingid_str(decoded->billing_id));
+ break;
+ default:
+ break;
+ }
+ switch (decoded->charge_type) {
+ case AST_AOC_CHARGE_CURRENCY:
+ ast_str_append(msg, 0, "%s: %s\r\n", charge_str,
+ decoded->currency_name);
+ aoc_amount_str(msg, charge_str,
+ decoded->currency_amount,
+ decoded->multiplier);
+ break;
+ case AST_AOC_CHARGE_UNIT:
+ ast_str_append(msg, 0, "%s/NumberItems: %d\r\n", charge_str,
+ decoded->unit_count);
+ for (idx = 0; idx < decoded->unit_count; ++idx) {
+ snprintf(prefix, sizeof(prefix), "%s/Item(%d)", charge_str, idx);
+ if (decoded->unit_list[idx].valid_amount) {
+ ast_str_append(msg, 0, "%s/NumberOf: %u\r\n", prefix,
+ decoded->unit_list[idx].amount);
+ }
+ if (decoded->unit_list[idx].valid_type) {
+ ast_str_append(msg, 0, "%s/TypeOf: %d\r\n", prefix,
+ decoded->unit_list[idx].type);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+int ast_aoc_manager_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan)
+{
+ struct ast_str *msg;
+
+ if (!decoded || !(msg = ast_str_create(1024))) {
+ return -1;
+ }
+
+ switch (decoded->msg_type) {
+ case AST_AOC_S:
+ if (chan) {
+ aoc_s_event(decoded, chan, &msg);
+ ast_manager_event(chan, EVENT_FLAG_AOC, "AOC-S", "%s", ast_str_buffer(msg));
+ }
+ break;
+ case AST_AOC_D:
+ if (chan) {
+ aoc_d_event(decoded, chan, &msg);
+ ast_manager_event(chan, EVENT_FLAG_AOC, "AOC-D", "%s", ast_str_buffer(msg));
+ }
+ break;
+ case AST_AOC_E:
+ {
+ struct ast_channel *chans[1];
+ aoc_e_event(decoded, chan, &msg);
+ chans[0] = chan;
+ ast_manager_event_multichan(EVENT_FLAG_AOC, "AOC-E", chan ? 1 : 0, chans, "%s", ast_str_buffer(msg));
+ }
+ break;
+ default:
+ /* events for AST_AOC_REQUEST are not generated here */
+ break;
+ }
+
+ ast_free(msg);
+ return 0;
+}
+
+int ast_aoc_decoded2str(const struct ast_aoc_decoded *decoded, struct ast_str **msg)
+{
+ if (!decoded || !msg) {
+ return -1;
+ }
+
+ switch (decoded->msg_type) {
+ case AST_AOC_S:
+ ast_str_append(msg, 0, "AOC-S\r\n");
+ aoc_s_event(decoded, NULL, msg);
+ break;
+ case AST_AOC_D:
+ ast_str_append(msg, 0, "AOC-D\r\n");
+ aoc_d_event(decoded, NULL, msg);
+ break;
+ case AST_AOC_E:
+ ast_str_append(msg, 0, "AOC-E\r\n");
+ aoc_e_event(decoded, NULL, msg);
+ break;
+ case AST_AOC_REQUEST:
+ ast_str_append(msg, 0, "AOC-Request\r\n");
+ aoc_request_event(decoded, NULL, msg);
+ break;
+ }
+
+ return 0;
+}
+
+static void aoc_display_decoded_debug(const struct ast_aoc_decoded *decoded, int decoding, struct ast_channel *chan)
+{
+ struct ast_str *msg;
+
+ if (!decoded || !(msg = ast_str_create(1024))) {
+ return;
+ }
+
+ if (decoding) {
+ ast_str_append(&msg, 0, "---- DECODED AOC MSG ----\r\n");
+ } else {
+ ast_str_append(&msg, 0, "---- ENCODED AOC MSG ----\r\n");
+ }
+ if (chan) {
+ ast_str_append(&msg, 0, "CHANNEL: %s\r\n", chan->name);
+ }
+
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+ ast_free(msg);
+ return;
+ }
+
+ ast_verb(1, "%s\r\n", ast_str_buffer(msg));
+ ast_free(msg);
+}
+
+static struct ast_cli_entry aoc_cli[] = {
+ AST_CLI_DEFINE(aoc_cli_debug_enable, "enable cli debugging of AOC messages"),
+};
+
+int ast_aoc_cli_init(void)
+{
+ return ast_cli_register_multiple(aoc_cli, ARRAY_LEN(aoc_cli));
+}
diff --git a/main/asterisk.c b/main/asterisk.c
index 4b09d1e6a..1e13729c1 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -142,6 +142,7 @@ int daemon(int, int); /* defined in libresolv of all places */
#include "asterisk/poll-compat.h"
#include "asterisk/ccss.h"
#include "asterisk/test.h"
+#include "asterisk/aoc.h"
#include "../defaults.h"
@@ -3602,6 +3603,8 @@ int main(int argc, char *argv[])
}
#endif
+ ast_aoc_cli_init();
+
ast_makesocket();
sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP);
diff --git a/main/channel.c b/main/channel.c
index 999e39ba6..3057fae9d 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -3796,6 +3796,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con
case _XXX_AST_CONTROL_T38:
case AST_CONTROL_CC:
case AST_CONTROL_READ_ACTION:
+ case AST_CONTROL_AOC:
break;
case AST_CONTROL_CONGESTION:
@@ -3941,6 +3942,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
case AST_CONTROL_REDIRECTING:
case AST_CONTROL_CC:
case AST_CONTROL_READ_ACTION:
+ case AST_CONTROL_AOC:
/* Nothing left to do for these. */
res = 0;
break;
@@ -6003,6 +6005,9 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct
int bridge_exit = 0;
switch (f->subclass.integer) {
+ case AST_CONTROL_AOC:
+ ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
+ break;
case AST_CONTROL_REDIRECTING:
if (ast_channel_redirecting_macro(who, other, f, other == c0, 1)) {
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
diff --git a/main/features.c b/main/features.c
index 72a626e45..6c85241db 100644
--- a/main/features.c
+++ b/main/features.c
@@ -3208,6 +3208,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
}
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
break;
+ case AST_CONTROL_AOC:
case AST_CONTROL_HOLD:
case AST_CONTROL_UNHOLD:
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
diff --git a/main/manager.c b/main/manager.c
index 43be08e60..b0b2d3771 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -74,6 +74,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/features.h"
#include "asterisk/security_events.h"
+#include "asterisk/aoc.h"
/*** DOCUMENTATION
<manager name="Ping" language="en_US">
@@ -694,6 +695,112 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
For success returns, the module revision number is included.</para>
</description>
</manager>
+ <manager name="AOCMessage" language="en_US">
+ <synopsis>
+ Generate an Advice of Charge message on a channel.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Channel" required="true">
+ <para>Channel name to generate the AOC message on.</para>
+ </parameter>
+ <parameter name="ChannelPrefix">
+ <para>Partial channel prefix. By using this option one can match the beginning part
+ of a channel name without having to put the entire name in. For example
+ if a channel name is SIP/snom-00000001 and this value is set to SIP/snom, then
+ that channel matches and the message will be sent. Note however that only
+ the first matched channel has the message sent on it. </para>
+ </parameter>
+ <parameter name="MsgType" required="true">
+ <para>Defines what type of AOC message to create, AOC-D or AOC-E</para>
+ <enumlist>
+ <enum name="D" />
+ <enum name="E" />
+ </enumlist>
+ </parameter>
+ <parameter name="ChargeType" required="true">
+ <para>Defines what kind of charge this message represents.</para>
+ <enumlist>
+ <enum name="NA" />
+ <enum name="FREE" />
+ <enum name="Currency" />
+ <enum name="Unit" />
+ </enumlist>
+ </parameter>
+ <parameter name="UnitAmount(0)">
+ <para>This represents the amount of units charged. The ETSI AOC standard specifies that
+ this value along with the optional UnitType value are entries in a list. To accommodate this
+ these values take an index value starting at 0 which can be used to generate this list of
+ unit entries. For Example, If two unit entires were required this could be achieved by setting the
+ paramter UnitAmount(0)=1234 and UnitAmount(1)=5678. Note that UnitAmount at index 0 is
+ required when ChargeType=Unit, all other entries in the list are optional.
+ </para>
+ </parameter>
+ <parameter name="UnitType(0)">
+ <para>Defines the type of unit. ETSI AOC standard specifies this as an integer
+ value between 1 and 16, but this value is left open to accept any positive
+ integer. Like the UnitAmount parameter, this value represents a list entry
+ and has an index parameter that starts at 0.
+ </para>
+ </parameter>
+ <parameter name="CurrencyName">
+ <para>Specifies the currency's name. Note that this value is truncated after 10 characters.</para>
+ </parameter>
+ <parameter name="CurrencyAmount">
+ <para>Specifies the charge unit amount as a positive integer. This value is required
+ when ChargeType==Currency.</para>
+ </parameter>
+ <parameter name="CurrencyMultiplier">
+ <para>Specifies the currency multiplier. This value is required when ChargeType==Currency.</para>
+ <enumlist>
+ <enum name="OneThousandth" />
+ <enum name="OneHundredth" />
+ <enum name="OneTenth" />
+ <enum name="One" />
+ <enum name="Ten" />
+ <enum name="Hundred" />
+ <enum name="Thousand" />
+ </enumlist>
+ </parameter>
+ <parameter name="TotalType" default="Total">
+ <para>Defines what kind of AOC-D total is represented.</para>
+ <enumlist>
+ <enum name="Total" />
+ <enum name="SubTotal" />
+ </enumlist>
+ </parameter>
+ <parameter name="AOCBillingId">
+ <para>Represents a billing ID associated with an AOC-D or AOC-E message. Note
+ that only the first 3 items of the enum are valid AOC-D billing IDs</para>
+ <enumlist>
+ <enum name="Normal" />
+ <enum name="ReverseCharge" />
+ <enum name="CreditCard" />
+ <enum name="CallFwdUnconditional" />
+ <enum name="CallFwdBusy" />
+ <enum name="CallFwdNoReply" />
+ <enum name="CallDeflection" />
+ <enum name="CallTransfer" />
+ </enumlist>
+ </parameter>
+ <parameter name="ChargingAssociationId">
+ <para>Charging association identifier. This is optional for AOC-E and can be
+ set to any value between -32768 and 32767</para>
+ </parameter>
+ <parameter name="ChargingAssociationNumber">
+ <para>Represents the charging association party number. This value is optional
+ for AOC-E.</para>
+ </parameter>
+ <parameter name="ChargingAssociationPlan">
+ <para>Integer representing the charging plan associated with the ChargingAssociationNumber.
+ The value is bits 7 through 1 of the Q.931 octet containing the type-of-number and
+ numbering-plan-identification fields.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>Generates an AOC-D or AOC-E message on a channel.</para>
+ </description>
+ </manager>
***/
enum error_type {
@@ -3396,6 +3503,236 @@ static void *fast_originate(void *data)
return NULL;
}
+static int aocmessage_get_unit_entry(const struct message *m, struct ast_aoc_unit_entry *entry, unsigned int entry_num)
+{
+ const char *unitamount;
+ const char *unittype;
+ struct ast_str *str = ast_str_alloca(32);
+
+ memset(entry, 0, sizeof(*entry));
+
+ ast_str_set(&str, 0, "UnitAmount(%u)", entry_num);
+ unitamount = astman_get_header(m, ast_str_buffer(str));
+
+ ast_str_set(&str, 0, "UnitType(%u)", entry_num);
+ unittype = astman_get_header(m, ast_str_buffer(str));
+
+ if (!ast_strlen_zero(unitamount) && (sscanf(unitamount, "%30u", &entry->amount) == 1)) {
+ entry->valid_amount = 1;
+ }
+
+ if (!ast_strlen_zero(unittype) && sscanf(unittype, "%30u", &entry->type) == 1) {
+ entry->valid_type = 1;
+ }
+
+ return 0;
+}
+
+static int action_aocmessage(struct mansession *s, const struct message *m)
+{
+ const char *channel = astman_get_header(m, "Channel");
+ const char *pchannel = astman_get_header(m, "ChannelPrefix");
+ const char *msgtype = astman_get_header(m, "MsgType");
+ const char *chargetype = astman_get_header(m, "ChargeType");
+ const char *currencyname = astman_get_header(m, "CurrencyName");
+ const char *currencyamount = astman_get_header(m, "CurrencyAmount");
+ const char *mult = astman_get_header(m, "CurrencyMultiplier");
+ const char *totaltype = astman_get_header(m, "TotalType");
+ const char *aocbillingid = astman_get_header(m, "AOCBillingId");
+ const char *association_id= astman_get_header(m, "ChargingAssociationId");
+ const char *association_num = astman_get_header(m, "ChargingAssociationNumber");
+ const char *association_plan = astman_get_header(m, "ChargingAssociationPlan");
+
+ enum ast_aoc_type _msgtype;
+ enum ast_aoc_charge_type _chargetype;
+ enum ast_aoc_currency_multiplier _mult = AST_AOC_MULT_ONE;
+ enum ast_aoc_total_type _totaltype = AST_AOC_TOTAL;
+ enum ast_aoc_billing_id _billingid = AST_AOC_BILLING_NA;
+ unsigned int _currencyamount = 0;
+ int _association_id = 0;
+ unsigned int _association_plan = 0;
+ struct ast_channel *chan = NULL;
+
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_aoc_encoded *encoded = NULL;
+ size_t encoded_size = 0;
+
+ if (ast_strlen_zero(channel) && ast_strlen_zero(pchannel)) {
+ astman_send_error(s, m, "Channel and PartialChannel are not specified. Specify at least one of these.");
+ goto aocmessage_cleanup;
+ }
+
+ if (!(chan = ast_channel_get_by_name(channel)) && !ast_strlen_zero(pchannel)) {
+ chan = ast_channel_get_by_name_prefix(pchannel, strlen(pchannel));
+ }
+
+ if (!chan) {
+ astman_send_error(s, m, "No such channel");
+ goto aocmessage_cleanup;
+ }
+
+ if (ast_strlen_zero(msgtype) || (strcasecmp(msgtype, "d") && strcasecmp(msgtype, "e"))) {
+ astman_send_error(s, m, "Invalid MsgType");
+ goto aocmessage_cleanup;
+ }
+
+ if (ast_strlen_zero(chargetype)) {
+ astman_send_error(s, m, "ChargeType not specified");
+ goto aocmessage_cleanup;
+ }
+
+ _msgtype = strcasecmp(msgtype, "d") ? AST_AOC_E : AST_AOC_D;
+
+ if (!strcasecmp(chargetype, "NA")) {
+ _chargetype = AST_AOC_CHARGE_NA;
+ } else if (!strcasecmp(chargetype, "Free")) {
+ _chargetype = AST_AOC_CHARGE_FREE;
+ } else if (!strcasecmp(chargetype, "Currency")) {
+ _chargetype = AST_AOC_CHARGE_CURRENCY;
+ } else if (!strcasecmp(chargetype, "Unit")) {
+ _chargetype = AST_AOC_CHARGE_UNIT;
+ } else {
+ astman_send_error(s, m, "Invalid ChargeType");
+ goto aocmessage_cleanup;
+ }
+
+ if (_chargetype == AST_AOC_CHARGE_CURRENCY) {
+
+ if (ast_strlen_zero(currencyamount) || (sscanf(currencyamount, "%30u", &_currencyamount) != 1)) {
+ astman_send_error(s, m, "Invalid CurrencyAmount, CurrencyAmount is a required when ChargeType is Currency");
+ goto aocmessage_cleanup;
+ }
+
+ if (ast_strlen_zero(mult)) {
+ astman_send_error(s, m, "ChargeMultiplier unspecified, ChargeMultiplier is required when ChargeType is Currency.");
+ goto aocmessage_cleanup;
+ } else if (!strcasecmp(mult, "onethousandth")) {
+ _mult = AST_AOC_MULT_ONETHOUSANDTH;
+ } else if (!strcasecmp(mult, "onehundredth")) {
+ _mult = AST_AOC_MULT_ONEHUNDREDTH;
+ } else if (!strcasecmp(mult, "onetenth")) {
+ _mult = AST_AOC_MULT_ONETENTH;
+ } else if (!strcasecmp(mult, "one")) {
+ _mult = AST_AOC_MULT_ONE;
+ } else if (!strcasecmp(mult, "ten")) {
+ _mult = AST_AOC_MULT_TEN;
+ } else if (!strcasecmp(mult, "hundred")) {
+ _mult = AST_AOC_MULT_HUNDRED;
+ } else if (!strcasecmp(mult, "thousand")) {
+ _mult = AST_AOC_MULT_THOUSAND;
+ } else {
+ astman_send_error(s, m, "Invalid ChargeMultiplier");
+ goto aocmessage_cleanup;
+ }
+ }
+
+ /* create decoded object and start setting values */
+ if (!(decoded = ast_aoc_create(_msgtype, _chargetype, 0))) {
+ astman_send_error(s, m, "Message Creation Failed");
+ goto aocmessage_cleanup;
+ }
+
+ if (_msgtype == AST_AOC_D) {
+ if (!ast_strlen_zero(totaltype) && !strcasecmp(totaltype, "subtotal")) {
+ _totaltype = AST_AOC_SUBTOTAL;
+ }
+
+ if (ast_strlen_zero(aocbillingid)) {
+ /* ignore this is optional */
+ } else if (!strcasecmp(aocbillingid, "Normal")) {
+ _billingid = AST_AOC_BILLING_NORMAL;
+ } else if (!strcasecmp(aocbillingid, "ReverseCharge")) {
+ _billingid = AST_AOC_BILLING_REVERSE_CHARGE;
+ } else if (!strcasecmp(aocbillingid, "CreditCard")) {
+ _billingid = AST_AOC_BILLING_CREDIT_CARD;
+ } else {
+ astman_send_error(s, m, "Invalid AOC-D AOCBillingId");
+ goto aocmessage_cleanup;
+ }
+ } else {
+ if (ast_strlen_zero(aocbillingid)) {
+ /* ignore this is optional */
+ } else if (!strcasecmp(aocbillingid, "Normal")) {
+ _billingid = AST_AOC_BILLING_NORMAL;
+ } else if (!strcasecmp(aocbillingid, "ReverseCharge")) {
+ _billingid = AST_AOC_BILLING_REVERSE_CHARGE;
+ } else if (!strcasecmp(aocbillingid, "CreditCard")) {
+ _billingid = AST_AOC_BILLING_CREDIT_CARD;
+ } else if (!strcasecmp(aocbillingid, "CallFwdUnconditional")) {
+ _billingid = AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL;
+ } else if (!strcasecmp(aocbillingid, "CallFwdBusy")) {
+ _billingid = AST_AOC_BILLING_CALL_FWD_BUSY;
+ } else if (!strcasecmp(aocbillingid, "CallFwdNoReply")) {
+ _billingid = AST_AOC_BILLING_CALL_FWD_NO_REPLY;
+ } else if (!strcasecmp(aocbillingid, "CallDeflection")) {
+ _billingid = AST_AOC_BILLING_CALL_DEFLECTION;
+ } else if (!strcasecmp(aocbillingid, "CallTransfer")) {
+ _billingid = AST_AOC_BILLING_CALL_TRANSFER;
+ } else {
+ astman_send_error(s, m, "Invalid AOC-E AOCBillingId");
+ goto aocmessage_cleanup;
+ }
+
+ if (!ast_strlen_zero(association_id) && (sscanf(association_id, "%30d", &_association_id) != 1)) {
+ astman_send_error(s, m, "Invalid ChargingAssociationId");
+ goto aocmessage_cleanup;
+ }
+ if (!ast_strlen_zero(association_plan) && (sscanf(association_plan, "%30u", &_association_plan) != 1)) {
+ astman_send_error(s, m, "Invalid ChargingAssociationPlan");
+ goto aocmessage_cleanup;
+ }
+
+ if (_association_id) {
+ ast_aoc_set_association_id(decoded, _association_id);
+ } else if (!ast_strlen_zero(association_num)) {
+ ast_aoc_set_association_number(decoded, association_num, _association_plan);
+ }
+ }
+
+ if (_chargetype == AST_AOC_CHARGE_CURRENCY) {
+ ast_aoc_set_currency_info(decoded, _currencyamount, _mult, ast_strlen_zero(currencyname) ? NULL : currencyname);
+ } else if (_chargetype == AST_AOC_CHARGE_UNIT) {
+ struct ast_aoc_unit_entry entry;
+ int i;
+
+ /* multiple unit entries are possible, lets get them all */
+ for (i = 0; i < 32; i++) {
+ if (aocmessage_get_unit_entry(m, &entry, i)) {
+ break; /* that's the end then */
+ }
+
+ ast_aoc_add_unit_entry(decoded, entry.valid_amount, entry.amount, entry.valid_type, entry.type);
+ }
+
+ /* at least one unit entry is required */
+ if (!i) {
+ astman_send_error(s, m, "Invalid UnitAmount(0), At least one valid unit entry is required when ChargeType is set to Unit");
+ goto aocmessage_cleanup;
+ }
+
+ }
+
+ ast_aoc_set_billing_id(decoded, _billingid);
+ ast_aoc_set_total_type(decoded, _totaltype);
+
+
+ if ((encoded = ast_aoc_encode(decoded, &encoded_size, NULL)) && !ast_indicate_data(chan, AST_CONTROL_AOC, encoded, encoded_size)) {
+ astman_send_ack(s, m, "AOC Message successfully queued on channel");
+ } else {
+ astman_send_error(s, m, "Error encoding AOC message, could not queue onto channel");
+ }
+
+aocmessage_cleanup:
+
+ ast_aoc_destroy_decoded(decoded);
+ ast_aoc_destroy_encoded(encoded);
+
+ if (chan) {
+ chan = ast_channel_unref(chan);
+ }
+ return 0;
+}
+
static int action_originate(struct mansession *s, const struct message *m)
{
const char *name = astman_get_header(m, "Channel");
@@ -5665,6 +6002,7 @@ static int __init_manager(int reload)
ast_manager_register_xml("CoreShowChannels", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coreshowchannels);
ast_manager_register_xml("ModuleLoad", EVENT_FLAG_SYSTEM, manager_moduleload);
ast_manager_register_xml("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
+ ast_manager_register_xml("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);
ast_cli_register_multiple(cli_manager, ARRAY_LEN(cli_manager));
ast_extension_state_add(NULL, NULL, manager_state_cb, NULL);
diff --git a/tests/test_aoc.c b/tests/test_aoc.c
new file mode 100644
index 000000000..f6355e54f
--- /dev/null
+++ b/tests/test_aoc.c
@@ -0,0 +1,690 @@
+/*
+ * 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 encode decode tests
+ *
+ * \author David Vossel <dvossel@digium.com>
+ *
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/aoc.h"
+
+
+AST_TEST_DEFINE(aoc_event_generation_test)
+{
+ int res = AST_TEST_PASS;
+ struct ast_aoc_decoded *decoded = NULL;
+ struct ast_str *msg = NULL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "aoc_event_test";
+ info->category = "main/aoc/";
+ info->summary = "Advice of Charge event generation test";
+ info->description =
+ "Creates AOC messages, verify event string matches expected results";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (!(msg = ast_str_create(1024))) {
+ goto cleanup_aoc_event_test;
+ }
+
+ /* ---- TEST 1, AOC-D event generation */
+ if (!(decoded = ast_aoc_create(AST_AOC_D, AST_AOC_CHARGE_CURRENCY, 0))) {
+
+ ast_test_status_update(test, "failed to create AOC-D message for event generation.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+ /* Add billing id information */
+ ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CREDIT_CARD);
+
+ /* Set currency information, verify results */
+ if ((ast_aoc_set_currency_info(decoded, 100, AST_AOC_MULT_ONE, "usd")) ||
+ (ast_aoc_set_total_type(decoded, AST_AOC_SUBTOTAL))) {
+
+ ast_test_status_update(test, "failed to set currency info in AOC-D msg\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+
+ ast_test_status_update(test, "failed to generate AOC-D msg string.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+ if (strncmp(ast_str_buffer(msg),
+ "AOC-D\r\n"
+ "Type: Currency\r\n"
+ "BillingID: CreditCard\r\n"
+ "TypeOfCharging: SubTotal\r\n"
+ "Currency: usd\r\n"
+ "Currency/Amount/Cost: 100\r\n"
+ "Currency/Amount/Multiplier: 1\r\n",
+ strlen(ast_str_buffer(msg)))) {
+
+ ast_test_status_update(test, "AOC-D msg event did not match expected results\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+ decoded = ast_aoc_destroy_decoded(decoded);
+ ast_str_reset(msg);
+
+
+ /* ---- TEST 2, AOC-S event generation */
+ if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) {
+ ast_test_status_update(test, "failed to create AOC-S message for event generation.\n");
+
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ ast_aoc_s_add_rate_flat(decoded,
+ AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION,
+ 123,
+ AST_AOC_MULT_TEN,
+ "pineapple");
+
+ ast_aoc_s_add_rate_volume(decoded,
+ AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
+ AST_AOC_VOLUME_UNIT_SEGMENT,
+ 937,
+ AST_AOC_MULT_ONEHUNDREDTH,
+ "oranges");
+
+ ast_aoc_s_add_rate_duration(decoded,
+ AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
+ 937,
+ AST_AOC_MULT_ONETHOUSANDTH,
+ "bananas",
+ 848,
+ AST_AOC_TIME_SCALE_TENTH_SECOND,
+ 949,
+ AST_AOC_TIME_SCALE_HOUR,
+ 1);
+
+ ast_aoc_s_add_rate_duration(decoded,
+ AST_AOC_CHARGED_ITEM_USER_USER_INFO,
+ 937,
+ AST_AOC_MULT_THOUSAND,
+ "bananas",
+ 1111,
+ AST_AOC_TIME_SCALE_SECOND,
+ 2222,
+ AST_AOC_TIME_SCALE_DAY,
+ 0);
+
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+
+ ast_test_status_update(test, "failed to generate AOC-D msg string.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+
+ if (strncmp(ast_str_buffer(msg),
+ "AOC-S\r\n"
+ "NumberRates: 4\r\n"
+ "Rate(0)/Chargeable: BasicCommunication\r\n"
+ "Rate(0)/Type: Flat\r\n"
+ "Rate(0)/Flat/Currency: pineapple\r\n"
+ "Rate(0)/Flat/Amount/Cost: 123\r\n"
+ "Rate(0)/Flat/Amount/Multiplier: 10\r\n"
+ "Rate(1)/Chargeable: CallAttempt\r\n"
+ "Rate(1)/Type: Volume\r\n"
+ "Rate(1)/Volume/Currency: oranges\r\n"
+ "Rate(1)/Volume/Amount/Cost: 937\r\n"
+ "Rate(1)/Volume/Amount/Multiplier: 1/100\r\n"
+ "Rate(1)/Volume/Unit: Segment\r\n"
+ "Rate(2)/Chargeable: CallAttempt\r\n"
+ "Rate(2)/Type: Duration\r\n"
+ "Rate(2)/Duration/Currency: bananas\r\n"
+ "Rate(2)/Duration/Amount/Cost: 937\r\n"
+ "Rate(2)/Duration/Amount/Multiplier: 1/1000\r\n"
+ "Rate(2)/Duration/ChargingType: StepFunction\r\n"
+ "Rate(2)/Duration/Time/Length: 848\r\n"
+ "Rate(2)/Duration/Time/Scale: OneTenthSecond\r\n"
+ "Rate(2)/Duration/Granularity/Length: 949\r\n"
+ "Rate(2)/Duration/Granularity/Scale: Hour\r\n"
+ "Rate(3)/Chargeable: UserUserInfo\r\n"
+ "Rate(3)/Type: Duration\r\n"
+ "Rate(3)/Duration/Currency: bananas\r\n"
+ "Rate(3)/Duration/Amount/Cost: 937\r\n"
+ "Rate(3)/Duration/Amount/Multiplier: 1000\r\n"
+ "Rate(3)/Duration/ChargingType: ContinuousCharging\r\n"
+ "Rate(3)/Duration/Time/Length: 1111\r\n"
+ "Rate(3)/Duration/Time/Scale: Second\r\n"
+ "Rate(3)/Duration/Granularity/Length: 2222\r\n"
+ "Rate(3)/Duration/Granularity/Scale: Day\r\n",
+ strlen(ast_str_buffer(msg)))) {
+
+ ast_test_status_update(test, "AOC-S msg event did not match expected results\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+ decoded = ast_aoc_destroy_decoded(decoded);
+ ast_str_reset(msg);
+
+ /* ---- TEST 3, AOC-E event generation with various charging association information*/
+ if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_UNIT, 0))) {
+ ast_test_status_update(test, "failed to create AOC-E message for event generation.\n");
+
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+ if ((ast_aoc_add_unit_entry(decoded, 1, 111, 1, 1)) ||
+ (!ast_aoc_add_unit_entry(decoded, 0, 2222, 0, 2)) || /* we expect this to fail, and it should not be added to entry list */
+ (ast_aoc_add_unit_entry(decoded, 1, 3333, 0, 3)) ||
+ (ast_aoc_add_unit_entry(decoded, 0, 44444, 1, 4))) {
+
+ ast_test_status_update(test, "failed to set unit info for AOC-E message\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+ ast_test_status_update(test, "failed to generate AOC-E msg string.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ if (strncmp(ast_str_buffer(msg),
+ "AOC-E\r\n"
+ "Type: Units\r\n"
+ "BillingID: NotAvailable\r\n"
+ "Units/NumberItems: 3\r\n"
+ "Units/Item(0)/NumberOf: 111\r\n"
+ "Units/Item(0)/TypeOf: 1\r\n"
+ "Units/Item(1)/NumberOf: 3333\r\n"
+ "Units/Item(2)/TypeOf: 4\r\n",
+ strlen(ast_str_buffer(msg)))) {
+
+ ast_test_status_update(test, "AOC-E msg event did not match expected results, with no charging association info\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ /* add AOC-E charging association number info */
+ if (ast_aoc_set_association_number(decoded, "555-555-5555", 16)) {
+ ast_test_status_update(test, "failed to set the charging association number info correctly, 3\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ ast_str_reset(msg);
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+ ast_test_status_update(test, "failed to generate AOC-E msg string.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ if (strncmp(ast_str_buffer(msg),
+ "AOC-E\r\n"
+ "ChargingAssociation/Number: 555-555-5555\r\n"
+ "ChargingAssociation/Number/Plan: 16\r\n"
+ "Type: Units\r\n"
+ "BillingID: NotAvailable\r\n"
+ "Units/NumberItems: 3\r\n"
+ "Units/Item(0)/NumberOf: 111\r\n"
+ "Units/Item(0)/TypeOf: 1\r\n"
+ "Units/Item(1)/NumberOf: 3333\r\n"
+ "Units/Item(2)/TypeOf: 4\r\n",
+ strlen(ast_str_buffer(msg)))) {
+
+ ast_test_status_update(test, "AOC-E msg event did not match expected results, with charging association number\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ /* add AOC-E charging association id info */
+ if (ast_aoc_set_association_id(decoded, 2222)) {
+ ast_test_status_update(test, "failed to set the charging association number info correctly, 3\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ ast_str_reset(msg);
+ if (ast_aoc_decoded2str(decoded, &msg)) {
+ ast_test_status_update(test, "failed to generate AOC-E msg string.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+ if (strncmp(ast_str_buffer(msg),
+ "AOC-E\r\n"
+ "ChargingAssociation/ID: 2222\r\n"
+ "Type: Units\r\n"
+ "BillingID: NotAvailable\r\n"
+ "Units/NumberItems: 3\r\n"
+ "Units/Item(0)/NumberOf: 111\r\n"
+ "Units/Item(0)/TypeOf: 1\r\n"
+ "Units/Item(1)/NumberOf: 3333\r\n"
+ "Units/Item(2)/TypeOf: 4\r\n",
+ strlen(ast_str_buffer(msg)))) {
+
+ ast_test_status_update(test, "AOC-E msg event did not match expected results with charging association id.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_event_test;
+ }
+
+
+cleanup_aoc_event_test:
+
+ decoded = ast_aoc_destroy_decoded(decoded);
+ ast_free(msg);
+ return res;
+}
+
+AST_TEST_DEFINE(aoc_encode_decode_test)
+{
+ int res = AST_TEST_PASS;
+ struct ast_aoc_decoded *decoded;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "aoc_encode_decode_test";
+ info->category = "main/aoc/";
+ info->summary = "Advice of Charge encode and decode test";
+ info->description =
+ "This tests the Advice of Charge encode and decode routines.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ /* ---- Test 1 ---- create AOC-D message, encode message, and decode message once again. */
+ /* create AOC-D message */
+ if (!(decoded = ast_aoc_create(AST_AOC_D, AST_AOC_CHARGE_CURRENCY, 0)) ||
+ (ast_aoc_get_msg_type(decoded) != AST_AOC_D) ||
+ (ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_CURRENCY)) {
+
+ ast_test_status_update(test, "Test 1: failed to create AOC-D message\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* Add billing id information */
+ if ((ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NORMAL) ||
+ (ast_aoc_get_billing_id(decoded) != AST_AOC_BILLING_NORMAL))) {
+
+ ast_test_status_update(test, "TEST 1, could not set billing id correctly\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+
+ }
+
+ /* Set currency information, verify results*/
+ if ((ast_aoc_set_currency_info(decoded, 100, AST_AOC_MULT_ONE, "usd")) ||
+ (ast_aoc_set_total_type(decoded, AST_AOC_SUBTOTAL)) ||
+ (ast_aoc_get_total_type(decoded) != AST_AOC_SUBTOTAL) ||
+ (ast_aoc_get_currency_amount(decoded) != 100) ||
+ (ast_aoc_get_currency_multiplier(decoded) != AST_AOC_MULT_ONE) ||
+ (strcmp(ast_aoc_get_currency_name(decoded), "usd"))) {
+
+ ast_test_status_update(test, "Test 1: failed to set currency info\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* Set a currency name larger than 10 characters which is the the maximum
+ * length allowed by the ETSI aoc standard. The name is expected to truncate
+ * to 10 characters. */
+ if ((ast_aoc_set_currency_info(decoded, 100, AST_AOC_MULT_ONE, "12345678901234567890")) ||
+ (ast_aoc_get_currency_amount(decoded) != 100) ||
+ (ast_aoc_get_currency_multiplier(decoded) != AST_AOC_MULT_ONE) ||
+ (strcmp(ast_aoc_get_currency_name(decoded), "1234567890"))) {
+
+ ast_test_status_update(test, "Test 1: failed to set currency info currency name exceeding limit\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* Encode the message */
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test1: encode decode routine did not match expected results \n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+
+ /* ---- Test 2 ---- create AOC-E message with charge type == unit */
+ /* create AOC-E message */
+ if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_UNIT, 0)) ||
+ (ast_aoc_get_msg_type(decoded) != AST_AOC_E) ||
+ (ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_UNIT)) {
+
+ ast_test_status_update(test, "Test 2: failed to create AOC-E message\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* Set unit information, verify results*/
+ if ((ast_aoc_add_unit_entry(decoded, 1, 1, 1, 2)) ||
+ (!ast_aoc_add_unit_entry(decoded, 0, 123, 0, 123)) || /* this entry should fail since either number nor type are present */
+ (ast_aoc_add_unit_entry(decoded, 0, 2, 1, 3)) ||
+ (ast_aoc_add_unit_entry(decoded, 1, 3, 0, 4))) {
+
+ ast_test_status_update(test, "Test 2: failed to set unit info\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* verify unit list is correct */
+ if (ast_aoc_get_unit_count(decoded) == 3) {
+ int i;
+ const struct ast_aoc_unit_entry *unit;
+ for (i = 0; i < 3; i++) {
+ if (!(unit = ast_aoc_get_unit_info(decoded, i)) ||
+ ((unit->valid_amount) && (unit->amount != (i+1))) ||
+ ((unit->valid_type) && (unit->type != (i+2)))) {
+ ast_test_status_update(test, "TEST 2, invalid unit entry result, got %d,%d, expected %d,%d\n",
+ unit->amount,
+ unit->type,
+ i+1,
+ i+2);
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ }
+ } else {
+ ast_test_status_update(test, "TEST 2, invalid unit list entry count \n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+
+ /* Test charging association information */
+ {
+ const struct ast_aoc_charging_association *ca;
+ if ((ast_aoc_set_association_id(decoded, 1234)) ||
+ (!(ca = ast_aoc_get_association_info(decoded)))) {
+ ast_test_status_update(test, "TEST 2, could not set charging association id info correctly\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ if ((ca->charging_type != AST_AOC_CHARGING_ASSOCIATION_ID) || (ca->charge.id != 1234)) {
+ ast_test_status_update(test, "TEST 2, could not get charging association id info correctly, 2\n");
+ }
+
+ if ((ast_aoc_set_association_number(decoded, "1234", 16)) ||
+ (!(ca = ast_aoc_get_association_info(decoded)))) {
+ ast_test_status_update(test, "TEST 2, could not set charging association number info correctly, 3\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ if ((ca->charging_type != AST_AOC_CHARGING_ASSOCIATION_NUMBER) ||
+ (ca->charge.number.plan != 16) ||
+ (strcmp(ca->charge.number.number, "1234"))) {
+ ast_test_status_update(test, "TEST 2, could not get charging association number info correctly\n");
+ }
+ }
+
+ /* Test every billing id possiblity */
+ {
+ int billid[9] = {
+ AST_AOC_BILLING_NA,
+ AST_AOC_BILLING_NORMAL,
+ AST_AOC_BILLING_REVERSE_CHARGE,
+ AST_AOC_BILLING_CREDIT_CARD,
+ AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL,
+ AST_AOC_BILLING_CALL_FWD_BUSY,
+ AST_AOC_BILLING_CALL_FWD_NO_REPLY,
+ AST_AOC_BILLING_CALL_DEFLECTION,
+ AST_AOC_BILLING_CALL_TRANSFER,
+ };
+ int i;
+
+ /* these should fail */
+ if (!(ast_aoc_set_billing_id(decoded, (AST_AOC_BILLING_NA - 1))) ||
+ !(ast_aoc_set_billing_id(decoded, (AST_AOC_BILLING_CALL_TRANSFER + 1)))) {
+
+ ast_test_status_update(test, "TEST 2, setting invalid billing id should fail\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ for (i = 0; i < ARRAY_LEN(billid); i++) {
+ if ((ast_aoc_set_billing_id(decoded, billid[i]) ||
+ (ast_aoc_get_billing_id(decoded) != billid[i]))) {
+
+ ast_test_status_update(test, "TEST 2, could not set billing id correctly, iteration #%d\n", i);
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ }
+ }
+ /* Encode the message */
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test2: encode decode routine did not match expected results \n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+
+ /* ---- Test 3 ---- create AOC-Request. test all possible combinations */
+ {
+ int request[7] = { /* all possible request combinations */
+ AST_AOC_REQUEST_S,
+ AST_AOC_REQUEST_D,
+ AST_AOC_REQUEST_E,
+ (AST_AOC_REQUEST_S | AST_AOC_REQUEST_D),
+ (AST_AOC_REQUEST_S | AST_AOC_REQUEST_E),
+ (AST_AOC_REQUEST_D | AST_AOC_REQUEST_E),
+ (AST_AOC_REQUEST_D | AST_AOC_REQUEST_E | AST_AOC_REQUEST_S)
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_LEN(request); i++) {
+ if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, request[i])) ||
+ (ast_aoc_get_msg_type(decoded) != AST_AOC_REQUEST) ||
+ (ast_aoc_get_termination_request(decoded)) ||
+ (ast_aoc_get_request(decoded) != request[i])) {
+
+ ast_test_status_update(test, "Test 3: failed to create AOC-Request message, iteration #%d\n", i);
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* Encode the message */
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test3: encode decode routine did not match expected results, iteration #%d\n", i);
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+ }
+
+
+ /* Test termination Request Type */
+ if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, AST_AOC_REQUEST_E)) ||
+ (ast_aoc_set_termination_request(decoded)) ||
+ (!ast_aoc_get_termination_request(decoded)) ||
+ (ast_aoc_get_msg_type(decoded) != AST_AOC_REQUEST) ||
+ (ast_aoc_get_request(decoded) != AST_AOC_REQUEST_E)) {
+
+ ast_test_status_update(test, "Test 3: failed to create AOC-Request message with Termination Request set\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* Encode the message */
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test3: encode decode routine did not match expected results with termination request set\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+ }
+
+ /* ---- Test 4 ---- Make stuff blow up */
+ if ((decoded = ast_aoc_create(AST_AOC_D, 1234567, 0))) {
+
+ ast_test_status_update(test, "Test 4: aoc-d creation with no valid charge type should fail\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ if ((decoded = ast_aoc_create(AST_AOC_REQUEST, 0, 0))) {
+
+ ast_test_status_update(test, "Test 4: aoc request creation with no data should have failed\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ if ((decoded = ast_aoc_create(AST_AOC_REQUEST, -12345678, -23456789))) {
+
+ ast_test_status_update(test, "Test 4: aoc request creation with random data should have failed\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ /* ---- Test 5 ---- create AOC-E message with charge type == FREE and charge type == NA */
+ /* create AOC-E message */
+ if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_FREE, 0)) ||
+ (ast_aoc_get_msg_type(decoded) != AST_AOC_E) ||
+ (ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_FREE)) {
+
+ ast_test_status_update(test, "Test 5: failed to create AOC-E message, charge type Free\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test5: encode decode routine did not match expected results, charge type Free\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+
+ /* create AOC-E message */
+ if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_NA, 0)) ||
+ (ast_aoc_get_msg_type(decoded) != AST_AOC_E) ||
+ (ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_NA)) {
+
+ ast_test_status_update(test, "Test 5: failed to create AOC-E message, charge type NA\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test5: encode decode routine did not match expected results, charge type NA.\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+
+
+/* ---- TEST 6, AOC-S encode decode */
+ if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) {
+ ast_test_status_update(test, "failed to create AOC-S message for encode decode testing.\n");
+
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+
+ ast_aoc_s_add_rate_duration(decoded,
+ AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE,
+ 937,
+ AST_AOC_MULT_THOUSAND,
+ "jkasdf",
+ 235328,
+ AST_AOC_TIME_SCALE_SECOND,
+ 905423,
+ AST_AOC_TIME_SCALE_DAY,
+ 1);
+
+ ast_aoc_s_add_rate_flat(decoded,
+ AST_AOC_CHARGED_ITEM_CALL_SETUP,
+ 1337,
+ AST_AOC_MULT_ONEHUNDREDTH,
+ "MONEYS");
+
+ ast_aoc_s_add_rate_volume(decoded,
+ AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
+ AST_AOC_VOLUME_UNIT_SEGMENT,
+ 5555,
+ AST_AOC_MULT_ONEHUNDREDTH,
+ "pounds");
+
+ ast_aoc_s_add_rate_duration(decoded,
+ AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
+ 78923,
+ AST_AOC_MULT_ONETHOUSANDTH,
+ "SNAP",
+ 9354,
+ AST_AOC_TIME_SCALE_HUNDREDTH_SECOND,
+ 234933,
+ AST_AOC_TIME_SCALE_SECOND,
+ 0);
+
+ ast_aoc_s_add_rate_free(decoded, AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT, 1);
+ ast_aoc_s_add_rate_free(decoded, AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT, 0);
+ ast_aoc_s_add_rate_na(decoded, AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT);
+
+ if (ast_aoc_test_encode_decode_match(decoded)) {
+ ast_test_status_update(test, "Test6: encode decode routine for AOC-S did not match expected results\n");
+ res = AST_TEST_FAIL;
+ goto cleanup_aoc_test;
+ }
+ /* cleanup decoded msg */
+ decoded = ast_aoc_destroy_decoded(decoded);
+
+
+
+cleanup_aoc_test:
+
+ decoded = ast_aoc_destroy_decoded(decoded);
+ return res;
+}
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(aoc_encode_decode_test);
+ AST_TEST_UNREGISTER(aoc_event_generation_test);
+ return 0;
+}
+
+static int load_module(void)
+{
+ AST_TEST_REGISTER(aoc_encode_decode_test);
+ AST_TEST_REGISTER(aoc_event_generation_test);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "AOC unit tests");