Generic Advice of Charge.
authorRichard Mudgett <rmudgett@digium.com>
Wed, 2 Jun 2010 18:10:15 +0000 (18:10 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Wed, 2 Jun 2010 18:10:15 +0000 (18:10 +0000)
Asterisk Generic AOC Representation
- Generic AOC encode/decode routines.
  (Generic AOC must be encoded to be passed on the wire in the AST_CONTROL_AOC frame)
- AST_CONTROL_AOC frame type to represent generic encoded AOC data
- Manager events for AOC-S, AOC-D, and AOC-E messages

Asterisk App Support
- app_dial AOC-S pass-through support on call setup
- app_queue AOC-S pass-through support on call setup

AOC Unit Tests
- AOC Unit Tests for encode/decode routines
- AOC Unit Test for manager event representation.

SIP AOC Support
- Pass-through of generic AOC-D and AOC-E messages to snom phones via the
  snom AOC specification.
- Creation of chan_sip page3 flags for the addition of the new
  'snom_aoc_enabled' sip.conf option.

IAX AOC Support
- Natively supports AOC pass-through through the use of the new
  AST_CONTROL_AOC frame type

DAHDI AOC Support
- ETSI PRI full AOC Pass-through support
- 'aoc_enable' chan_dahdi.conf option for independently enabling
  pass-through of AOC-S, AOC-D, AOC-E.
- 'aoce_delayhangup' option for retrieving AOC-E on disconnect.
- DAHDI A() dial string option for requesting AOC services.
  example usage:
  ;requests AOC-S, AOC-D, and AOC-E on call setup
  exten=>1111,1,Dial(DAHDI/g1/1112/A(s,d,e))

Review: https://reviewboard.asterisk.org/r/552/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@267096 65c4cc65-6c06-0410-ace0-fbb531ad65f3

19 files changed:
apps/app_dial.c
apps/app_queue.c
channels/chan_dahdi.c
channels/chan_sip.c
channels/sig_pri.c
channels/sig_pri.h
channels/sip/include/sip.h
configs/chan_dahdi.conf.sample
configs/manager.conf.sample
configs/sip.conf.sample
doc/advice_of_charge.txt [new file with mode: 0644]
include/asterisk/aoc.h [new file with mode: 0644]
include/asterisk/frame.h
main/aoc.c [new file with mode: 0644]
main/asterisk.c
main/channel.c
main/features.c
main/manager.c
tests/test_aoc.c [new file with mode: 0644]

index 1422a4e..d813c00 100644 (file)
@@ -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);
index 68146dd..1fdd97c 100644 (file)
@@ -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);
index 2d28c6f..926daff 100644 (file)
@@ -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);
index eed3645..6c4cba7 100644 (file)
@@ -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);
                }
        }
 
index 44f5d23..2fda977 100644 (file)
@@ -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);
index efd3523..64c915b 100644 (file)
 #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) */
index c528754..0ee377c 100644 (file)
        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;
index ac4008d..57b0fe0 100644 (file)
 ;
 ;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:
index 3666854..c6536bc 100644 (file)
@@ -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
index a9ed6fa..d8ae62d 100644 (file)
@@ -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 (file)
index 0000000..bb090ec
--- /dev/null
@@ -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 (file)
index 0000000..727362c
--- /dev/null
@@ -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_ */
index 4198fad..bfd92fa 100644 (file)
@@ -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 (file)
index 0000000..c7cc8cf
--- /dev/null
@@ -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));
+}
index 4b09d1e..1e13729 100644 (file)
@@ -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);
index 999e39b..3057fae 100644 (file)
@@ -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);
index 72a626e..6c85241 100644 (file)
@@ -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);
index 43be08e..b0b2d37 100644 (file)
@@ -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 (file)
index 0000000..f6355e5
--- /dev/null
@@ -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");