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