dect
/
asterisk
Archived
13
0
Fork 0

Generic Advice of Charge.

Asterisk Generic AOC Representation
- Generic AOC encode/decode routines.
  (Generic AOC must be encoded to be passed on the wire in the AST_CONTROL_AOC frame)
- AST_CONTROL_AOC frame type to represent generic encoded AOC data
- Manager events for AOC-S, AOC-D, and AOC-E messages

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

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

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

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

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

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


git-svn-id: http://svn.digium.com/svn/asterisk/trunk@267096 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
rmudgett 2010-06-02 18:10:15 +00:00
parent 79137525f0
commit 245c5d9eb8
19 changed files with 4589 additions and 469 deletions

View File

@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/global_datastores.h"
#include "asterisk/dsp.h"
#include "asterisk/cel.h"
#include "asterisk/aoc.h"
#include "asterisk/ccss.h"
#include "asterisk/indications.h"
@ -638,6 +639,7 @@ struct chanlist {
struct ast_party_connected_line connected;
/*! TRUE if an AST_CONTROL_CONNECTED_LINE update was saved to the connected element. */
unsigned int pending_connected_update:1;
struct ast_aoc_decoded *aoc_s_rate_list;
};
static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str *featurecode);
@ -645,6 +647,7 @@ static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str
static void chanlist_free(struct chanlist *outgoing)
{
ast_party_connected_line_free(&outgoing->connected);
ast_aoc_destroy_decoded(outgoing->aoc_s_rate_list);
ast_free(outgoing);
}
@ -1053,6 +1056,14 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
ast_party_connected_line_free(&connected_caller);
}
}
if (o->aoc_s_rate_list) {
size_t encoded_size;
struct ast_aoc_encoded *encoded;
if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
ast_aoc_destroy_encoded(encoded);
}
}
peer = c;
ast_copy_flags64(peerflags, o,
OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
@ -1115,6 +1126,14 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
ast_party_connected_line_free(&connected_caller);
}
}
if (o->aoc_s_rate_list) {
size_t encoded_size;
struct ast_aoc_encoded *encoded;
if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
ast_aoc_destroy_encoded(encoded);
}
}
peer = c;
if (peer->cdr) {
peer->cdr->answer = ast_tvnow();
@ -1230,6 +1249,17 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
}
}
break;
case AST_CONTROL_AOC:
{
struct ast_aoc_decoded *decoded = ast_aoc_decode(f->data.ptr, f->datalen, o->chan);
if (decoded && (ast_aoc_get_msg_type(decoded) == AST_AOC_S)) {
ast_aoc_destroy_decoded(o->aoc_s_rate_list);
o->aoc_s_rate_list = decoded;
} else {
ast_aoc_destroy_decoded(decoded);
}
}
break;
case AST_CONTROL_REDIRECTING:
if (ast_test_flag64(peerflags, OPT_IGNORE_CONNECTEDLINE)) {
ast_verb(3, "Redirecting update to %s prevented.\n", in->name);

View File

@ -94,6 +94,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/strings.h"
#include "asterisk/global_datastores.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/aoc.h"
#include "asterisk/callerid.h"
#include "asterisk/cel.h"
#include "asterisk/data.h"
@ -820,6 +821,7 @@ struct callattempt {
unsigned int pending_connected_update:1;
/*! TRUE if caller id is not available for connected line */
unsigned int dial_callerid_absent:1;
struct ast_aoc_decoded *aoc_s_rate_list;
};
@ -2654,6 +2656,7 @@ static void hangupcalls(struct callattempt *outgoing, struct ast_channel *except
}
oo = outgoing;
outgoing = outgoing->q_next;
ast_aoc_destroy_decoded(oo->aoc_s_rate_list);
callattempt_free(oo);
}
}
@ -3335,6 +3338,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
ast_party_connected_line_free(&connected_caller);
}
}
if (o->aoc_s_rate_list) {
size_t encoded_size;
struct ast_aoc_encoded *encoded;
if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
ast_aoc_destroy_encoded(encoded);
}
}
peer = o;
}
} else if (o->chan && (o->chan == winner)) {
@ -3454,6 +3465,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
ast_party_connected_line_free(&connected_caller);
}
}
if (o->aoc_s_rate_list) {
size_t encoded_size;
struct ast_aoc_encoded *encoded;
if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
ast_aoc_destroy_encoded(encoded);
}
}
peer = o;
}
break;
@ -3523,6 +3542,17 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
}
}
break;
case AST_CONTROL_AOC:
{
struct ast_aoc_decoded *decoded = ast_aoc_decode(f->data.ptr, f->datalen, o->chan);
if (decoded && (ast_aoc_get_msg_type(decoded) == AST_AOC_S)) {
ast_aoc_destroy_decoded(o->aoc_s_rate_list);
o->aoc_s_rate_list = decoded;
} else {
ast_aoc_destroy_decoded(decoded);
}
}
break;
case AST_CONTROL_REDIRECTING:
if (!update_connectedline) {
ast_verb(3, "Redirecting update to %s prevented\n", inchan_name);

View File

@ -11779,6 +11779,10 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
#endif /* defined(HAVE_PRI_CCSS) */
pris[span].pri.transfer = conf->chan.transfer;
pris[span].pri.facilityenable = conf->pri.pri.facilityenable;
#if defined(HAVE_PRI_AOC_EVENTS)
pris[span].pri.aoc_passthrough_flag = conf->pri.pri.aoc_passthrough_flag;
pris[span].pri.aoce_delayhangup = conf->pri.pri.aoce_delayhangup;
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
ast_copy_string(pris[span].pri.msn_list, conf->pri.pri.msn_list, sizeof(pris[span].pri.msn_list));
ast_copy_string(pris[span].pri.idledial, conf->pri.pri.idledial, sizeof(pris[span].pri.idledial));
ast_copy_string(pris[span].pri.idleext, conf->pri.pri.idleext, sizeof(pris[span].pri.idleext));
@ -17195,6 +17199,21 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
#endif /* PRI_GETSET_TIMERS */
} else if (!strcasecmp(v->name, "facilityenable")) {
confp->pri.pri.facilityenable = ast_true(v->value);
#if defined(HAVE_PRI_AOC_EVENTS)
} else if (!strcasecmp(v->name, "aoc_enable")) {
confp->pri.pri.aoc_passthrough_flag = 0;
if (strchr(v->value, 's') || strchr(v->value, 'S')) {
confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_S;
}
if (strchr(v->value, 'd') || strchr(v->value, 'D')) {
confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_D;
}
if (strchr(v->value, 'e') || strchr(v->value, 'E')) {
confp->pri.pri.aoc_passthrough_flag |= SIG_PRI_AOC_GRANT_E;
}
} else if (!strcasecmp(v->name, "aoce_delayhangup")) {
confp->pri.pri.aoce_delayhangup = ast_true(v->value);
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_CALL_HOLD)
} else if (!strcasecmp(v->name, "hold_disconnect_transfer")) {
confp->pri.pri.hold_disconnect_transfer = ast_true(v->value);

View File

@ -261,6 +261,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/event.h"
#include "asterisk/stun.h"
#include "asterisk/cel.h"
#include "asterisk/aoc.h"
#include "sip/include/sip.h"
#include "sip/include/globals.h"
#include "sip/include/config_parser.h"
@ -804,7 +805,7 @@ static int apeerobjs = 0; /*!< Autocreated peer objects */
static int regobjs = 0; /*!< Registry objects */
/* }@ */
static struct ast_flags global_flags[2] = {{0}}; /*!< global SIP_ flags */
static struct ast_flags global_flags[3] = {{0}}; /*!< global SIP_ flags */
static int global_t38_maxdatagram; /*!< global T.38 FaxMaxDatagram override */
static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */
@ -1275,6 +1276,7 @@ static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqn
static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri);
static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri);
static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded);
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
static int transmit_info_with_vidupdate(struct sip_pvt *p);
static int transmit_message_with_text(struct sip_pvt *p, const char *text);
@ -4702,6 +4704,7 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
ast_copy_flags(&dialog->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
dialog->capability = peer->capability;
dialog->prefs = peer->prefs;
if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
@ -6249,6 +6252,41 @@ static int sip_indicate(struct ast_channel *ast, int condition, const void *data
case AST_CONTROL_REDIRECTING:
update_redirecting(p, data, datalen);
break;
case AST_CONTROL_AOC:
{
struct ast_aoc_decoded *decoded = ast_aoc_decode((struct ast_aoc_encoded *) data, datalen, ast);
if (!decoded) {
ast_log(LOG_ERROR, "Error decoding indicated AOC data\n");
res = -1;
break;
}
switch (ast_aoc_get_msg_type(decoded)) {
case AST_AOC_REQUEST:
if (ast_aoc_get_termination_request(decoded)) {
/* TODO, once there is a way to get AOC-E on hangup, attempt that here
* before hanging up the channel.*/
/* The other side has already initiated the hangup. This frame
* just says they are waiting to get AOC-E before completely tearing
* the call down. Since SIP does not support this at the moment go
* ahead and terminate the call here to avoid an unnecessary timeout. */
ast_log(LOG_DEBUG, "AOC-E termination request received on %s. This is not yet supported on sip. Continue with hangup \n", p->owner->name);
ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
}
break;
case AST_AOC_D:
case AST_AOC_E:
if (ast_test_flag(&p->flags[2], SIP_PAGE3_SNOM_AOC)) {
transmit_info_with_aoc(p, decoded);
}
break;
case AST_AOC_S: /* S not supported yet */
default:
break;
}
ast_aoc_destroy_decoded(decoded);
}
break;
case -1:
res = -1;
break;
@ -6888,6 +6926,7 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin,
/* Copy global flags to this PVT at setup. */
ast_copy_flags(&p->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY);
p->do_history = recordhistory;
@ -11856,6 +11895,53 @@ static int transmit_refer(struct sip_pvt *p, const char *dest)
*/
}
/*! \brief Send SIP INFO advice of charge message */
static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded)
{
struct sip_request req;
struct ast_str *str = ast_str_alloca(512);
const struct ast_aoc_unit_entry *unit_entry = ast_aoc_get_unit_info(decoded, 0);
enum ast_aoc_charge_type charging = ast_aoc_get_charge_type(decoded);
reqprep(&req, p, SIP_INFO, 0, 1);
if (ast_aoc_get_msg_type(decoded) == AST_AOC_D) {
ast_str_append(&str, 0, "type=active;");
} else if (ast_aoc_get_msg_type(decoded) == AST_AOC_E) {
ast_str_append(&str, 0, "type=terminated;");
} else {
/* unsupported message type */
return -1;
}
switch (charging) {
case AST_AOC_CHARGE_FREE:
ast_str_append(&str, 0, "free-of-charge;");
break;
case AST_AOC_CHARGE_CURRENCY:
ast_str_append(&str, 0, "charging;");
ast_str_append(&str, 0, "charging-info=currency;");
ast_str_append(&str, 0, "amount=%u;", ast_aoc_get_currency_amount(decoded));
ast_str_append(&str, 0, "multiplier=%s;", ast_aoc_get_currency_multiplier_decimal(decoded));
if (!ast_strlen_zero(ast_aoc_get_currency_name(decoded))) {
ast_str_append(&str, 0, "currency=%s;", ast_aoc_get_currency_name(decoded));
}
break;
case AST_AOC_CHARGE_UNIT:
ast_str_append(&str, 0, "charging;");
ast_str_append(&str, 0, "charging-info=pulse;");
if (unit_entry) {
ast_str_append(&str, 0, "recorded-units=%u;", unit_entry->amount);
}
break;
default:
ast_str_append(&str, 0, "not-available;");
};
add_header(&req, "AOC", ast_str_buffer(str));
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
}
/*! \brief Send SIP INFO dtmf message, see Cisco documentation on cisco.com */
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration)
@ -14037,6 +14123,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
/* Take the peer */
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) && p->udptl) {
p->t38_maxdatagram = peer->t38_maxdatagram;
@ -14081,6 +14168,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of,
if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable, req->ignore))) {
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
/* If we have a call limit, set flag */
if (peer->call_limit)
ast_set_flag(&p->flags[0], SIP_CALL_LIMIT);
@ -24000,6 +24088,7 @@ static int sip_poke_peer(struct sip_peer *peer, int force)
copy_socket_data(&p->socket, &peer->socket);
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
ast_copy_flags(&p->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY);
/* Send OPTIONs to peer's fullcontact */
if (!ast_strlen_zero(peer->fullcontact))
@ -24757,6 +24846,7 @@ static void set_peer_defaults(struct sip_peer *peer)
peer->type = SIP_TYPE_PEER;
ast_copy_flags(&peer->flags[0], &global_flags[0], SIP_FLAGS_TO_COPY);
ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_FLAGS_TO_COPY);
ast_copy_flags(&peer->flags[2], &global_flags[2], SIP_PAGE3_FLAGS_TO_COPY);
ast_string_field_set(peer, context, sip_cfg.default_context);
ast_string_field_set(peer, subscribecontext, sip_cfg.default_subscribecontext);
ast_string_field_set(peer, language, default_language);
@ -24863,8 +24953,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
int format = 0; /* Ama flags */
int timerb_set = 0, timert1_set = 0;
time_t regseconds = 0;
struct ast_flags peerflags[2] = {{(0)}};
struct ast_flags mask[2] = {{(0)}};
struct ast_flags peerflags[3] = {{(0)}};
struct ast_flags mask[3] = {{(0)}};
char callback[256] = "";
struct sip_peer tmp_peer;
const char *srvlookup = NULL;
@ -25255,6 +25345,8 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
ast_string_field_set(peer, unsolicited_mailbox, v->value);
} else if (!strcasecmp(v->name, "use_q850_reason")) {
ast_set2_flag(&peer->flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON);
} else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);
}
}
@ -25424,6 +25516,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
ast_copy_flags(&peer->flags[0], &peerflags[0], mask[0].flags);
ast_copy_flags(&peer->flags[1], &peerflags[1], mask[1].flags);
ast_copy_flags(&peer->flags[2], &peerflags[2], mask[2].flags);
if (ast_test_flag(&peer->flags[1], SIP_PAGE2_ALLOWSUBSCRIBE)) {
sip_cfg.allowsubscribe = TRUE; /* No global ban any more */
}
@ -26163,6 +26256,8 @@ static int reload_config(enum channelreloadreason reason)
}
} else if (!strcasecmp(v->name, "use_q850_reason")) {
ast_set2_flag(&global_flags[1], ast_true(v->value), SIP_PAGE2_Q850_REASON);
} else if (!strcasecmp(v->name, "snom_aoc_enabled")) {
ast_set2_flag(&global_flags[2], ast_true(v->value), SIP_PAGE3_SNOM_AOC);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,10 @@
#include <libpri.h>
#include <dahdi/user.h>
#define SIG_PRI_AOC_GRANT_S (1 << 0)
#define SIG_PRI_AOC_GRANT_D (1 << 1)
#define SIG_PRI_AOC_GRANT_E (1 << 2)
#if defined(HAVE_PRI_CCSS)
/*! PRI debug message flags when normal PRI debugging is turned on at the command line. */
#define SIG_PRI_DEBUG_NORMAL \
@ -192,6 +196,13 @@ struct sig_pri_chan {
char keypad_digits[AST_MAX_EXTENSION];
#endif /* defined(HAVE_PRI_SETUP_KEYPAD) */
#if defined(HAVE_PRI_AOC_EVENTS)
struct pri_subcmd_aoc_e aoc_e;
int aoc_s_request_invoke_id; /*!< If an AOC-S request was present for the call, this is the invoke_id to use for the response */
unsigned int aoc_s_request_invoke_id_valid:1; /*!< This is set when the AOC-S invoke id is present */
unsigned int waiting_for_aoce:1; /*!< Delaying hangup for AOC-E msg. If this is set and AOC-E is recieved, continue with hangup before timeout period. */
unsigned int holding_aoce:1; /*!< received AOC-E msg from asterisk. holding for disconnect/release */
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
unsigned int inalarm:1;
unsigned int alerting:1; /*!< TRUE if channel is alerting/ringing */
unsigned int alreadyhungup:1; /*!< TRUE if the call has already gone/hungup */
@ -243,6 +254,12 @@ struct sig_pri_pri {
int facilityenable; /*!< Enable facility IEs */
int dchan_logical_span[SIG_PRI_NUM_DCHANS]; /*!< Logical offset the DCHAN sits in */
int fds[SIG_PRI_NUM_DCHANS]; /*!< FD's for d-channels */
#if defined(HAVE_PRI_AOC_EVENTS)
int aoc_passthrough_flag; /*!< Represents what AOC messages (S,D,E) are allowed to pass-through */
int aoce_delayhangup:1; /*!< defines whether the aoce_delayhangup option is enabled or not */
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
#if defined(HAVE_PRI_SERVICE_MESSAGES)
unsigned int enable_service_message_support:1; /*!< enable SERVICE message support */
#endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */

View File

@ -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;

View File

@ -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:

View File

@ -107,7 +107,8 @@ bindaddr = 0.0.0.0
; originate - Permission to originate new calls. Write-only.
; agi - Output AGI commands executed. Input AGI command to execute.
; cc - Call Completion events. Read-only.
; aoc - Advice Of Charge events. Read-only.
; aoc - Permission to send Advice Of Charge messages and receive Advice
; - Of Charge events.
;
;read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
;write = system,call,agent,user,config,command,reporting,originate

View File

@ -886,6 +886,12 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
; destinations which do not have a prior
; account relationship with your server.
;------------------------------ Advice of Charge CONFIGURATION --------------------------
; snom_aoc_enabled = yes; ; This options turns on and off support for sending AOC-D and
; AOC-E to snom endpoints. This option can be used both in the
; peer and global scope. The default for this option is off.
;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of a
; SIP channel. Defaults to "no". An enabled jitterbuffer will

180
doc/advice_of_charge.txt Normal file
View File

@ -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

584
include/asterisk/aoc.h Normal file
View File

@ -0,0 +1,584 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2010, Digium, Inc.
*
* David Vossel <dvossel@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
* \brief Generic Advice of Charge encode and decode routines
*
* \author David Vossel <dvossel@digium.com>
*/
#ifndef _AST_AOC_H_
#define _AST_AOC_H_
#include "asterisk/channel.h"
#define AOC_CURRENCY_NAME_SIZE (10 + 1)
/*! \brief Defines the currency multiplier for an aoc message. */
enum ast_aoc_currency_multiplier {
AST_AOC_MULT_ONETHOUSANDTH = 1,
AST_AOC_MULT_ONEHUNDREDTH,
AST_AOC_MULT_ONETENTH,
AST_AOC_MULT_ONE,
AST_AOC_MULT_TEN,
AST_AOC_MULT_HUNDRED,
AST_AOC_MULT_THOUSAND,
AST_AOC_MULT_NUM_ENTRIES, /* must remain the last item in enum, this is not a valid type */
};
/*!
* \brief Defines the billing id options for an aoc message.
* \note AOC-D is limited to NORMAL, REVERSE_CHARGE, and CREDIT_CARD.
*/
enum ast_aoc_billing_id {
AST_AOC_BILLING_NA = 0,
AST_AOC_BILLING_NORMAL,
AST_AOC_BILLING_REVERSE_CHARGE,
AST_AOC_BILLING_CREDIT_CARD,
AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL,
AST_AOC_BILLING_CALL_FWD_BUSY,
AST_AOC_BILLING_CALL_FWD_NO_REPLY,
AST_AOC_BILLING_CALL_DEFLECTION,
AST_AOC_BILLING_CALL_TRANSFER,
AST_AOC_BILLING_NUM_ENTRIES /* must remain the last item in enum, not a valid billing id */
};
enum ast_aoc_type {
AST_AOC_REQUEST = 0,
AST_AOC_S,
AST_AOC_D,
AST_AOC_E, /* aoc-e must remain the last item in this enum */
};
enum ast_aoc_charge_type {
AST_AOC_CHARGE_NA = 0,
AST_AOC_CHARGE_FREE,
AST_AOC_CHARGE_CURRENCY,
AST_AOC_CHARGE_UNIT, /* unit must remain the last item in enum */
};
enum ast_aoc_request {
AST_AOC_REQUEST_S = (1 << 0),
AST_AOC_REQUEST_D = (1 << 1),
AST_AOC_REQUEST_E = (1 << 2),
};
enum ast_aoc_total_type {
AST_AOC_TOTAL = 0,
AST_AOC_SUBTOTAL = 1,
};
enum ast_aoc_time_scale {
AST_AOC_TIME_SCALE_HUNDREDTH_SECOND,
AST_AOC_TIME_SCALE_TENTH_SECOND,
AST_AOC_TIME_SCALE_SECOND,
AST_AOC_TIME_SCALE_TEN_SECOND,
AST_AOC_TIME_SCALE_MINUTE,
AST_AOC_TIME_SCALE_HOUR,
AST_AOC_TIME_SCALE_DAY,
};
struct ast_aoc_time {
/*! LengthOfTimeUnit (Not valid if length is zero.) */
uint32_t length;
uint16_t scale;
};
struct ast_aoc_duration_rate {
uint32_t amount;
uint32_t time;
/*! Not present if the granularity time is zero. */
uint32_t granularity_time;
uint16_t multiplier;
uint16_t time_scale;
uint16_t granularity_time_scale;
/*! Name of currency involved. Null terminated. */
char currency_name[AOC_CURRENCY_NAME_SIZE];
/*!
* \brief Charging interval type
* \details
* continuousCharging(0),
* stepFunction(1)
*/
uint8_t charging_type;
};
enum ast_aoc_volume_unit {
AST_AOC_VOLUME_UNIT_OCTET,
AST_AOC_VOLUME_UNIT_SEGMENT,
AST_AOC_VOLUME_UNIT_MESSAGE,
};
struct ast_aoc_volume_rate {
uint32_t amount;
uint16_t multiplier;
uint16_t volume_unit;
char currency_name[AOC_CURRENCY_NAME_SIZE];
};
struct ast_aoc_flat_rate {
uint32_t amount;
uint16_t multiplier;
/*! Name of currency involved. Null terminated. */
char currency_name[AOC_CURRENCY_NAME_SIZE];
};
enum ast_aoc_s_charged_item {
AST_AOC_CHARGED_ITEM_NA,
AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT,
AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION,
AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
AST_AOC_CHARGED_ITEM_CALL_SETUP,
AST_AOC_CHARGED_ITEM_USER_USER_INFO,
AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE,
};
enum ast_aoc_s_rate_type {
AST_AOC_RATE_TYPE_NA,
AST_AOC_RATE_TYPE_FREE,
AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING,
AST_AOC_RATE_TYPE_DURATION,
AST_AOC_RATE_TYPE_FLAT,
AST_AOC_RATE_TYPE_VOLUME,
AST_AOC_RATE_TYPE_SPECIAL_CODE,
};
struct ast_aoc_s_entry {
uint16_t charged_item;
uint16_t rate_type;
/*! \brief Charge rate being applied. */
union {
struct ast_aoc_duration_rate duration;
struct ast_aoc_flat_rate flat;
struct ast_aoc_volume_rate volume;
uint16_t special_code; /* 1...10 */
} rate;
} __attribute__((packed));
struct ast_aoc_unit_entry {
char valid_amount;
unsigned int amount;
char valid_type;
unsigned int type; /* 1 - 16 by ETSI standard */
};
enum AST_AOC_CHARGING_ASSOCIATION {
AST_AOC_CHARGING_ASSOCIATION_NA,
AST_AOC_CHARGING_ASSOCIATION_NUMBER,
AST_AOC_CHARGING_ASSOCIATION_ID,
};
struct ast_aoc_charging_association_number {
uint8_t plan;
char number[32];
} __attribute__((packed));
struct ast_aoc_charging_association {
union {
int32_t id;
struct ast_aoc_charging_association_number number;
} charge;
/*! \see enum AST_AOC_CHARGING_ASSOCIATION */
uint8_t charging_type;
} __attribute__((packed));
/*! \brief AOC Payload Header. Holds all the encoded AOC data to pass on the wire */
struct ast_aoc_encoded;
/*! \brief Decoded AOC data. This value is used to set all the values in an AOC message before encoding.*/
struct ast_aoc_decoded;
/*!
* \brief creates a ast_aoc_decode object of a specific message type
* \since 1.8
*
* \param msg_type AOC-D, AOC-E, or AOC Request
* \param charge_type this is ignored if message type is not AOC-D or AOC-E.
* \param requests flags. This defines the types of AOC requested. This
* field should only be set when the message type is AOC Request,
* the value is ignored otherwise.
*
* \retval heap allocated ast_aoc_decoded object ptr on success
* \retval NULL failure
*/
struct ast_aoc_decoded *ast_aoc_create(const enum ast_aoc_type msg_type,
const enum ast_aoc_charge_type charge_type,
const enum ast_aoc_request requests);
/*! \brief free an ast_aoc_decoded object */
void *ast_aoc_destroy_decoded(struct ast_aoc_decoded *decoded);
/*! \brief free an ast_aoc_encoded object */
void *ast_aoc_destroy_encoded(struct ast_aoc_encoded *encoded);
/*!
* \brief decodes an encoded aoc payload.
* \since 1.8
*
* \param encoded the encoded payload to decode.
* \param size total size of encoded payload
* \param chan ast channel, Optional for DEBUG output purposes
*
* \retval heap allocated ast_aoc_decoded object ptr on success
* \retval NULL failure
*/
struct ast_aoc_decoded *ast_aoc_decode(struct ast_aoc_encoded *encoded, size_t size, struct ast_channel *chan);
/*!
* \brief encodes a decoded aoc structure so it can be passed on the wire
* \since 1.8
*
* \param decoded the decoded struct to be encoded
* \param out_size output parameter representing size of encoded data
* \param chan ast channel, Optional for DEBUG output purposes
*
* \retval pointer to encoded data
* \retval NULL failure
*/
struct ast_aoc_encoded *ast_aoc_encode(struct ast_aoc_decoded *decoded, size_t *out_size, struct ast_channel *chan);
/*!
* \brief Sets the type of total for a AOC-D message
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
* \param type total type: TOTAL or SUBTOTAL
*
* \note If this value is not set, the default for the message is TOTAL
*
* \retval 0 success
*/
int ast_aoc_set_total_type(struct ast_aoc_decoded *decoded, const enum ast_aoc_total_type type);
/*!
* \brief Sets the currency values for a AOC-D or AOC-E message
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
* \param amount currency amount REQUIRED
* \param multiplier currency multiplier REQUIRED, 0 or undefined value defaults to AST_AOC_MULT_ONE.
* \param name currency name OPTIONAL
*
* \retval 0 success
*/
int ast_aoc_set_currency_info(struct ast_aoc_decoded *decoded,
const unsigned int amount,
const enum ast_aoc_currency_multiplier multiplier,
const char *name);
/*!
* \brief Adds a unit entry into the list of units
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
* \param amount_is_present set this if the number of units is actually present.
* \param amount number of units
* \param type_is_present set this if the type value is present
* \param type unit type
*
* \note If neither the amount nor the type is present, the entry will
* not be added.
*
* \retval 0 success
*/
int ast_aoc_add_unit_entry(struct ast_aoc_decoded *decoded,
const unsigned int amount_is_present,
const unsigned int amount,
const unsigned int type_is_present,
const unsigned int type);
/*!
* \brief set the billing id for a AOC-D or AST_AOC_E message
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
* \param id billing id
*
* \retval 0 success
*/
int ast_aoc_set_billing_id(struct ast_aoc_decoded *decoded, const enum ast_aoc_billing_id id);
/*!
* \brief set the charging association id for an AST_AOC_E message
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
* \param id charging association identifier
*
* \note If the association number was set, this will override that value. Only the id OR the
* number can be set at a time, not both.
*
* \retval 0 success
*/
int ast_aoc_set_association_id(struct ast_aoc_decoded *decoded, const int id);
/*!
* \brief set the charging accociation number for an AOC-E message
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
* \param num charging association number
* \param plan charging association number plan and type-of-number fields
*
* \note If the association id was set, this will override that value. Only the id OR the
* number can be set at a time, not both.
*
* \retval 0 success
*/
int ast_aoc_set_association_number(struct ast_aoc_decoded *decoded, const char *num, uint8_t plan);
/*!
* \brief Mark the AST_AOC_REQUEST message as a termination request.
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to set values on
*
* \note A termination request indicates that the call has terminated,
* but that the other side is waiting for a short period of time before
* hanging up so it can get the final AOC-E message.
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_set_termination_request(struct ast_aoc_decoded *decoded);
/*!
* \brief Add AOC-S duration rate entry
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param charged_item ast_aoc_s_charged_item
* \param amount currency amount
* \param multiplier currency multiplier
* \param currency_name truncated after 10 characters
* \param time
* \param time_scale from ast_aoc_time_scale enum
* \param granularity_time (optional, set to 0 if not present);
* \param granularity_time_scale (optional, set to 0 if not present);
* \param step_function set to 1 if this is to use a step function, 0 if continuious
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_rate_duration(struct ast_aoc_decoded *decoded,
enum ast_aoc_s_charged_item charged_item,
unsigned int amount,
enum ast_aoc_currency_multiplier multiplier,
const char *currency_name,
unsigned long time,
enum ast_aoc_time_scale time_scale,
unsigned long granularity_time,
enum ast_aoc_time_scale granularity_time_scale,
int step_function);
/*!
* \brief Add AOC-S flat rate entry
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param charged_item ast_aoc_s_charged_item
* \param amount currency amount
* \param multiplier currency multiplier
* \param currency_name truncated after 10 characters
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_rate_flat(struct ast_aoc_decoded *decoded,
enum ast_aoc_s_charged_item charged_item,
unsigned int amount,
enum ast_aoc_currency_multiplier multiplier,
const char *currency_name);
/*!
* \brief Add AOC-S volume rate entry
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param charged_item ast_aoc_s_charged_item
* \param volume_unit from ast_aoc_volume_unit enum
* \param amount currency amount
* \param multiplier currency multiplier
* \param currency_name truncated after 10 characters
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_rate_volume(struct ast_aoc_decoded *decoded,
enum ast_aoc_s_charged_item charged_item,
enum ast_aoc_volume_unit volume_unit,
unsigned int amount,
enum ast_aoc_currency_multiplier multiplier,
const char *currency_name);
/*!
* \brief Add AOC-S special rate entry
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param charged_item ast_aoc_s_charged_item
* \param code special charging code
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_rate_special_charge_code(struct ast_aoc_decoded *decoded,
enum ast_aoc_s_charged_item charged_item,
unsigned int code);
/*!
* \brief Add AOC-S indicating charge item is free
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param charged_item ast_aoc_s_charged_item
* \param from_beginning TRUE if the rate is free from beginning.
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_rate_free(struct ast_aoc_decoded *decoded,
enum ast_aoc_s_charged_item charged_item, int from_beginning);
/*!
* \brief Add AOC-S entry indicating charge item is not available
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param charged_item ast_aoc_s_charged_item
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_rate_na(struct ast_aoc_decoded *decoded,
enum ast_aoc_s_charged_item charged_item);
/*!
* \brief Add AOC-S special arrangement entry
* \since 1.8
*
* \param decoded aoc decoded object to add entry to
* \param code special arrangement code
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_s_add_special_arrangement(struct ast_aoc_decoded *decoded,
unsigned int code);
/*!
* \brief Convert decoded aoc msg to string representation
* \since 1.8
*
* \param decoded ast_aoc_decoded struct to convert to string
* \param msg dynamic heap allocated ast_str object to store string representation in
*
* \retval 0 success
* \retval -1 failure
*/
int ast_aoc_decoded2str(const struct ast_aoc_decoded *decoded, struct ast_str **msg);
/*! \brief generate AOC manager event for an AOC-S, AOC-D, or AOC-E msg */
int ast_aoc_manager_event(const struct ast_aoc_decoded *decoded, struct ast_channel *chan);
/*! \brief get the message type, AOC-D, AOC-E, or AOC Request */
enum ast_aoc_type ast_aoc_get_msg_type(struct ast_aoc_decoded *decoded);
/*! \brief get the charging type for an AOC-D or AOC-E message */
enum ast_aoc_charge_type ast_aoc_get_charge_type(struct ast_aoc_decoded *decoded);
/*! \brief get the types of AOC requested for when message type is AOC Request */
enum ast_aoc_request ast_aoc_get_request(struct ast_aoc_decoded *decoded);
/*! \brief get the type of total for a AOC-D message */
enum ast_aoc_total_type ast_aoc_get_total_type(struct ast_aoc_decoded *decoded);
/*! \brief get the currency amount for AOC-D and AOC-E messages*/
unsigned int ast_aoc_get_currency_amount(struct ast_aoc_decoded *decoded);
/*! \brief get the number rates associated with an AOC-S message */
unsigned int ast_aoc_s_get_count(struct ast_aoc_decoded *decoded);
/*!
* \brief get a specific AOC-S rate entry.
* \since 1.8
*
* \note This can be used in conjunction with ast_aoc_s_get_count to create
* a unit entry iterator.
*/
const struct ast_aoc_s_entry *ast_aoc_s_get_rate_info(struct ast_aoc_decoded *decoded, unsigned int entry_number);
/*! \brief get the number of unit entries for AOC-D and AOC-E messages*/
unsigned int ast_aoc_get_unit_count(struct ast_aoc_decoded *decoded);
/*!
* \brief get a specific unit entry.
* \since 1.8
*
* \note This can be used in conjunction with ast_aoc_get_unit_count to create
* a unit entry iterator.
*/
const struct ast_aoc_unit_entry *ast_aoc_get_unit_info(struct ast_aoc_decoded *decoded, unsigned int entry_number);
/*! \brief get the currency multiplier for AOC-D and AOC-E messages */
enum ast_aoc_currency_multiplier ast_aoc_get_currency_multiplier(struct ast_aoc_decoded *decoded);
/*! \brief get the currency multiplier for AOC-D and AOC-E messages in decimal format */
const char *ast_aoc_get_currency_multiplier_decimal(struct ast_aoc_decoded *decoded);
/*! \brief get the currency name for AOC-D and AOC-E messages*/
const char *ast_aoc_get_currency_name(struct ast_aoc_decoded *decoded);
/*! \brief get the billing id for AOC-D and AOC-E messages*/
enum ast_aoc_billing_id ast_aoc_get_billing_id(struct ast_aoc_decoded *decoded);
/*! \brief get the charging association info for AOC-E messages*/
const struct ast_aoc_charging_association *ast_aoc_get_association_info(struct ast_aoc_decoded *decoded);
/*!
* \brief get whether or not the AST_AOC_REQUEST message as a termination request.
* \since 1.8
*
* \note a termination request indicates that the call has terminated,
* but that the other side is waiting for a short period of time
* before hanging up so it can get the final AOC-E message.
*
* \param decoded ast_aoc_decoded struct to get values on
*
* \retval 0 not a termination request
* \retval 1 is a termination request
*/
int ast_aoc_get_termination_request(struct ast_aoc_decoded *decoded);
/*!
* \brief test aoc encode decode routines.
* \since 1.8
*
* \note This function verifies that a decoded message matches itself after
* the encode decode routine.
*/
int ast_aoc_test_encode_decode_match(struct ast_aoc_decoded *decoded);
/*! \brief enable aoc cli options */
int ast_aoc_cli_init(void);
#endif /* _AST_AOC_H_ */

View File

@ -327,6 +327,7 @@ enum ast_control_frame_type {
AST_CONTROL_CC = 25, /*!< Indication that Call completion service is possible */
AST_CONTROL_SRCCHANGE = 26, /*!< Media source has changed and requires a new RTP SSRC */
AST_CONTROL_READ_ACTION = 27, /*!< Tell ast_read to take a specific action */
AST_CONTROL_AOC = 28, /*!< Advice of Charge with encoded generic AOC payload */
};
enum ast_frame_read_action {

1607
main/aoc.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -142,6 +142,7 @@ int daemon(int, int); /* defined in libresolv of all places */
#include "asterisk/poll-compat.h"
#include "asterisk/ccss.h"
#include "asterisk/test.h"
#include "asterisk/aoc.h"
#include "../defaults.h"
@ -3602,6 +3603,8 @@ int main(int argc, char *argv[])
}
#endif
ast_aoc_cli_init();
ast_makesocket();
sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP);

View File

@ -3796,6 +3796,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con
case _XXX_AST_CONTROL_T38:
case AST_CONTROL_CC:
case AST_CONTROL_READ_ACTION:
case AST_CONTROL_AOC:
break;
case AST_CONTROL_CONGESTION:
@ -3941,6 +3942,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
case AST_CONTROL_REDIRECTING:
case AST_CONTROL_CC:
case AST_CONTROL_READ_ACTION:
case AST_CONTROL_AOC:
/* Nothing left to do for these. */
res = 0;
break;
@ -6003,6 +6005,9 @@ static enum ast_bridge_result ast_generic_bridge(struct ast_channel *c0, struct
int bridge_exit = 0;
switch (f->subclass.integer) {
case AST_CONTROL_AOC:
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
break;
case AST_CONTROL_REDIRECTING:
if (ast_channel_redirecting_macro(who, other, f, other == c0, 1)) {
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);

View File

@ -3208,6 +3208,7 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
}
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
break;
case AST_CONTROL_AOC:
case AST_CONTROL_HOLD:
case AST_CONTROL_UNHOLD:
ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);

View File

@ -74,6 +74,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/features.h"
#include "asterisk/security_events.h"
#include "asterisk/aoc.h"
/*** DOCUMENTATION
<manager name="Ping" language="en_US">
@ -694,6 +695,112 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
For success returns, the module revision number is included.</para>
</description>
</manager>
<manager name="AOCMessage" language="en_US">
<synopsis>
Generate an Advice of Charge message on a channel.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Channel" required="true">
<para>Channel name to generate the AOC message on.</para>
</parameter>
<parameter name="ChannelPrefix">
<para>Partial channel prefix. By using this option one can match the beginning part
of a channel name without having to put the entire name in. For example
if a channel name is SIP/snom-00000001 and this value is set to SIP/snom, then
that channel matches and the message will be sent. Note however that only
the first matched channel has the message sent on it. </para>
</parameter>
<parameter name="MsgType" required="true">
<para>Defines what type of AOC message to create, AOC-D or AOC-E</para>
<enumlist>
<enum name="D" />
<enum name="E" />
</enumlist>
</parameter>
<parameter name="ChargeType" required="true">
<para>Defines what kind of charge this message represents.</para>
<enumlist>
<enum name="NA" />
<enum name="FREE" />
<enum name="Currency" />
<enum name="Unit" />
</enumlist>
</parameter>
<parameter name="UnitAmount(0)">
<para>This represents the amount of units charged. The ETSI AOC standard specifies that
this value along with the optional UnitType value are entries in a list. To accommodate this
these values take an index value starting at 0 which can be used to generate this list of
unit entries. For Example, If two unit entires were required this could be achieved by setting the
paramter UnitAmount(0)=1234 and UnitAmount(1)=5678. Note that UnitAmount at index 0 is
required when ChargeType=Unit, all other entries in the list are optional.
</para>
</parameter>
<parameter name="UnitType(0)">
<para>Defines the type of unit. ETSI AOC standard specifies this as an integer
value between 1 and 16, but this value is left open to accept any positive
integer. Like the UnitAmount parameter, this value represents a list entry
and has an index parameter that starts at 0.
</para>
</parameter>
<parameter name="CurrencyName">
<para>Specifies the currency's name. Note that this value is truncated after 10 characters.</para>
</parameter>
<parameter name="CurrencyAmount">
<para>Specifies the charge unit amount as a positive integer. This value is required
when ChargeType==Currency.</para>
</parameter>
<parameter name="CurrencyMultiplier">
<para>Specifies the currency multiplier. This value is required when ChargeType==Currency.</para>
<enumlist>
<enum name="OneThousandth" />
<enum name="OneHundredth" />
<enum name="OneTenth" />
<enum name="One" />
<enum name="Ten" />
<enum name="Hundred" />
<enum name="Thousand" />
</enumlist>
</parameter>
<parameter name="TotalType" default="Total">
<para>Defines what kind of AOC-D total is represented.</para>
<enumlist>
<enum name="Total" />
<enum name="SubTotal" />
</enumlist>
</parameter>
<parameter name="AOCBillingId">
<para>Represents a billing ID associated with an AOC-D or AOC-E message. Note
that only the first 3 items of the enum are valid AOC-D billing IDs</para>
<enumlist>
<enum name="Normal" />
<enum name="ReverseCharge" />
<enum name="CreditCard" />
<enum name="CallFwdUnconditional" />
<enum name="CallFwdBusy" />
<enum name="CallFwdNoReply" />
<enum name="CallDeflection" />
<enum name="CallTransfer" />
</enumlist>
</parameter>
<parameter name="ChargingAssociationId">
<para>Charging association identifier. This is optional for AOC-E and can be
set to any value between -32768 and 32767</para>
</parameter>
<parameter name="ChargingAssociationNumber">
<para>Represents the charging association party number. This value is optional
for AOC-E.</para>
</parameter>
<parameter name="ChargingAssociationPlan">
<para>Integer representing the charging plan associated with the ChargingAssociationNumber.
The value is bits 7 through 1 of the Q.931 octet containing the type-of-number and
numbering-plan-identification fields.</para>
</parameter>
</syntax>
<description>
<para>Generates an AOC-D or AOC-E message on a channel.</para>
</description>
</manager>
***/
enum error_type {
@ -3396,6 +3503,236 @@ static void *fast_originate(void *data)
return NULL;
}
static int aocmessage_get_unit_entry(const struct message *m, struct ast_aoc_unit_entry *entry, unsigned int entry_num)
{
const char *unitamount;
const char *unittype;
struct ast_str *str = ast_str_alloca(32);
memset(entry, 0, sizeof(*entry));
ast_str_set(&str, 0, "UnitAmount(%u)", entry_num);
unitamount = astman_get_header(m, ast_str_buffer(str));
ast_str_set(&str, 0, "UnitType(%u)", entry_num);
unittype = astman_get_header(m, ast_str_buffer(str));
if (!ast_strlen_zero(unitamount) && (sscanf(unitamount, "%30u", &entry->amount) == 1)) {
entry->valid_amount = 1;
}
if (!ast_strlen_zero(unittype) && sscanf(unittype, "%30u", &entry->type) == 1) {
entry->valid_type = 1;
}
return 0;
}
static int action_aocmessage(struct mansession *s, const struct message *m)
{
const char *channel = astman_get_header(m, "Channel");
const char *pchannel = astman_get_header(m, "ChannelPrefix");
const char *msgtype = astman_get_header(m, "MsgType");
const char *chargetype = astman_get_header(m, "ChargeType");
const char *currencyname = astman_get_header(m, "CurrencyName");
const char *currencyamount = astman_get_header(m, "CurrencyAmount");
const char *mult = astman_get_header(m, "CurrencyMultiplier");
const char *totaltype = astman_get_header(m, "TotalType");
const char *aocbillingid = astman_get_header(m, "AOCBillingId");
const char *association_id= astman_get_header(m, "ChargingAssociationId");
const char *association_num = astman_get_header(m, "ChargingAssociationNumber");
const char *association_plan = astman_get_header(m, "ChargingAssociationPlan");
enum ast_aoc_type _msgtype;
enum ast_aoc_charge_type _chargetype;
enum ast_aoc_currency_multiplier _mult = AST_AOC_MULT_ONE;
enum ast_aoc_total_type _totaltype = AST_AOC_TOTAL;
enum ast_aoc_billing_id _billingid = AST_AOC_BILLING_NA;
unsigned int _currencyamount = 0;
int _association_id = 0;
unsigned int _association_plan = 0;
struct ast_channel *chan = NULL;
struct ast_aoc_decoded *decoded = NULL;
struct ast_aoc_encoded *encoded = NULL;
size_t encoded_size = 0;
if (ast_strlen_zero(channel) && ast_strlen_zero(pchannel)) {
astman_send_error(s, m, "Channel and PartialChannel are not specified. Specify at least one of these.");
goto aocmessage_cleanup;
}
if (!(chan = ast_channel_get_by_name(channel)) && !ast_strlen_zero(pchannel)) {
chan = ast_channel_get_by_name_prefix(pchannel, strlen(pchannel));
}
if (!chan) {
astman_send_error(s, m, "No such channel");
goto aocmessage_cleanup;
}
if (ast_strlen_zero(msgtype) || (strcasecmp(msgtype, "d") && strcasecmp(msgtype, "e"))) {
astman_send_error(s, m, "Invalid MsgType");
goto aocmessage_cleanup;
}
if (ast_strlen_zero(chargetype)) {
astman_send_error(s, m, "ChargeType not specified");
goto aocmessage_cleanup;
}
_msgtype = strcasecmp(msgtype, "d") ? AST_AOC_E : AST_AOC_D;
if (!strcasecmp(chargetype, "NA")) {
_chargetype = AST_AOC_CHARGE_NA;
} else if (!strcasecmp(chargetype, "Free")) {
_chargetype = AST_AOC_CHARGE_FREE;
} else if (!strcasecmp(chargetype, "Currency")) {
_chargetype = AST_AOC_CHARGE_CURRENCY;
} else if (!strcasecmp(chargetype, "Unit")) {
_chargetype = AST_AOC_CHARGE_UNIT;
} else {
astman_send_error(s, m, "Invalid ChargeType");
goto aocmessage_cleanup;
}
if (_chargetype == AST_AOC_CHARGE_CURRENCY) {
if (ast_strlen_zero(currencyamount) || (sscanf(currencyamount, "%30u", &_currencyamount) != 1)) {
astman_send_error(s, m, "Invalid CurrencyAmount, CurrencyAmount is a required when ChargeType is Currency");
goto aocmessage_cleanup;
}
if (ast_strlen_zero(mult)) {
astman_send_error(s, m, "ChargeMultiplier unspecified, ChargeMultiplier is required when ChargeType is Currency.");
goto aocmessage_cleanup;
} else if (!strcasecmp(mult, "onethousandth")) {
_mult = AST_AOC_MULT_ONETHOUSANDTH;
} else if (!strcasecmp(mult, "onehundredth")) {
_mult = AST_AOC_MULT_ONEHUNDREDTH;
} else if (!strcasecmp(mult, "onetenth")) {
_mult = AST_AOC_MULT_ONETENTH;
} else if (!strcasecmp(mult, "one")) {
_mult = AST_AOC_MULT_ONE;
} else if (!strcasecmp(mult, "ten")) {
_mult = AST_AOC_MULT_TEN;
} else if (!strcasecmp(mult, "hundred")) {
_mult = AST_AOC_MULT_HUNDRED;
} else if (!strcasecmp(mult, "thousand")) {
_mult = AST_AOC_MULT_THOUSAND;
} else {
astman_send_error(s, m, "Invalid ChargeMultiplier");
goto aocmessage_cleanup;
}
}
/* create decoded object and start setting values */
if (!(decoded = ast_aoc_create(_msgtype, _chargetype, 0))) {
astman_send_error(s, m, "Message Creation Failed");
goto aocmessage_cleanup;
}
if (_msgtype == AST_AOC_D) {
if (!ast_strlen_zero(totaltype) && !strcasecmp(totaltype, "subtotal")) {
_totaltype = AST_AOC_SUBTOTAL;
}
if (ast_strlen_zero(aocbillingid)) {
/* ignore this is optional */
} else if (!strcasecmp(aocbillingid, "Normal")) {
_billingid = AST_AOC_BILLING_NORMAL;
} else if (!strcasecmp(aocbillingid, "ReverseCharge")) {
_billingid = AST_AOC_BILLING_REVERSE_CHARGE;
} else if (!strcasecmp(aocbillingid, "CreditCard")) {
_billingid = AST_AOC_BILLING_CREDIT_CARD;
} else {
astman_send_error(s, m, "Invalid AOC-D AOCBillingId");
goto aocmessage_cleanup;
}
} else {
if (ast_strlen_zero(aocbillingid)) {
/* ignore this is optional */
} else if (!strcasecmp(aocbillingid, "Normal")) {
_billingid = AST_AOC_BILLING_NORMAL;
} else if (!strcasecmp(aocbillingid, "ReverseCharge")) {
_billingid = AST_AOC_BILLING_REVERSE_CHARGE;
} else if (!strcasecmp(aocbillingid, "CreditCard")) {
_billingid = AST_AOC_BILLING_CREDIT_CARD;
} else if (!strcasecmp(aocbillingid, "CallFwdUnconditional")) {
_billingid = AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL;
} else if (!strcasecmp(aocbillingid, "CallFwdBusy")) {
_billingid = AST_AOC_BILLING_CALL_FWD_BUSY;
} else if (!strcasecmp(aocbillingid, "CallFwdNoReply")) {
_billingid = AST_AOC_BILLING_CALL_FWD_NO_REPLY;
} else if (!strcasecmp(aocbillingid, "CallDeflection")) {
_billingid = AST_AOC_BILLING_CALL_DEFLECTION;
} else if (!strcasecmp(aocbillingid, "CallTransfer")) {
_billingid = AST_AOC_BILLING_CALL_TRANSFER;
} else {
astman_send_error(s, m, "Invalid AOC-E AOCBillingId");
goto aocmessage_cleanup;
}
if (!ast_strlen_zero(association_id) && (sscanf(association_id, "%30d", &_association_id) != 1)) {
astman_send_error(s, m, "Invalid ChargingAssociationId");
goto aocmessage_cleanup;
}
if (!ast_strlen_zero(association_plan) && (sscanf(association_plan, "%30u", &_association_plan) != 1)) {
astman_send_error(s, m, "Invalid ChargingAssociationPlan");
goto aocmessage_cleanup;
}
if (_association_id) {
ast_aoc_set_association_id(decoded, _association_id);
} else if (!ast_strlen_zero(association_num)) {
ast_aoc_set_association_number(decoded, association_num, _association_plan);
}
}
if (_chargetype == AST_AOC_CHARGE_CURRENCY) {
ast_aoc_set_currency_info(decoded, _currencyamount, _mult, ast_strlen_zero(currencyname) ? NULL : currencyname);
} else if (_chargetype == AST_AOC_CHARGE_UNIT) {
struct ast_aoc_unit_entry entry;
int i;
/* multiple unit entries are possible, lets get them all */
for (i = 0; i < 32; i++) {
if (aocmessage_get_unit_entry(m, &entry, i)) {
break; /* that's the end then */
}
ast_aoc_add_unit_entry(decoded, entry.valid_amount, entry.amount, entry.valid_type, entry.type);
}
/* at least one unit entry is required */
if (!i) {
astman_send_error(s, m, "Invalid UnitAmount(0), At least one valid unit entry is required when ChargeType is set to Unit");
goto aocmessage_cleanup;
}
}
ast_aoc_set_billing_id(decoded, _billingid);
ast_aoc_set_total_type(decoded, _totaltype);
if ((encoded = ast_aoc_encode(decoded, &encoded_size, NULL)) && !ast_indicate_data(chan, AST_CONTROL_AOC, encoded, encoded_size)) {
astman_send_ack(s, m, "AOC Message successfully queued on channel");
} else {
astman_send_error(s, m, "Error encoding AOC message, could not queue onto channel");
}
aocmessage_cleanup:
ast_aoc_destroy_decoded(decoded);
ast_aoc_destroy_encoded(encoded);
if (chan) {
chan = ast_channel_unref(chan);
}
return 0;
}
static int action_originate(struct mansession *s, const struct message *m)
{
const char *name = astman_get_header(m, "Channel");
@ -5665,6 +6002,7 @@ static int __init_manager(int reload)
ast_manager_register_xml("CoreShowChannels", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coreshowchannels);
ast_manager_register_xml("ModuleLoad", EVENT_FLAG_SYSTEM, manager_moduleload);
ast_manager_register_xml("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
ast_manager_register_xml("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);
ast_cli_register_multiple(cli_manager, ARRAY_LEN(cli_manager));
ast_extension_state_add(NULL, NULL, manager_state_cb, NULL);

690
tests/test_aoc.c Normal file
View File

@ -0,0 +1,690 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2010, Digium, Inc.
*
* David Vossel <dvossel@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
* \brief Generic AOC encode decode tests
*
* \author David Vossel <dvossel@digium.com>
*
* \ingroup tests
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/utils.h"
#include "asterisk/module.h"
#include "asterisk/test.h"
#include "asterisk/aoc.h"
AST_TEST_DEFINE(aoc_event_generation_test)
{
int res = AST_TEST_PASS;
struct ast_aoc_decoded *decoded = NULL;
struct ast_str *msg = NULL;
switch (cmd) {
case TEST_INIT:
info->name = "aoc_event_test";
info->category = "main/aoc/";
info->summary = "Advice of Charge event generation test";
info->description =
"Creates AOC messages, verify event string matches expected results";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
if (!(msg = ast_str_create(1024))) {
goto cleanup_aoc_event_test;
}
/* ---- TEST 1, AOC-D event generation */
if (!(decoded = ast_aoc_create(AST_AOC_D, AST_AOC_CHARGE_CURRENCY, 0))) {
ast_test_status_update(test, "failed to create AOC-D message for event generation.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
/* Add billing id information */
ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_CREDIT_CARD);
/* Set currency information, verify results */
if ((ast_aoc_set_currency_info(decoded, 100, AST_AOC_MULT_ONE, "usd")) ||
(ast_aoc_set_total_type(decoded, AST_AOC_SUBTOTAL))) {
ast_test_status_update(test, "failed to set currency info in AOC-D msg\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (ast_aoc_decoded2str(decoded, &msg)) {
ast_test_status_update(test, "failed to generate AOC-D msg string.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (strncmp(ast_str_buffer(msg),
"AOC-D\r\n"
"Type: Currency\r\n"
"BillingID: CreditCard\r\n"
"TypeOfCharging: SubTotal\r\n"
"Currency: usd\r\n"
"Currency/Amount/Cost: 100\r\n"
"Currency/Amount/Multiplier: 1\r\n",
strlen(ast_str_buffer(msg)))) {
ast_test_status_update(test, "AOC-D msg event did not match expected results\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
decoded = ast_aoc_destroy_decoded(decoded);
ast_str_reset(msg);
/* ---- TEST 2, AOC-S event generation */
if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) {
ast_test_status_update(test, "failed to create AOC-S message for event generation.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
ast_aoc_s_add_rate_flat(decoded,
AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION,
123,
AST_AOC_MULT_TEN,
"pineapple");
ast_aoc_s_add_rate_volume(decoded,
AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
AST_AOC_VOLUME_UNIT_SEGMENT,
937,
AST_AOC_MULT_ONEHUNDREDTH,
"oranges");
ast_aoc_s_add_rate_duration(decoded,
AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
937,
AST_AOC_MULT_ONETHOUSANDTH,
"bananas",
848,
AST_AOC_TIME_SCALE_TENTH_SECOND,
949,
AST_AOC_TIME_SCALE_HOUR,
1);
ast_aoc_s_add_rate_duration(decoded,
AST_AOC_CHARGED_ITEM_USER_USER_INFO,
937,
AST_AOC_MULT_THOUSAND,
"bananas",
1111,
AST_AOC_TIME_SCALE_SECOND,
2222,
AST_AOC_TIME_SCALE_DAY,
0);
if (ast_aoc_decoded2str(decoded, &msg)) {
ast_test_status_update(test, "failed to generate AOC-D msg string.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (strncmp(ast_str_buffer(msg),
"AOC-S\r\n"
"NumberRates: 4\r\n"
"Rate(0)/Chargeable: BasicCommunication\r\n"
"Rate(0)/Type: Flat\r\n"
"Rate(0)/Flat/Currency: pineapple\r\n"
"Rate(0)/Flat/Amount/Cost: 123\r\n"
"Rate(0)/Flat/Amount/Multiplier: 10\r\n"
"Rate(1)/Chargeable: CallAttempt\r\n"
"Rate(1)/Type: Volume\r\n"
"Rate(1)/Volume/Currency: oranges\r\n"
"Rate(1)/Volume/Amount/Cost: 937\r\n"
"Rate(1)/Volume/Amount/Multiplier: 1/100\r\n"
"Rate(1)/Volume/Unit: Segment\r\n"
"Rate(2)/Chargeable: CallAttempt\r\n"
"Rate(2)/Type: Duration\r\n"
"Rate(2)/Duration/Currency: bananas\r\n"
"Rate(2)/Duration/Amount/Cost: 937\r\n"
"Rate(2)/Duration/Amount/Multiplier: 1/1000\r\n"
"Rate(2)/Duration/ChargingType: StepFunction\r\n"
"Rate(2)/Duration/Time/Length: 848\r\n"
"Rate(2)/Duration/Time/Scale: OneTenthSecond\r\n"
"Rate(2)/Duration/Granularity/Length: 949\r\n"
"Rate(2)/Duration/Granularity/Scale: Hour\r\n"
"Rate(3)/Chargeable: UserUserInfo\r\n"
"Rate(3)/Type: Duration\r\n"
"Rate(3)/Duration/Currency: bananas\r\n"
"Rate(3)/Duration/Amount/Cost: 937\r\n"
"Rate(3)/Duration/Amount/Multiplier: 1000\r\n"
"Rate(3)/Duration/ChargingType: ContinuousCharging\r\n"
"Rate(3)/Duration/Time/Length: 1111\r\n"
"Rate(3)/Duration/Time/Scale: Second\r\n"
"Rate(3)/Duration/Granularity/Length: 2222\r\n"
"Rate(3)/Duration/Granularity/Scale: Day\r\n",
strlen(ast_str_buffer(msg)))) {
ast_test_status_update(test, "AOC-S msg event did not match expected results\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
decoded = ast_aoc_destroy_decoded(decoded);
ast_str_reset(msg);
/* ---- TEST 3, AOC-E event generation with various charging association information*/
if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_UNIT, 0))) {
ast_test_status_update(test, "failed to create AOC-E message for event generation.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if ((ast_aoc_add_unit_entry(decoded, 1, 111, 1, 1)) ||
(!ast_aoc_add_unit_entry(decoded, 0, 2222, 0, 2)) || /* we expect this to fail, and it should not be added to entry list */
(ast_aoc_add_unit_entry(decoded, 1, 3333, 0, 3)) ||
(ast_aoc_add_unit_entry(decoded, 0, 44444, 1, 4))) {
ast_test_status_update(test, "failed to set unit info for AOC-E message\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (ast_aoc_decoded2str(decoded, &msg)) {
ast_test_status_update(test, "failed to generate AOC-E msg string.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (strncmp(ast_str_buffer(msg),
"AOC-E\r\n"
"Type: Units\r\n"
"BillingID: NotAvailable\r\n"
"Units/NumberItems: 3\r\n"
"Units/Item(0)/NumberOf: 111\r\n"
"Units/Item(0)/TypeOf: 1\r\n"
"Units/Item(1)/NumberOf: 3333\r\n"
"Units/Item(2)/TypeOf: 4\r\n",
strlen(ast_str_buffer(msg)))) {
ast_test_status_update(test, "AOC-E msg event did not match expected results, with no charging association info\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
/* add AOC-E charging association number info */
if (ast_aoc_set_association_number(decoded, "555-555-5555", 16)) {
ast_test_status_update(test, "failed to set the charging association number info correctly, 3\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
ast_str_reset(msg);
if (ast_aoc_decoded2str(decoded, &msg)) {
ast_test_status_update(test, "failed to generate AOC-E msg string.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (strncmp(ast_str_buffer(msg),
"AOC-E\r\n"
"ChargingAssociation/Number: 555-555-5555\r\n"
"ChargingAssociation/Number/Plan: 16\r\n"
"Type: Units\r\n"
"BillingID: NotAvailable\r\n"
"Units/NumberItems: 3\r\n"
"Units/Item(0)/NumberOf: 111\r\n"
"Units/Item(0)/TypeOf: 1\r\n"
"Units/Item(1)/NumberOf: 3333\r\n"
"Units/Item(2)/TypeOf: 4\r\n",
strlen(ast_str_buffer(msg)))) {
ast_test_status_update(test, "AOC-E msg event did not match expected results, with charging association number\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
/* add AOC-E charging association id info */
if (ast_aoc_set_association_id(decoded, 2222)) {
ast_test_status_update(test, "failed to set the charging association number info correctly, 3\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
ast_str_reset(msg);
if (ast_aoc_decoded2str(decoded, &msg)) {
ast_test_status_update(test, "failed to generate AOC-E msg string.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
if (strncmp(ast_str_buffer(msg),
"AOC-E\r\n"
"ChargingAssociation/ID: 2222\r\n"
"Type: Units\r\n"
"BillingID: NotAvailable\r\n"
"Units/NumberItems: 3\r\n"
"Units/Item(0)/NumberOf: 111\r\n"
"Units/Item(0)/TypeOf: 1\r\n"
"Units/Item(1)/NumberOf: 3333\r\n"
"Units/Item(2)/TypeOf: 4\r\n",
strlen(ast_str_buffer(msg)))) {
ast_test_status_update(test, "AOC-E msg event did not match expected results with charging association id.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_event_test;
}
cleanup_aoc_event_test:
decoded = ast_aoc_destroy_decoded(decoded);
ast_free(msg);
return res;
}
AST_TEST_DEFINE(aoc_encode_decode_test)
{
int res = AST_TEST_PASS;
struct ast_aoc_decoded *decoded;
switch (cmd) {
case TEST_INIT:
info->name = "aoc_encode_decode_test";
info->category = "main/aoc/";
info->summary = "Advice of Charge encode and decode test";
info->description =
"This tests the Advice of Charge encode and decode routines.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
/* ---- Test 1 ---- create AOC-D message, encode message, and decode message once again. */
/* create AOC-D message */
if (!(decoded = ast_aoc_create(AST_AOC_D, AST_AOC_CHARGE_CURRENCY, 0)) ||
(ast_aoc_get_msg_type(decoded) != AST_AOC_D) ||
(ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_CURRENCY)) {
ast_test_status_update(test, "Test 1: failed to create AOC-D message\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Add billing id information */
if ((ast_aoc_set_billing_id(decoded, AST_AOC_BILLING_NORMAL) ||
(ast_aoc_get_billing_id(decoded) != AST_AOC_BILLING_NORMAL))) {
ast_test_status_update(test, "TEST 1, could not set billing id correctly\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Set currency information, verify results*/
if ((ast_aoc_set_currency_info(decoded, 100, AST_AOC_MULT_ONE, "usd")) ||
(ast_aoc_set_total_type(decoded, AST_AOC_SUBTOTAL)) ||
(ast_aoc_get_total_type(decoded) != AST_AOC_SUBTOTAL) ||
(ast_aoc_get_currency_amount(decoded) != 100) ||
(ast_aoc_get_currency_multiplier(decoded) != AST_AOC_MULT_ONE) ||
(strcmp(ast_aoc_get_currency_name(decoded), "usd"))) {
ast_test_status_update(test, "Test 1: failed to set currency info\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Set a currency name larger than 10 characters which is the the maximum
* length allowed by the ETSI aoc standard. The name is expected to truncate
* to 10 characters. */
if ((ast_aoc_set_currency_info(decoded, 100, AST_AOC_MULT_ONE, "12345678901234567890")) ||
(ast_aoc_get_currency_amount(decoded) != 100) ||
(ast_aoc_get_currency_multiplier(decoded) != AST_AOC_MULT_ONE) ||
(strcmp(ast_aoc_get_currency_name(decoded), "1234567890"))) {
ast_test_status_update(test, "Test 1: failed to set currency info currency name exceeding limit\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Encode the message */
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test1: encode decode routine did not match expected results \n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
/* ---- Test 2 ---- create AOC-E message with charge type == unit */
/* create AOC-E message */
if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_UNIT, 0)) ||
(ast_aoc_get_msg_type(decoded) != AST_AOC_E) ||
(ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_UNIT)) {
ast_test_status_update(test, "Test 2: failed to create AOC-E message\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Set unit information, verify results*/
if ((ast_aoc_add_unit_entry(decoded, 1, 1, 1, 2)) ||
(!ast_aoc_add_unit_entry(decoded, 0, 123, 0, 123)) || /* this entry should fail since either number nor type are present */
(ast_aoc_add_unit_entry(decoded, 0, 2, 1, 3)) ||
(ast_aoc_add_unit_entry(decoded, 1, 3, 0, 4))) {
ast_test_status_update(test, "Test 2: failed to set unit info\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* verify unit list is correct */
if (ast_aoc_get_unit_count(decoded) == 3) {
int i;
const struct ast_aoc_unit_entry *unit;
for (i = 0; i < 3; i++) {
if (!(unit = ast_aoc_get_unit_info(decoded, i)) ||
((unit->valid_amount) && (unit->amount != (i+1))) ||
((unit->valid_type) && (unit->type != (i+2)))) {
ast_test_status_update(test, "TEST 2, invalid unit entry result, got %d,%d, expected %d,%d\n",
unit->amount,
unit->type,
i+1,
i+2);
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
}
} else {
ast_test_status_update(test, "TEST 2, invalid unit list entry count \n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Test charging association information */
{
const struct ast_aoc_charging_association *ca;
if ((ast_aoc_set_association_id(decoded, 1234)) ||
(!(ca = ast_aoc_get_association_info(decoded)))) {
ast_test_status_update(test, "TEST 2, could not set charging association id info correctly\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
if ((ca->charging_type != AST_AOC_CHARGING_ASSOCIATION_ID) || (ca->charge.id != 1234)) {
ast_test_status_update(test, "TEST 2, could not get charging association id info correctly, 2\n");
}
if ((ast_aoc_set_association_number(decoded, "1234", 16)) ||
(!(ca = ast_aoc_get_association_info(decoded)))) {
ast_test_status_update(test, "TEST 2, could not set charging association number info correctly, 3\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
if ((ca->charging_type != AST_AOC_CHARGING_ASSOCIATION_NUMBER) ||
(ca->charge.number.plan != 16) ||
(strcmp(ca->charge.number.number, "1234"))) {
ast_test_status_update(test, "TEST 2, could not get charging association number info correctly\n");
}
}
/* Test every billing id possiblity */
{
int billid[9] = {
AST_AOC_BILLING_NA,
AST_AOC_BILLING_NORMAL,
AST_AOC_BILLING_REVERSE_CHARGE,
AST_AOC_BILLING_CREDIT_CARD,
AST_AOC_BILLING_CALL_FWD_UNCONDITIONAL,
AST_AOC_BILLING_CALL_FWD_BUSY,
AST_AOC_BILLING_CALL_FWD_NO_REPLY,
AST_AOC_BILLING_CALL_DEFLECTION,
AST_AOC_BILLING_CALL_TRANSFER,
};
int i;
/* these should fail */
if (!(ast_aoc_set_billing_id(decoded, (AST_AOC_BILLING_NA - 1))) ||
!(ast_aoc_set_billing_id(decoded, (AST_AOC_BILLING_CALL_TRANSFER + 1)))) {
ast_test_status_update(test, "TEST 2, setting invalid billing id should fail\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
for (i = 0; i < ARRAY_LEN(billid); i++) {
if ((ast_aoc_set_billing_id(decoded, billid[i]) ||
(ast_aoc_get_billing_id(decoded) != billid[i]))) {
ast_test_status_update(test, "TEST 2, could not set billing id correctly, iteration #%d\n", i);
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
}
}
/* Encode the message */
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test2: encode decode routine did not match expected results \n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
/* ---- Test 3 ---- create AOC-Request. test all possible combinations */
{
int request[7] = { /* all possible request combinations */
AST_AOC_REQUEST_S,
AST_AOC_REQUEST_D,
AST_AOC_REQUEST_E,
(AST_AOC_REQUEST_S | AST_AOC_REQUEST_D),
(AST_AOC_REQUEST_S | AST_AOC_REQUEST_E),
(AST_AOC_REQUEST_D | AST_AOC_REQUEST_E),
(AST_AOC_REQUEST_D | AST_AOC_REQUEST_E | AST_AOC_REQUEST_S)
};
int i;
for (i = 0; i < ARRAY_LEN(request); i++) {
if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, request[i])) ||
(ast_aoc_get_msg_type(decoded) != AST_AOC_REQUEST) ||
(ast_aoc_get_termination_request(decoded)) ||
(ast_aoc_get_request(decoded) != request[i])) {
ast_test_status_update(test, "Test 3: failed to create AOC-Request message, iteration #%d\n", i);
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Encode the message */
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test3: encode decode routine did not match expected results, iteration #%d\n", i);
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
}
/* Test termination Request Type */
if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, AST_AOC_REQUEST_E)) ||
(ast_aoc_set_termination_request(decoded)) ||
(!ast_aoc_get_termination_request(decoded)) ||
(ast_aoc_get_msg_type(decoded) != AST_AOC_REQUEST) ||
(ast_aoc_get_request(decoded) != AST_AOC_REQUEST_E)) {
ast_test_status_update(test, "Test 3: failed to create AOC-Request message with Termination Request set\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* Encode the message */
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test3: encode decode routine did not match expected results with termination request set\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
}
/* ---- Test 4 ---- Make stuff blow up */
if ((decoded = ast_aoc_create(AST_AOC_D, 1234567, 0))) {
ast_test_status_update(test, "Test 4: aoc-d creation with no valid charge type should fail\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
if ((decoded = ast_aoc_create(AST_AOC_REQUEST, 0, 0))) {
ast_test_status_update(test, "Test 4: aoc request creation with no data should have failed\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
if ((decoded = ast_aoc_create(AST_AOC_REQUEST, -12345678, -23456789))) {
ast_test_status_update(test, "Test 4: aoc request creation with random data should have failed\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* ---- Test 5 ---- create AOC-E message with charge type == FREE and charge type == NA */
/* create AOC-E message */
if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_FREE, 0)) ||
(ast_aoc_get_msg_type(decoded) != AST_AOC_E) ||
(ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_FREE)) {
ast_test_status_update(test, "Test 5: failed to create AOC-E message, charge type Free\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test5: encode decode routine did not match expected results, charge type Free\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
/* create AOC-E message */
if (!(decoded = ast_aoc_create(AST_AOC_E, AST_AOC_CHARGE_NA, 0)) ||
(ast_aoc_get_msg_type(decoded) != AST_AOC_E) ||
(ast_aoc_get_charge_type(decoded) != AST_AOC_CHARGE_NA)) {
ast_test_status_update(test, "Test 5: failed to create AOC-E message, charge type NA\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test5: encode decode routine did not match expected results, charge type NA.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
/* ---- TEST 6, AOC-S encode decode */
if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) {
ast_test_status_update(test, "failed to create AOC-S message for encode decode testing.\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
ast_aoc_s_add_rate_duration(decoded,
AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE,
937,
AST_AOC_MULT_THOUSAND,
"jkasdf",
235328,
AST_AOC_TIME_SCALE_SECOND,
905423,
AST_AOC_TIME_SCALE_DAY,
1);
ast_aoc_s_add_rate_flat(decoded,
AST_AOC_CHARGED_ITEM_CALL_SETUP,
1337,
AST_AOC_MULT_ONEHUNDREDTH,
"MONEYS");
ast_aoc_s_add_rate_volume(decoded,
AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
AST_AOC_VOLUME_UNIT_SEGMENT,
5555,
AST_AOC_MULT_ONEHUNDREDTH,
"pounds");
ast_aoc_s_add_rate_duration(decoded,
AST_AOC_CHARGED_ITEM_CALL_ATTEMPT,
78923,
AST_AOC_MULT_ONETHOUSANDTH,
"SNAP",
9354,
AST_AOC_TIME_SCALE_HUNDREDTH_SECOND,
234933,
AST_AOC_TIME_SCALE_SECOND,
0);
ast_aoc_s_add_rate_free(decoded, AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT, 1);
ast_aoc_s_add_rate_free(decoded, AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT, 0);
ast_aoc_s_add_rate_na(decoded, AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT);
if (ast_aoc_test_encode_decode_match(decoded)) {
ast_test_status_update(test, "Test6: encode decode routine for AOC-S did not match expected results\n");
res = AST_TEST_FAIL;
goto cleanup_aoc_test;
}
/* cleanup decoded msg */
decoded = ast_aoc_destroy_decoded(decoded);
cleanup_aoc_test:
decoded = ast_aoc_destroy_decoded(decoded);
return res;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(aoc_encode_decode_test);
AST_TEST_UNREGISTER(aoc_event_generation_test);
return 0;
}
static int load_module(void)
{
AST_TEST_REGISTER(aoc_encode_decode_test);
AST_TEST_REGISTER(aoc_event_generation_test);
return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "AOC unit tests");