From: Matthew Jordan Date: Mon, 17 Jun 2013 03:00:38 +0000 (+0000) Subject: Update Asterisk's CDRs for the new bridging framework X-Git-Tag: 13.0.0-beta1~1661 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6258bbe7;p=thirdparty%2Fasterisk.git Update Asterisk's CDRs for the new bridging framework This patch is the initial push to update Asterisk's CDR engine for the new bridging framework. This patch guts the existing CDR engine and builds the new on top of messages coming across Stasis. As changes in channel state and bridge state are detected, CDRs are built and dispatched accordingly. This fundamentally changes CDRs in a few ways. (1) CDRs are now *very* reflective of the actual state of channels and bridges. This means CDRs track well with what an actual channel is doing - which is useful in transfer scenarios (which were previously difficult to pin down). It does, however, mean that CDRs cannot be 'fooled'. Previous behavior in Asterisk allowed for CDR applications, channels, and other properties to be spoofed in parts of the code - this no longer works. (2) CDRs have defined behavior in multi-party scenarios. This behavior will not be what everyone wants, but it is a defined behavior and as such, it is predictable. (3) The CDR manipulation functions and applications have been overhauled. Major changes have been made to ResetCDR and ForkCDR in particular. Many of the options for these two applications no longer made any sense with the new framework and the (slightly) more immutable nature of CDRs. There are a plethora of other changes. For a full description of CDR behavior, see the CDR specification on the Asterisk wiki. (closes issue ASTERISK-21196) Review: https://reviewboard.asterisk.org/r/2486/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@391947 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- diff --git a/CHANGES b/CHANGES index f3d420bc8f..b81c2da287 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,94 @@ --- Functionality changes from Asterisk 11 to Asterisk 12 -------------------- ------------------------------------------------------------------------------ +Applications +------------------ + +AgentMonitorOutgoing +------------------ + * The 'c' option has been removed. It is not possible to modify the name of a + channel involved in a CDR. + +ForkCDR +------------------ + * ForkCDR no longer automatically resets the forked CDR. See the 'r' option + for more information. + + * Variables are no longer purged from the original CDR. See the 'v' option for + more information. + + * The 'A' option has been removed. The Answer time on a CDR is never updated + once set. + + * The 'd' option has been removed. The disposition on a CDR is a function of + the state of the channel and cannot be altered. + + * The 'D' option has been removed. Who the Party B is on a CDR is a function + of the state of the respective channels, and cannot be altered. + + * The 'r' option has been changed. Previously, ForkCDR always reset the CDR + such that the start time and, if applicable, the answer time was updated. + Now, by default, ForkCDR simply forks the CDR, maintaining any times. The + 'r' option now triggers the Reset, setting the start time (and answer time + if applicable) to the current time. + + * The 's' option has been removed. A variable can be set on the original CDR + if desired using the CDR function, and removed from a forked CDR using the + same function. + + * The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no + longer applies in the CDR engine. + + * The 'v' option now prevents the copy of the variables from the original CDR + to the forked CDR. Previously the variables were always copied but were + removed from the original. Removing variables from a CDR can have unintended + side effects - this option allows the user to prevent propagation of + variables from the original to the forked without modifying the original. + +MeetMe +------------------- +* Added the 'n' option to MeetMe to prevent application of the DENOISE function + to a channel joining a conference. Some channel drivers that vary the number + of audio samples in a voice frame will experience significant quality problems + if a denoiser is attached to the channel; this option gives them the ability + to remove the denoiser without having to unload func_speex. + +NoCDR +------------------ + * The NoCDR application is deprecated. Please use the CDR_PROP function to + disable CDRs. + * While the NoCDR application will prevent CDRs for a channel from being + propagated to registered CDR backends, it will not prevent that data from + being collected. Hence, a subsequent call to ResetCDR or the CDR_PROP + function that enables CDRs on a channel will restore those records that have + not yet been finalized. + +Queue +------------------- + * Add queue available hint. exten => 8501,hint,Queue:markq_avail + Note: the suffix '_avail' after the queuename. + Reports 'InUse' for no logged in agents or no free agents. + Reports 'Idle' when an agent is free. + +ResetCDR +------------------ + * The 'e' option has been deprecated. Use the CDR_PROP function to re-enable + CDRs when they were previously disabled on a channel. + * The 'w' and 'a' options have been removed. Dispatching CDRs to registered + backends occurs on an as-needed basis in order to preserve linkedid + propagation and other needed behavior. + +SetAMAFlags +------------------ + * This application is deprecated in favor of the CHANNEL function. + + +Core +------------------ + * Redirecting reasons can now be set to arbitrary strings. This means + that the REDIRECTING dialplan function can be used to set the redirecting + reason to any string. It also allows for custom strings to be read as the + redirecting reason from SIP Diversion headers. AMI (Asterisk Manager Interface) ------------------ @@ -72,6 +160,9 @@ AMI (Asterisk Manager Interface) event, the various ChanVariable fields will contain a suffix that specifies which channel they correspond to. +* The NewPeerAccount AMI event is no longer raised. The NewAccountCode AMI + event always conveys the AMI event for a particular channel. + * All "Reload" events have been consolidated into a single event type. This event will always contain a Module field specifying the name of the module and a Status field denoting the result of the reload. All modules now issue @@ -118,33 +209,21 @@ AGI (Asterisk Gateway Interface) * The manager event AsyncAGI has been split into AsyncAGIStart, AsyncAGIExec, and AsyncAGIEnd. -Channel Drivers ------------------- - * When a channel driver is configured to enable jiterbuffers, they are now - applied unconditionally when a channel joins a bridge. If a jitterbuffer - is already set for that channel when it enters, such as by the JITTERBUFFER - function, then the existing jitterbuffer will be used and the one set by - the channel driver will not be applied. - -chan_local ------------------- - * The /b option is removed. - - * chan_local moved into the system core and is no longer a loadable module. - -chan_mobile +CDR (Call Detail Records) ------------------ - * Added general support for busy detection. + * Significant changes have been made to the behavior of CDRs. For a full + definition of CDR behavior in Asterisk 12, please read the specification + on the Asterisk wiki (wiki.asterisk.org). - * Added ECAM command support for Sony Ericsson phones. + * CDRs will now be created between all participants in a bridge. For each + pair of channels in a bridge, a CDR is created to represent the path of + communication between those two endpoints. This lets an end user choose who + to bill for what during multi-party bridges or bridge operations during + transfer scenarios. -chan_sip ------------------- - * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf - using the 'supportpath' setting, either on a global basis or on a peer basis. - This setting enables Asterisk to route outgoing out-of-dialog requests via a - set of proxies by using a pre-loaded route-set defined by the Path headers in - the REGISTER request. See Realtime updates for more configuration information. + * When a CDR is dispatched, user defined CDR variables from both parties are + included in the resulting CDR. If both parties have the same variable, only + the Party A value is provided. Features ------------------- @@ -163,12 +242,6 @@ Features and FEATUREMAP() functions inherited to child channels by setting FEATURE(inherit)=yes. -Functions ------------------- - * JITTERBUFFER now accepts an argument of 'disabled' which can be used - to remove jitterbuffers previously set on a channel with JITTERBUFFER. - The value of this setting is ignored when disabled is used for the argument. - Logging ------------------- * When performing queue pause/unpause on an interface without specifying an @@ -178,25 +251,12 @@ Logging * Added the 'queue_log_realtime_use_gmt' option to have timestamps in GMT for realtime queue log entries. -MeetMe -------------------- -* Added the 'n' option to MeetMe to prevent application of the DENOISE function - to a channel joining a conference. Some channel drivers that vary the number - of audio samples in a voice frame will experience significant quality problems - if a denoiser is attached to the channel; this option gives them the ability - to remove the denoiser without having to unload func_speex. - Parking ------------------- * Parking is now implemented as a module instead of as core functionality. The preferred way to configure parking is now through res_parking.conf while configuration through features.conf is not currently supported. - * res_parking uses the configuration framework. If an invalid configuration is - supplied, res_parking will fail to load or fail to reload. Previously parking - lots that were misconfigured would generally be accepted with certain - configuration problems leading to individual disabled parking lots. - * Parked calls are now placed in bridges. This is a largely architectural change, but it could have some implications in allowing for new parked call retrieval methods and the contents of parking lots will be visible though certain bridge @@ -236,63 +296,96 @@ Parking by default. Instead, it will follow the timeout rules of the parking lot. The old behavior can be reproduced by using the 'c' option. - * Added a channel variable PARKER_FLAT which stores the name of the extension - that would be used to come back to if comebacktoorigin was set to use. This can - be useful when comebacktoorigin is off if you still want to use the extensions - in the park-dial context that are generated to redial the parker on timeout. +Realtime +------------------ + * Dynamic realtime tables for SIP Users can now include a 'path' field. This + will store the path information for that peer when it registers. Realtime + tables can also use the 'supportpath' field to enable Path header support. -Queue -------------------- - * Add queue available hint. exten => 8501,hint,Queue:markq_avail - Note: the suffix '_avail' after the queuename. - Reports 'InUse' for no logged in agents or no free agents. - Reports 'Idle' when an agent is free. + * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport + objectIdentifier. This maps to the supportpath option in sip.conf. + +Sorcery +------------------ + * All future modules which utilize Sorcery for object persistence must have a + column named "id" within their schema when using the Sorcery realtime module. + This column must be able to contain a string of up to 128 characters in length. - * The configuration options eventwhencalled and eventmemberstatus have been - removed. As a result, the AMI events QueueMemberStatus, AgentCalled, - AgentConnect, AgentComplete, AgentDump, and AgentRingNoAnswer will always be - sent. The "Variable" fields will also no longer exist on the Agent* events. -Core +Channel Drivers ------------------ - * Redirecting reasons can now be set to arbitrary strings. This means - that the REDIRECTING dialplan function can be used to set the redirecting - reason to any string. It also allows for custom strings to be read as the - redirecting reason from SIP Diversion headers. + * When a channel driver is configured to enable jiterbuffers, they are now + applied unconditionally when a channel joins a bridge. If a jitterbuffer + is already set for that channel when it enters, such as by the JITTERBUFFER + function, then the existing jitterbuffer will be used and the one set by + the channel driver will not be applied. + +chan_agent +------------------ + * The updatecdr option has been removed. Altering the names of channels on a + CDR is not supported - the name of the channel is the name of the channel, + and pretending otherwise helps no one. + * The AGENTUPDATECDR channel variable has also been removed, for the same + reason as the updatecdr option. + +chan_local +------------------ + * The /b option is removed. - * For DTMF blind and attended transfers, the channel variable TRANSFER_CONTEXT - must be on the channel initiating the transfer to have any effect. + * chan_local moved into the system core and is no longer a loadable module. - * The channel variable ATTENDED_TRANSFER_COMPLETE_SOUND is no longer channel - driver specific. If the channel variable is set on the transferrer channel, - the sound will be played to the target of an attended transfer. +chan_mobile +------------------ + * Added general support for busy detection. - * The channel variable BRIDGEPEER becomes a comma separated list of peers in - a multi-party bridge. The BRIDGEPEER value can have a maximum of 10 peers - listed. Any more peers in the bridge will not be included in the list. - BRIDGEPEER is not valid in holding bridges like parking since those channels - do not talk to each other even though they are in a bridge. + * Added ECAM command support for Sony Ericsson phones. - * The channel variable BRIDGEPVTCALLID is only valid for two party bridges - and will contain a value if the BRIDGEPEER's channel driver supports it. +chan_sip +------------------ + * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf + using the 'supportpath' setting, either on a global basis or on a peer basis. + This setting enables Asterisk to route outgoing out-of-dialog requests via a + set of proxies by using a pre-loaded route-set defined by the Path headers in + the REGISTER request. See Realtime updates for more configuration information. - * The channel variable DYNAMIC_PEERNAME is redundant with BRIDGEPEER and is - removed. The more useful DYNAMIC_WHO_ACTIVATED gives the channel name that - activated the dynamic feature. - * The channel variables DYNAMIC_FEATURENAME and DYNAMIC_WHO_ACTIVATED are set - only on the channel executing the dynamic feature. Executing a dynamic - feature on the bridge peer in a multi-party bridge will execute it on all - peers of the activating channel. +Functions +------------------ -Realtime +JITTERBUFFER ------------------ - * Dynamic realtime tables for SIP Users can now include a 'path' field. This - will store the path information for that peer when it registers. Realtime - tables can also use the 'supportpath' field to enable Path header support. + * JITTERBUFFER now accepts an argument of 'disabled' which can be used + to remove jitterbuffers previously set on a channel with JITTERBUFFER. + The value of this setting is ignored when disabled is used for the argument. - * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport - objectIdentifier. This maps to the supportpath option in sip.conf. +CDR (function) +------------------ + * The 'amaflags' and 'accountcode' attributes for the CDR function are + deprecated. Use the CHANNEL function instead to access these attributes. + * The 'l' option has been removed. When reading a CDR attribute, the most + recent record is always used. When writing a CDR attribute, all non-finalized + CDRs are updated. + * The 'r' option has been removed, for the same reason as the 'l' option. + * The 's' option has been removed, as LOCKED semantics no longer exist in the + CDR engine. + +CDR_PROP +------------------ + * A new function CDR_PROP has been added. This function lets you set properties + on a channel's active CDRs. This function is write-only. Properties accept + boolean values to set/clear them on the channel's CDRs. Valid properties + include: + * 'party_a' - make this channel the preferred Party A in any CDR between two + channels. If two channels have this property set, the creation time of the + channel is used to determine who is Party A. Note that dialed channels are + never Party A in a CDR. + * 'disable' - disable CDRs on this channel. This is analogous to the NoCDR + application when set to True, and analogous to the 'e' option in ResetCDR + when set to False. + + +Resources +------------------ RTP ------------------ diff --git a/UPGRADE.txt b/UPGRADE.txt index dfe808141e..7a5261b94d 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -21,6 +21,49 @@ === =========================================================== +AgentMonitorOutgoing + - The 'c' option has been removed. It is not possible to modify the name of a + channel involved in a CDR. + +NoCDR: + - This application is deprecated. Please use the CDR_PROP function instead. + +ResetCDR: + - The 'w' and 'a' options have been removed. Dispatching CDRs to registered + backends occurs on an as-needed basis in order to preserve linkedid + propagation and other needed behavior. + - The 'e' option is deprecated. Please use the CDR_PROP function to enable + CDRs on a channel that they were previously disabled on. + - The ResetCDR application is no longer a part of core Asterisk, and instead + is now delivered as part of app_cdr. + +ForkCDR: + - ForkCDR no longer automatically resets the forked CDR. See the 'r' option + for more information. + - Variables are no longer purged from the original CDR. See the 'v' option for + more information. + - The 'A' option has been removed. The Answer time on a CDR is never updated + once set. + - The 'd' option has been removed. The disposition on a CDR is a function of + the state of the channel and cannot be altered. + - The 'D' option has been removed. Who the Party B is on a CDR is a function + of the state of the respective channels, and cannot be altered. + - The 'r' option has been changed. Previously, ForkCDR always reset the CDR + such that the start time and, if applicable, the answer time was updated. + Now, by default, ForkCDR simply forks the CDR, maintaining any times. The + 'r' option now triggers the Reset, setting the start time (and answer time + if applicable) to the current time. + - The 's' option has been removed. A variable can be set on the original CDR + if desired using the CDR function, and removed from a forked CDR using the + same function. + - The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no + longer applies in the CDR engine. + - The 'v' option now prevents the copy of the variables from the original CDR + to the forked CDR. Previously the variables were always copied but were + removed from the original. Removing variables from a CDR can have unintended + side effects - this option allows the user to prevent propagation of + variables from the original to the forked without modifying the original. + AMI: - The SIP SIPqualifypeer action now sends a response indicating it will qualify a peer once a peer has been found to qualify. Once the qualify has been @@ -72,6 +115,16 @@ SendDTMF: - Now recognizes 'W' to pause sending DTMF for one second in addition to the previously existing 'w' that paused sending DTMF for half a second. +SetAMAFlags + - This application is deprecated in favor of the CHANNEL function. + +chan_agent: + - The updatecdr option has been removed. Altering the names of channels on a + CDR is not supported - the name of the channel is the name of the channel, + and pretending otherwise helps no one. + - The AGENTUPDATECDR channel variable has also been removed, for the same + reason as the updatecdr option. + chan_dahdi: - Analog port dialing and deferred DTMF dialing for PRI now distinguishes between 'w' and 'W'. The 'w' pauses dialing for half a second. The 'W' @@ -79,7 +132,7 @@ chan_dahdi: - The default for inband_on_proceeding has changed to no. chan_local: - - The /b option is removed. + - The /b option has been removed. Dialplan: - All channel and global variable names are evaluated in a case-sensitive manner. diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c index b87d8c6aa1..23e96c562f 100644 --- a/addons/cdr_mysql.c +++ b/addons/cdr_mysql.c @@ -251,7 +251,7 @@ db_reconnect: char timestr[128]; ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL); ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm); - ast_cdr_setvar(cdr, "calldate", timestr, 0); + ast_cdr_setvar(cdr, "calldate", timestr); cdrname = "calldate"; } else { cdrname = "start"; @@ -277,9 +277,9 @@ db_reconnect: strstr(entry->type, "real") || strstr(entry->type, "numeric") || strstr(entry->type, "fixed"))) { - ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 1); + ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1); } else { - ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 0); + ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0); } if (value) { diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c index a4f62acd89..303b06857a 100644 --- a/addons/chan_ooh323.c +++ b/addons/chan_ooh323.c @@ -2376,7 +2376,7 @@ static struct ooh323_user *build_user(const char *name, struct ast_variable *v) ast_parse_allow_disallow(&user->prefs, user->cap, tcodecs, 1); } else if (!strcasecmp(v->name, "amaflags")) { - user->amaflags = ast_cdr_amaflags2int(v->value); + user->amaflags = ast_channel_string2amaflag(v->value); } else if (!strcasecmp(v->name, "ip") || !strcasecmp(v->name, "host")) { struct ast_sockaddr p; if (!ast_parse_arg(v->value, PARSE_ADDR, &p)) { @@ -2560,7 +2560,7 @@ static struct ooh323_peer *build_peer(const char *name, struct ast_variable *v, ast_parse_allow_disallow(&peer->prefs, peer->cap, tcodecs, 1); } else if (!strcasecmp(v->name, "amaflags")) { - peer->amaflags = ast_cdr_amaflags2int(v->value); + peer->amaflags = ast_channel_string2amaflag(v->value); } else if (!strcasecmp(v->name, "roundtrip")) { sscanf(v->value, "%d,%d", &peer->rtdrcount, &peer->rtdrinterval); } else if (!strcasecmp(v->name, "dtmfmode")) { @@ -2917,7 +2917,7 @@ int reload_config(int reload) "'lowdelay', 'throughput', 'reliability', " "'mincost', or 'none'\n", v->lineno); } else if (!strcasecmp(v->name, "amaflags")) { - gAMAFLAGS = ast_cdr_amaflags2int(v->value); + gAMAFLAGS = ast_channel_string2amaflag(v->value); } else if (!strcasecmp(v->name, "accountcode")) { ast_copy_string(gAccountcode, v->value, sizeof(gAccountcode)); } else if (!strcasecmp(v->name, "disallow")) { @@ -3117,7 +3117,7 @@ static char *handle_cli_ooh323_show_peer(struct ast_cli_entry *e, int cmd, struc } ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", peer->accountcode); - ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(peer->amaflags)); + ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_channel_amaflags2string(peer->amaflags)); ast_cli(a->fd, "%-15.15s%s\n", "IP:Port: ", ip_port); ast_cli(a->fd, "%-15.15s%d\n", "OutgoingLimit: ", peer->outgoinglimit); ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", peer->rtptimeout); @@ -3276,7 +3276,7 @@ static char *handle_cli_ooh323_show_user(struct ast_cli_entry *e, int cmd, struc } ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", user->accountcode); - ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(user->amaflags)); + ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_channel_amaflags2string(user->amaflags)); ast_cli(a->fd, "%-15.15s%s\n", "Context: ", user->context); ast_cli(a->fd, "%-15.15s%d\n", "IncomingLimit: ", user->incominglimit); ast_cli(a->fd, "%-15.15s%d\n", "InUse: ", user->inUse); @@ -3524,7 +3524,7 @@ static char *handle_cli_ooh323_show_config(struct ast_cli_entry *e, int cmd, str ast_cli(a->fd, "%-20s%ld\n", "Call counter: ", callnumber); ast_cli(a->fd, "%-20s%s\n", "AccountCode: ", gAccountcode); - ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_cdr_flags2str(gAMAFLAGS)); + ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_channel_amaflags2string(gAMAFLAGS)); pAlias = gAliasList; if(pAlias) { diff --git a/apps/app_authenticate.c b/apps/app_authenticate.c index fbb4300895..a8370588b8 100644 --- a/apps/app_authenticate.c +++ b/apps/app_authenticate.c @@ -213,9 +213,9 @@ static int auth_exec(struct ast_channel *chan, const char *data) continue; ast_md5_hash(md5passwd, passwd); if (!strcmp(md5passwd, md5secret)) { - if (ast_test_flag(&flags,OPT_ACCOUNT)) { + if (ast_test_flag(&flags, OPT_ACCOUNT)) { ast_channel_lock(chan); - ast_cdr_setaccount(chan, buf); + ast_channel_accountcode_set(chan, buf); ast_channel_unlock(chan); } break; @@ -224,7 +224,7 @@ static int auth_exec(struct ast_channel *chan, const char *data) if (!strcmp(passwd, buf)) { if (ast_test_flag(&flags, OPT_ACCOUNT)) { ast_channel_lock(chan); - ast_cdr_setaccount(chan, buf); + ast_channel_accountcode_set(chan, buf); ast_channel_unlock(chan); } break; @@ -250,7 +250,7 @@ static int auth_exec(struct ast_channel *chan, const char *data) if ((retries < 3) && !res) { if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE)) { ast_channel_lock(chan); - ast_cdr_setaccount(chan, passwd); + ast_channel_accountcode_set(chan, passwd); ast_channel_unlock(chan); } if (!(res = ast_streamfile(chan, "auth-thankyou", ast_channel_language(chan)))) diff --git a/apps/app_cdr.c b/apps/app_cdr.c index 3c244712bb..ba7139cf1a 100644 --- a/apps/app_cdr.c +++ b/apps/app_cdr.c @@ -35,25 +35,114 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/channel.h" #include "asterisk/module.h" +#include "asterisk/app.h" /*** DOCUMENTATION - Tell Asterisk to not maintain a CDR for the current call + Tell Asterisk to not maintain a CDR for this channel. - This application will tell Asterisk not to maintain a CDR for the current call. + This application will tell Asterisk not to maintain a CDR for + the current channel. This does NOT mean that + information is not tracked; rather, if the channel is hung up no + CDRs will be created for that channel. + If a subsequent call to ResetCDR occurs, all non-finalized + CDRs created for the channel will be enabled. + This application is deprecated. Please use the CDR_PROP + function to disable CDRs on a channel. + + ResetCDR + CDR_PROP + + + + + Resets the Call Data Record. + + + + + + + + + + + This application causes the Call Data Record to be reset. + Depending on the flags passed in, this can have several effects. + With no options, a reset does the following: + 1. The start time is set to the current time. + 2. If the channel is answered, the answer time is set to the + current time. + 3. All variables are wiped from the CDR. Note that this step + can be prevented with the v option. + On the other hand, if the e option is + specified, the effects of the NoCDR application will be lifted. CDRs + will be re-enabled for this channel. + The e option is deprecated. Please + use the CDR_PROP function instead. + + + ForkCDR + NoCDR + CDR_PROP + ***/ static const char nocdr_app[] = "NoCDR"; +static const char resetcdr_app[] = "ResetCDR"; + +enum reset_cdr_options { + OPT_DISABLE_DISPATCH = (1 << 0), + OPT_KEEP_VARS = (1 << 1), + OPT_ENABLE = (1 << 2), +}; + +AST_APP_OPTIONS(resetcdr_opts, { + AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), + AST_APP_OPTION('e', AST_CDR_FLAG_DISABLE_ALL), +}); + +static int resetcdr_exec(struct ast_channel *chan, const char *data) +{ + char *args; + struct ast_flags flags = { 0 }; + int res = 0; + + if (!ast_strlen_zero(data)) { + args = ast_strdupa(data); + ast_app_parse_options(resetcdr_opts, &flags, NULL, args); + } + + if (ast_test_flag(&flags, AST_CDR_FLAG_DISABLE_ALL)) { + if (ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) { + res = 1; + } + } + if (ast_cdr_reset(ast_channel_name(chan), &flags)) { + res = 1; + } + + if (res) { + ast_log(AST_LOG_WARNING, "Failed to reset CDR for channel %s\n", ast_channel_name(chan)); + } + return res; +} static int nocdr_exec(struct ast_channel *chan, const char *data) { - if (ast_channel_cdr(chan)) - ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED); + if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) { + ast_log(AST_LOG_WARNING, "Failed to disable CDR for channel %s\n", ast_channel_name(chan)); + } return 0; } @@ -65,8 +154,14 @@ static int unload_module(void) static int load_module(void) { - if (ast_register_application_xml(nocdr_app, nocdr_exec)) + int res = 0; + + res |= ast_register_application_xml(nocdr_app, nocdr_exec); + res |= ast_register_application_xml(resetcdr_app, resetcdr_exec); + + if (res) { return AST_MODULE_LOAD_FAILURE; + } return AST_MODULE_LOAD_SUCCESS; } diff --git a/apps/app_dial.c b/apps/app_dial.c index b0caa5c6be..c75eb2d3a4 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -55,7 +55,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/app.h" #include "asterisk/causes.h" #include "asterisk/rtp_engine.h" -#include "asterisk/cdr.h" #include "asterisk/manager.h" #include "asterisk/privacy.h" #include "asterisk/stringfields.h" @@ -753,36 +752,20 @@ struct cause_args { static void handle_cause(int cause, struct cause_args *num) { - struct ast_cdr *cdr = ast_channel_cdr(num->chan); - switch(cause) { case AST_CAUSE_BUSY: - if (cdr) - ast_cdr_busy(cdr); num->busy++; break; - case AST_CAUSE_CONGESTION: - if (cdr) - ast_cdr_failed(cdr); num->congestion++; break; - case AST_CAUSE_NO_ROUTE_DESTINATION: case AST_CAUSE_UNREGISTERED: - if (cdr) - ast_cdr_failed(cdr); num->nochan++; break; - case AST_CAUSE_NO_ANSWER: - if (cdr) { - ast_cdr_noanswer(cdr); - } - break; case AST_CAUSE_NORMAL_CLEARING: break; - default: num->nochan++; break; @@ -974,7 +957,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num, ast_channel_appl_set(c, "AppDial"); ast_channel_data_set(c, "(Outgoing Line)"); - ast_publish_channel_state(c); + ast_channel_publish_snapshot(c); ast_channel_unlock(in); if (single && !ast_test_flag64(o, OPT_IGNORE_CONNECTEDLINE)) { @@ -1096,7 +1079,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, */ *to = -1; strcpy(pa->status, "CONGESTION"); - ast_cdr_failed(ast_channel_cdr(in)); ast_channel_publish_dial(in, outgoing->chan, NULL, pa->status); return NULL; } @@ -1301,10 +1283,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, peer = c; ast_channel_publish_dial(in, peer, NULL, "ANSWER"); publish_dial_end_event(in, out_chans, peer, "CANCEL"); - if (ast_channel_cdr(peer)) { - ast_channel_cdr(peer)->answer = ast_tvnow(); - ast_channel_cdr(peer)->disposition = AST_CDR_ANSWERED; - } ast_copy_flags64(peerflags, o, OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER | OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP | @@ -1326,7 +1304,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, case AST_CONTROL_BUSY: ast_verb(3, "%s is busy\n", ast_channel_name(c)); ast_channel_hangupcause_set(in, ast_channel_hangupcause(c)); - ast_channel_publish_dial(in, c, NULL, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(c))); + ast_channel_publish_dial(in, c, NULL, "BUSY"); ast_hangup(c); c = o->chan = NULL; ast_clear_flag64(o, DIAL_STILLGOING); @@ -1335,7 +1313,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, case AST_CONTROL_CONGESTION: ast_verb(3, "%s is circuit-busy\n", ast_channel_name(c)); ast_channel_hangupcause_set(in, ast_channel_hangupcause(c)); - ast_channel_publish_dial(in, c, NULL, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(c))); + ast_channel_publish_dial(in, c, NULL, "CONGESTION"); ast_hangup(c); c = o->chan = NULL; ast_clear_flag64(o, DIAL_STILLGOING); @@ -1542,7 +1520,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, /* Got hung up */ *to = -1; strcpy(pa->status, "CANCEL"); - ast_cdr_noanswer(ast_channel_cdr(in)); publish_dial_end_event(in, out_chans, NULL, pa->status); if (f) { if (f->data.uint32) { @@ -1565,7 +1542,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, if (onedigit_goto(in, context, (char) f->subclass.integer, 1)) { ast_verb(3, "User hit %c to disconnect call.\n", f->subclass.integer); *to = 0; - ast_cdr_noanswer(ast_channel_cdr(in)); *result = f->subclass.integer; strcpy(pa->status, "CANCEL"); publish_dial_end_event(in, out_chans, NULL, pa->status); @@ -1584,7 +1560,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, ast_verb(3, "User requested call disconnect.\n"); *to = 0; strcpy(pa->status, "CANCEL"); - ast_cdr_noanswer(ast_channel_cdr(in)); publish_dial_end_event(in, out_chans, NULL, pa->status); ast_frfree(f); if (is_cc_recall) { @@ -1679,13 +1654,10 @@ skip_frame:; } } - if (!*to) { + if (!*to || ast_check_hangup(in)) { ast_verb(3, "Nobody picked up in %d ms\n", orig); publish_dial_end_event(in, out_chans, NULL, "NOANSWER"); } - if (!*to || ast_check_hangup(in)) { - ast_cdr_noanswer(ast_channel_cdr(in)); - } #ifdef HAVE_EPOLL AST_LIST_TRAVERSE(out_chans, epollo, node) { @@ -1985,22 +1957,13 @@ static void end_bridge_callback(void *data) time_t end; struct ast_channel *chan = data; - if (!ast_channel_cdr(chan)) { - return; - } - time(&end); ast_channel_lock(chan); - if (ast_channel_cdr(chan)->answer.tv_sec) { - snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec); - pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf); - } - - if (ast_channel_cdr(chan)->start.tv_sec) { - snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec); - pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf); - } + snprintf(buf, sizeof(buf), "%d", ast_channel_get_up_time(chan)); + pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf); + snprintf(buf, sizeof(buf), "%d", ast_channel_get_duration(chan)); + pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf); ast_channel_unlock(chan); } @@ -2294,8 +2257,9 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast ast_channel_unlock(chan); } - if (ast_test_flag64(&opts, OPT_RESETCDR) && ast_channel_cdr(chan)) - ast_cdr_reset(ast_channel_cdr(chan), NULL); + if (ast_test_flag64(&opts, OPT_RESETCDR)) { + ast_cdr_reset(ast_channel_name(chan), 0); + } if (ast_test_flag64(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY])) opt_args[OPT_ARG_PRIVACY] = ast_strdupa(ast_channel_exten(chan)); @@ -2489,7 +2453,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast ast_channel_appl_set(tc, "AppDial"); ast_channel_data_set(tc, "(Outgoing Line)"); - ast_publish_channel_state(tc); + ast_channel_publish_snapshot(tc); memset(ast_channel_whentohangup(tc), 0, sizeof(*ast_channel_whentohangup(tc))); @@ -2620,10 +2584,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast res = ast_call(tmp->chan, tmp->number, 0); /* Place the call, but don't wait on the answer */ ast_channel_lock(chan); - /* Save the info in cdr's that we called them */ - if (ast_channel_cdr(chan)) - ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(tmp->chan)); - /* check the results of ast_call */ if (res) { /* Again, keep going even if there's an error */ @@ -2738,10 +2698,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast conversation. */ hanguptree(&out_chans, peer, 1); /* If appropriate, log that we have a destination channel and set the answer time */ - if (ast_channel_cdr(chan)) { - ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer)); - ast_cdr_setanswer(ast_channel_cdr(chan), ast_channel_cdr(peer)->answer); - } if (ast_channel_name(peer)) pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", ast_channel_name(peer)); @@ -2836,10 +2792,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast } if (chan && peer && ast_test_flag64(&opts, OPT_GOTO) && !ast_strlen_zero(opt_args[OPT_ARG_GOTO])) { - /* chan and peer are going into the PBX, they both - * should probably get CDR records. */ - ast_clear_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED); - ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_DIALED); + /* chan and peer are going into the PBX; as such neither are considered + * outgoing channels any longer */ + ast_clear_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING); + ast_clear_flag(ast_channel_flags(peer), AST_FLAG_OUTGOING); ast_replace_subargument_delimiter(opt_args[OPT_ARG_GOTO]); ast_parseable_goto(chan, opt_args[OPT_ARG_GOTO]); diff --git a/apps/app_disa.c b/apps/app_disa.c index c43370c95e..fe53772f1e 100644 --- a/apps/app_disa.c +++ b/apps/app_disa.c @@ -362,7 +362,7 @@ static int disa_exec(struct ast_channel *chan, const char *data) if (k == 3) { int recheck = 0; - struct ast_flags cdr_flags = { AST_CDR_FLAG_POSTED }; + struct ast_flags cdr_flags = { AST_CDR_FLAG_DISABLE, }; if (!ast_exists_extension(chan, args.context, exten, 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { @@ -384,8 +384,10 @@ static int disa_exec(struct ast_channel *chan, const char *data) if (!ast_strlen_zero(acctcode)) ast_channel_accountcode_set(chan, acctcode); - if (special_noanswer) cdr_flags.flags = 0; - ast_cdr_reset(ast_channel_cdr(chan), &cdr_flags); + if (special_noanswer) { + ast_clear_flag(&cdr_flags, AST_CDR_FLAG_DISABLE); + } + ast_cdr_reset(ast_channel_name(chan), &cdr_flags); ast_explicit_goto(chan, args.context, exten, 1); return 0; } diff --git a/apps/app_dumpchan.c b/apps/app_dumpchan.c index 722f155417..7613832d4d 100644 --- a/apps/app_dumpchan.c +++ b/apps/app_dumpchan.c @@ -70,7 +70,6 @@ static const char app[] = "DumpChan"; static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) { - struct timeval now; long elapsed_seconds = 0; int hour = 0, min = 0, sec = 0; char nf[256]; @@ -80,21 +79,19 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size) struct ast_str *read_transpath = ast_str_alloca(256); struct ast_bridge *bridge; - now = ast_tvnow(); memset(buf, 0, size); if (!c) return 0; - if (ast_channel_cdr(c)) { - elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec; - hour = elapsed_seconds / 3600; - min = (elapsed_seconds % 3600) / 60; - sec = elapsed_seconds % 60; - } + elapsed_seconds = ast_channel_get_duration(c); + hour = elapsed_seconds / 3600; + min = (elapsed_seconds % 3600) / 60; + sec = elapsed_seconds % 60; ast_channel_lock(c); bridge = ast_channel_get_bridge(c); ast_channel_unlock(c); + snprintf(buf,size, "Name= %s\n" "Type= %s\n" diff --git a/apps/app_followme.c b/apps/app_followme.c index 66980009d4..d12de3c1a9 100644 --- a/apps/app_followme.c +++ b/apps/app_followme.c @@ -578,29 +578,6 @@ static void clear_caller(struct findme_user *tmpuser) } outbound = tmpuser->ochan; - ast_channel_lock(outbound); - if (!ast_channel_cdr(outbound)) { - ast_channel_cdr_set(outbound, ast_cdr_alloc()); - if (ast_channel_cdr(outbound)) { - ast_cdr_init(ast_channel_cdr(outbound), outbound); - } - } - if (ast_channel_cdr(outbound)) { - char tmp[256]; - - snprintf(tmp, sizeof(tmp), "Local/%s", tmpuser->dialarg); - ast_cdr_setapp(ast_channel_cdr(outbound), "FollowMe", tmp); - ast_cdr_update(outbound); - ast_cdr_start(ast_channel_cdr(outbound)); - ast_cdr_end(ast_channel_cdr(outbound)); - /* If the cause wasn't handled properly */ - if (ast_cdr_disposition(ast_channel_cdr(outbound), ast_channel_hangupcause(outbound))) { - ast_cdr_failed(ast_channel_cdr(outbound)); - } - } else { - ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); - } - ast_channel_unlock(outbound); ast_hangup(outbound); tmpuser->ochan = NULL; } @@ -1127,11 +1104,6 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel * Destoy all new outgoing calls. */ while ((tmpuser = AST_LIST_REMOVE_HEAD(&new_user_list, entry))) { - ast_channel_lock(tmpuser->ochan); - if (ast_channel_cdr(tmpuser->ochan)) { - ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan); - } - ast_channel_unlock(tmpuser->ochan); destroy_calling_node(tmpuser); } @@ -1153,11 +1125,6 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel AST_LIST_REMOVE_CURRENT(entry); /* Destroy this failed new outgoing call. */ - ast_channel_lock(tmpuser->ochan); - if (ast_channel_cdr(tmpuser->ochan)) { - ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan); - } - ast_channel_unlock(tmpuser->ochan); destroy_calling_node(tmpuser); continue; } @@ -1310,15 +1277,10 @@ static void end_bridge_callback(void *data) time(&end); ast_channel_lock(chan); - if (ast_channel_cdr(chan)->answer.tv_sec) { - snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec); - pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf); - } - - if (ast_channel_cdr(chan)->start.tv_sec) { - snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec); - pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf); - } + snprintf(buf, sizeof(buf), "%d", ast_channel_get_up_time(chan)); + pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf); + snprintf(buf, sizeof(buf), "%d", ast_channel_get_duration(chan)); + pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf); ast_channel_unlock(chan); } diff --git a/apps/app_forkcdr.c b/apps/app_forkcdr.c index 354792fb9a..6231d381f7 100644 --- a/apps/app_forkcdr.c +++ b/apps/app_forkcdr.c @@ -44,98 +44,46 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*** DOCUMENTATION - Forks the Call Data Record. + Forks the current Call Data Record for this channel. - - - - - - Causes the Call Data Record to fork an additional cdr record starting from the time - of the fork call. This new cdr record will be linked to end of the list of cdr records attached - to the channel. The original CDR has a LOCKED flag set, which forces most cdr operations to skip - it, except for the functions that set the answer and end times, which ignore the LOCKED flag. This - allows all the cdr records in the channel to be 'ended' together when the channel is closed. - The CDR() func (when setting CDR values) normally ignores the LOCKED flag also, but has options - to vary its behavior. The 'T' option (described below), can override this behavior, but beware - the risks. - First, this app finds the last cdr record in the list, and makes a copy of it. This new copy - will be the newly forked cdr record. Next, this new record is linked to the end of the cdr record list. - Next, The new cdr record is RESET (unless you use an option to prevent this) - This means that: - 1. All flags are unset on the cdr record - 2. the start, end, and answer times are all set to zero. - 3. the billsec and duration fields are set to zero. - 4. the start time is set to the current time. - 5. the disposition is set to NULL. - Next, unless you specified the v option, all variables will be removed from - the original cdr record. Thus, the v option allows any CDR variables to be replicated - to all new forked cdr records. Without the v option, the variables on the original - are effectively moved to the new forked cdr record. - Next, if the s option is set, the provided variable and value are set on the - original cdr record. - Next, if the a option is given, and the original cdr record has an answer time - set, then the new forked cdr record will have its answer time set to its start time. If the old answer - time were carried forward, the answer time would be earlier than the start time, giving strange - duration and billsec times. - If the d option was specified, the disposition is copied from - the original cdr record to the new forked cdr. If the D option was specified, - the destination channel field in the new forked CDR is erased. If the e option - was specified, the 'end' time for the original cdr record is set to the current time. Future hang-up or - ending events will not override this time stamp. If the A option is specified, - the original cdr record will have it ANS_LOCKED flag set, which prevent future answer events from updating - the original cdr record's disposition. Normally, an ANSWERED event would mark all cdr - records in the chain as ANSWERED. If the T option is specified, - the original cdr record will have its DONT_TOUCH flag set, which will force the - cdr_answer, cdr_end, and cdr_setvar functions to leave that cdr record alone. - And, last but not least, the original cdr record has its LOCKED flag set. Almost all internal - CDR functions (except for the funcs that set the end, and answer times, and set a variable) will honor - this flag and leave a LOCKED cdr record alone. This means that the newly created forked cdr record - will be affected by events transpiring within Asterisk, with the previously noted exceptions. + Causes the Call Data Record engine to fork a new CDR starting + from the time the application is executed. The forked CDR will be + linked to the end of the CDRs associated with the channel. CDR @@ -147,126 +95,34 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") static char *app = "ForkCDR"; -enum { - OPT_SETANS = (1 << 0), - OPT_SETDISP = (1 << 1), - OPT_RESETDEST = (1 << 2), - OPT_ENDCDR = (1 << 3), - OPT_NORESET = (1 << 4), - OPT_KEEPVARS = (1 << 5), - OPT_VARSET = (1 << 6), - OPT_ANSLOCK = (1 << 7), - OPT_DONTOUCH = (1 << 8), -}; - -enum { - OPT_ARG_VARSET = 0, - /* note: this entry _MUST_ be the last one in the enum */ - OPT_ARG_ARRAY_SIZE, -}; - AST_APP_OPTIONS(forkcdr_exec_options, { - AST_APP_OPTION('a', OPT_SETANS), - AST_APP_OPTION('A', OPT_ANSLOCK), - AST_APP_OPTION('d', OPT_SETDISP), - AST_APP_OPTION('D', OPT_RESETDEST), - AST_APP_OPTION('e', OPT_ENDCDR), - AST_APP_OPTION('R', OPT_NORESET), - AST_APP_OPTION_ARG('s', OPT_VARSET, OPT_ARG_VARSET), - AST_APP_OPTION('T', OPT_DONTOUCH), - AST_APP_OPTION('v', OPT_KEEPVARS), + AST_APP_OPTION('a', AST_CDR_FLAG_SET_ANSWER), + AST_APP_OPTION('e', AST_CDR_FLAG_FINALIZE), + AST_APP_OPTION('r', AST_CDR_FLAG_RESET), + AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), }); -static void ast_cdr_fork(struct ast_channel *chan, struct ast_flags optflags, char *set) -{ - struct ast_cdr *cdr; - struct ast_cdr *newcdr; - struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS }; - - cdr = ast_channel_cdr(chan); - - while (cdr->next) - cdr = cdr->next; - - if (!(newcdr = ast_cdr_dup_unique(cdr))) - return; - - /* - * End the original CDR if requested BEFORE appending the new CDR - * otherwise we incorrectly end the new CDR also. - */ - if (ast_test_flag(&optflags, OPT_ENDCDR)) { - ast_cdr_end(cdr); - } - - ast_cdr_append(cdr, newcdr); - - if (!ast_test_flag(&optflags, OPT_NORESET)) - ast_cdr_reset(newcdr, &flags); - - if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS)) - ast_cdr_free_vars(cdr, 0); - - if (!ast_strlen_zero(set)) { - char *varname = ast_strdupa(set), *varval; - varval = strchr(varname,'='); - if (varval) { - *varval = 0; - varval++; - ast_cdr_setvar(cdr, varname, varval, 0); - } - } - - if (ast_test_flag(&optflags, OPT_SETANS) && !ast_tvzero(cdr->answer)) - newcdr->answer = newcdr->start; - - if (ast_test_flag(&optflags, OPT_SETDISP)) - newcdr->disposition = cdr->disposition; - - if (ast_test_flag(&optflags, OPT_RESETDEST)) - newcdr->dstchannel[0] = 0; - - if (ast_test_flag(&optflags, OPT_ANSLOCK)) - ast_set_flag(cdr, AST_CDR_FLAG_ANSLOCKED); - - if (ast_test_flag(&optflags, OPT_DONTOUCH)) - ast_set_flag(cdr, AST_CDR_FLAG_DONT_TOUCH); - - ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED); -} - static int forkcdr_exec(struct ast_channel *chan, const char *data) { - int res = 0; - char *argcopy = NULL; - struct ast_flags flags = {0}; - char *opts[OPT_ARG_ARRAY_SIZE]; - AST_DECLARE_APP_ARGS(arglist, + char *parse; + struct ast_flags flags = { 0, }; + AST_DECLARE_APP_ARGS(args, AST_APP_ARG(options); ); - if (!ast_channel_cdr(chan)) { - ast_log(LOG_WARNING, "Channel does not have a CDR\n"); - return 0; - } - - argcopy = ast_strdupa(data); + parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(arglist, argcopy); + AST_STANDARD_APP_ARGS(args, parse); - opts[OPT_ARG_VARSET] = 0; - - if (!ast_strlen_zero(arglist.options)) - ast_app_parse_options(forkcdr_exec_options, &flags, opts, arglist.options); + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(forkcdr_exec_options, &flags, NULL, args.options); + } - if (!ast_strlen_zero(data)) { - int keepvars = ast_test_flag(&flags, OPT_KEEPVARS) ? 1 : 0; - ast_set2_flag(ast_channel_cdr(chan), keepvars, AST_CDR_FLAG_KEEP_VARS); + if (ast_cdr_fork(ast_channel_name(chan), &flags)) { + ast_log(AST_LOG_WARNING, "Failed to fork CDR for channel %s\n", ast_channel_name(chan)); } - - ast_cdr_fork(chan, flags, opts[OPT_ARG_VARSET]); - return res; + return 0; } static int unload_module(void) diff --git a/apps/app_osplookup.c b/apps/app_osplookup.c index b37a2ae63a..5ab497f5e4 100644 --- a/apps/app_osplookup.c +++ b/apps/app_osplookup.c @@ -2814,7 +2814,7 @@ static int ospfinished_exec( int inhandle = OSP_INVALID_HANDLE; int outhandle = OSP_INVALID_HANDLE; int recorded = 0; - time_t start, connect, end; + time_t start = 0, connect = 0, end = 0; unsigned int release; char buffer[OSP_SIZE_INTSTR]; char inqos[OSP_SIZE_QOSSTR] = { 0 }; @@ -2866,19 +2866,18 @@ static int ospfinished_exec( } ast_debug(1, "OSPFinish: cause '%d'\n", cause); - if (ast_channel_cdr(chan)) { - start = ast_channel_cdr(chan)->start.tv_sec; - connect = ast_channel_cdr(chan)->answer.tv_sec; - if (connect) { - end = time(NULL); - } else { - end = connect; - } + if (!ast_tvzero(ast_channel_creationtime(chan))) { + start = ast_channel_creationtime(chan).tv_sec; + } + if (!ast_tvzero(ast_channel_answertime(chan))) { + connect = ast_channel_answertime(chan).tv_sec; + } + if (connect) { + end = time(NULL); } else { - start = 0; - connect = 0; - end = 0; + end = connect; } + ast_debug(1, "OSPFinish: start '%ld'\n", start); ast_debug(1, "OSPFinish: connect '%ld'\n", connect); ast_debug(1, "OSPFinish: end '%ld'\n", end); diff --git a/apps/app_queue.c b/apps/app_queue.c index 4c96583137..724ea47ff3 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -3666,10 +3666,10 @@ static void publish_dial_end_event(struct ast_channel *in, struct callattempt *o struct callattempt *cur; for (cur = outgoing; cur; cur = cur->q_next) { - if (cur->chan && cur->chan != exception) { + if (cur->chan && cur->chan != exception) { ast_channel_publish_dial(in, cur->chan, NULL, status); - } - } + } + } } /*! \brief Hang up a list of outgoing calls */ @@ -3931,9 +3931,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies member_call_pending_clear(tmp->member); - if (ast_channel_cdr(qe->chan)) { - ast_cdr_busy(ast_channel_cdr(qe->chan)); - } + /* BUGBUG: Raise a BUSY dial end message here */ tmp->stillgoing = 0; ++*busies; return 0; @@ -3987,21 +3985,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies } else { ast_channel_exten_set(tmp->chan, ast_channel_exten(qe->chan)); } - if (ast_cdr_isset_unanswered()) { - /* they want to see the unanswered dial attempts! */ - /* set up the CDR fields on all the CDRs to give sensical information */ - ast_cdr_setdestchan(ast_channel_cdr(tmp->chan), ast_channel_name(tmp->chan)); - strcpy(ast_channel_cdr(tmp->chan)->clid, ast_channel_cdr(qe->chan)->clid); - strcpy(ast_channel_cdr(tmp->chan)->channel, ast_channel_cdr(qe->chan)->channel); - strcpy(ast_channel_cdr(tmp->chan)->src, ast_channel_cdr(qe->chan)->src); - strcpy(ast_channel_cdr(tmp->chan)->dst, ast_channel_exten(qe->chan)); - strcpy(ast_channel_cdr(tmp->chan)->dcontext, ast_channel_context(qe->chan)); - strcpy(ast_channel_cdr(tmp->chan)->lastapp, ast_channel_cdr(qe->chan)->lastapp); - strcpy(ast_channel_cdr(tmp->chan)->lastdata, ast_channel_cdr(qe->chan)->lastdata); - ast_channel_cdr(tmp->chan)->amaflags = ast_channel_cdr(qe->chan)->amaflags; - strcpy(ast_channel_cdr(tmp->chan)->accountcode, ast_channel_cdr(qe->chan)->accountcode); - strcpy(ast_channel_cdr(tmp->chan)->userfield, ast_channel_cdr(qe->chan)->userfield); - } ast_channel_unlock(tmp->chan); ast_channel_unlock(qe->chan); @@ -4371,14 +4354,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte if (pos == 1 /* not found */) { if (numlines == (numbusies + numnochan)) { ast_debug(1, "Everyone is busy at this time\n"); - if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) { - ast_cdr_busy(ast_channel_cdr(in)); - } + /* BUGBUG: We shouldn't have to set anything here, as each + * individual dial attempt should have set that CDR to busy + */ } else { ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan); - if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) { - ast_cdr_failed(ast_channel_cdr(in)); - } + /* BUGBUG: We shouldn't have to set anything here, as each + * individual dial attempt should have set that CDR to busy + */ } *to = 0; return NULL; @@ -4609,9 +4592,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte break; case AST_CONTROL_BUSY: ast_verb(3, "%s is busy\n", ochan_name); - if (ast_channel_cdr(in)) { - ast_cdr_busy(ast_channel_cdr(in)); - } ast_channel_publish_dial(qe->chan, o->chan, on, "BUSY"); do_hang(o); endtime = (long) time(NULL); @@ -4631,9 +4611,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte break; case AST_CONTROL_CONGESTION: ast_verb(3, "%s is circuit-busy\n", ochan_name); - if (ast_channel_cdr(in)) { - ast_cdr_failed(ast_channel_cdr(in)); - } ast_channel_publish_dial(qe->chan, o->chan, on, "CONGESTION"); endtime = (long) time(NULL); endtime -= starttime; @@ -4769,9 +4746,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte *to = 0; publish_dial_end_event(in, outgoing, NULL, "CANCEL"); ast_frfree(f); - if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) { - ast_cdr_noanswer(ast_channel_cdr(in)); - } return NULL; } if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass.integer)) { @@ -4780,9 +4754,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte publish_dial_end_event(in, outgoing, NULL, "CANCEL"); *digit = f->subclass.integer; ast_frfree(f); - if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) { - ast_cdr_noanswer(ast_channel_cdr(in)); - } return NULL; } @@ -4839,11 +4810,6 @@ skip_frame:; } publish_dial_end_event(qe->chan, outgoing, NULL, "NOANSWER"); - if (ast_channel_cdr(in) - && ast_channel_state(in) != AST_STATE_UP - && (!*to || ast_check_hangup(in))) { - ast_cdr_noanswer(ast_channel_cdr(in)); - } } #ifdef HAVE_EPOLL @@ -5678,20 +5644,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a if (res == -1) { ast_debug(1, "%s: Nobody answered.\n", ast_channel_name(qe->chan)); } - if (ast_cdr_isset_unanswered()) { - /* channel contains the name of one of the outgoing channels - in its CDR; zero out this CDR to avoid a dual-posting */ - struct callattempt *o; - for (o = outgoing; o; o = o->q_next) { - if (!o->chan) { - continue; - } - if (strcmp(ast_channel_cdr(o->chan)->dstchannel, ast_channel_cdr(qe->chan)->dstchannel) == 0) { - ast_set_flag(ast_channel_cdr(o->chan), AST_CDR_FLAG_POST_DISABLED); - break; - } - } - } } else { /* peer is valid */ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); /* Ah ha! Someone answered within the desired timeframe. Of course after this @@ -5785,17 +5737,14 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a } else { ast_moh_stop(qe->chan); } - /* If appropriate, log that we have a destination channel */ - if (ast_channel_cdr(qe->chan)) { - ast_cdr_setdestchan(ast_channel_cdr(qe->chan), ast_channel_name(peer)); - } + /* Make sure channels are compatible */ res = ast_channel_make_compatible(qe->chan, peer); if (res < 0) { ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "SYSCOMPAT", "%s", ""); ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(qe->chan), ast_channel_name(peer)); record_abandoned(qe); - ast_cdr_failed(ast_channel_cdr(qe->chan)); + ast_channel_publish_dial(qe->chan, peer, member->interface, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(peer))); ast_autoservice_chan_hangup_peer(qe->chan, peer); ao2_ref(member, -1); return -1; @@ -5855,10 +5804,10 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a ast_channel_unlock(qe->chan); if (monitorfilename) { ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1, X_REC_IN | X_REC_OUT); - } else if (ast_channel_cdr(qe->chan)) { - ast_monitor_start(which, qe->parent->monfmt, ast_channel_cdr(qe->chan)->uniqueid, 1, X_REC_IN | X_REC_OUT); + } else if (qe->chan) { + ast_monitor_start(which, qe->parent->monfmt, ast_channel_uniqueid(qe->chan), 1, X_REC_IN | X_REC_OUT); } else { - /* Last ditch effort -- no CDR, make up something */ + /* Last ditch effort -- no channel, make up something */ snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); ast_monitor_start(which, qe->parent->monfmt, tmpid, 1, X_REC_IN | X_REC_OUT); } @@ -5871,8 +5820,8 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a if (mixmonapp) { ast_debug(1, "Starting MixMonitor as requested.\n"); if (!monitorfilename) { - if (ast_channel_cdr(qe->chan)) { - ast_copy_string(tmpid, ast_channel_cdr(qe->chan)->uniqueid, sizeof(tmpid)); + if (qe->chan) { + ast_copy_string(tmpid, ast_channel_uniqueid(qe->chan), sizeof(tmpid)); } else { snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); } @@ -5944,14 +5893,15 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a } ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs); - /* We purposely lock the CDR so that pbx_exec does not update the application data */ - if (ast_channel_cdr(qe->chan)) { - ast_set_flag(ast_channel_cdr(qe->chan), AST_CDR_FLAG_LOCKED); - } + /* BUGBUG + * This needs to be done differently. We need to start a MixMonitor on + * the actual queue bridge itself, not drop some channel out and pull it + * back. Once the media channel work is done, start a media channel on + * the bridge. + * + * Alternatively, don't use pbx_exec to put an audio hook on a channel. + */ pbx_exec(qe->chan, mixmonapp, mixmonargs); - if (ast_channel_cdr(qe->chan)) { - ast_clear_flag(ast_channel_cdr(qe->chan), AST_CDR_FLAG_LOCKED); - } } else { ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n"); } @@ -6039,33 +5989,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "CONNECT", "%ld|%s|%ld", (long) time(NULL) - qe->start, ast_channel_uniqueid(peer), (long)(orig - to > 0 ? (orig - to) / 1000 : 0)); - if (ast_channel_cdr(qe->chan)) { - struct ast_cdr *cdr; - struct ast_cdr *newcdr; - - /* Only work with the last CDR in the stack*/ - cdr = ast_channel_cdr(qe->chan); - while (cdr->next) { - cdr = cdr->next; - } - - /* If this CDR is not related to us add new one*/ - if ((strcasecmp(cdr->uniqueid, ast_channel_uniqueid(qe->chan))) && - (strcasecmp(cdr->linkedid, ast_channel_uniqueid(qe->chan))) && - (newcdr = ast_cdr_dup(cdr))) { - ast_channel_lock(qe->chan); - ast_cdr_init(newcdr, qe->chan); - ast_cdr_reset(newcdr, 0); - cdr = ast_cdr_append(cdr, newcdr); - cdr = cdr->next; - ast_channel_unlock(qe->chan); - } - - if (update_cdr) { - ast_copy_string(cdr->dstchannel, member->membername, sizeof(cdr->dstchannel)); - } - } - blob = ast_json_pack("{s: s, s: s, s: s, s: i, s: i}", "Queue", queuename, "Interface", member->interface, diff --git a/cdr/cdr_adaptive_odbc.c b/cdr/cdr_adaptive_odbc.c index 4bf3602cbc..a590fb32a1 100644 --- a/cdr/cdr_adaptive_odbc.c +++ b/cdr/cdr_adaptive_odbc.c @@ -433,7 +433,7 @@ static int odbc_log(struct ast_cdr *cdr) ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S", &tm); colptr = colbuf; } else { - ast_cdr_getvar(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), 0, datefield ? 0 : 1); + ast_cdr_format_var(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), datefield ? 0 : 1); } if (colptr) { @@ -472,9 +472,9 @@ static int odbc_log(struct ast_cdr *cdr) * form (but only when we're dealing with a character-based field). */ if (strcasecmp(entry->name, "disposition") == 0) { - ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0); + ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0); } else if (strcasecmp(entry->name, "amaflags") == 0) { - ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0); + ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0); } /* Truncate too-long fields */ diff --git a/cdr/cdr_csv.c b/cdr/cdr_csv.c index 5cfde82d7d..a6f8a4dc0a 100644 --- a/cdr/cdr_csv.c +++ b/cdr/cdr_csv.c @@ -234,7 +234,7 @@ static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr) /* Disposition */ append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize); /* AMA Flags */ - append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize); + append_string(buf, ast_channel_amaflags2string(cdr->amaflags), bufsize); /* Unique ID */ if (loguniqueid) append_string(buf, cdr->uniqueid, bufsize); @@ -285,9 +285,6 @@ static int csv_log(struct ast_cdr *cdr) char buf[1024]; char csvmaster[PATH_MAX]; snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER); -#if 0 - printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode); -#endif if (build_csv_record(buf, sizeof(buf), cdr)) { ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes. CDR not recorded!\n", (int)sizeof(buf)); return 0; diff --git a/cdr/cdr_custom.c b/cdr/cdr_custom.c index 290e5344da..2a3b1a1dd0 100644 --- a/cdr/cdr_custom.c +++ b/cdr/cdr_custom.c @@ -67,20 +67,20 @@ AST_THREADSTORAGE(custom_buf); static const char name[] = "cdr-custom"; -struct cdr_config { +struct cdr_custom_config { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(filename); AST_STRING_FIELD(format); ); ast_mutex_t lock; - AST_RWLIST_ENTRY(cdr_config) list; + AST_RWLIST_ENTRY(cdr_custom_config) list; }; -static AST_RWLIST_HEAD_STATIC(sinks, cdr_config); +static AST_RWLIST_HEAD_STATIC(sinks, cdr_custom_config); static void free_config(void) { - struct cdr_config *sink; + struct cdr_custom_config *sink; while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) { ast_mutex_destroy(&sink->lock); ast_free(sink); @@ -103,7 +103,7 @@ static int load_config(void) var = ast_variable_browse(cfg, "mappings"); while (var) { if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) { - struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024); + struct cdr_custom_config *sink = ast_calloc_with_stringfields(1, struct cdr_custom_config, 1024); if (!sink) { ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n"); @@ -130,7 +130,7 @@ static int custom_log(struct ast_cdr *cdr) { struct ast_channel *dummy; struct ast_str *str; - struct cdr_config *config; + struct cdr_custom_config *config; /* Batching saves memory management here. Otherwise, it's the same as doing an allocation and free each time. */ if (!(str = ast_str_thread_get(&custom_buf, 16))) { diff --git a/cdr/cdr_manager.c b/cdr/cdr_manager.c index a82bcf9895..e3ae7a57d2 100644 --- a/cdr/cdr_manager.c +++ b/cdr/cdr_manager.c @@ -203,7 +203,7 @@ static int manager_log(struct ast_cdr *cdr) cdr->accountcode, cdr->src, cdr->dst, cdr->dcontext, cdr->clid, cdr->channel, cdr->dstchannel, cdr->lastapp, cdr->lastdata, strStartTime, strAnswerTime, strEndTime, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), - ast_cdr_flags2str(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf); + ast_channel_amaflags2string(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf); return 0; } diff --git a/cdr/cdr_odbc.c b/cdr/cdr_odbc.c index 7ea2f041fe..022d75210e 100644 --- a/cdr/cdr_odbc.c +++ b/cdr/cdr_odbc.c @@ -124,10 +124,13 @@ static SQLHSTMT execute_cb(struct odbc_obj *obj, void *data) SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL); } - if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING)) - SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL); - else + if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING)) { + char *disposition; + disposition = ast_strdupa(ast_cdr_disp2str(cdr->disposition)); + SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(disposition) + 1, 0, disposition, 0, NULL); + } else { SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->disposition, 0, NULL); + } SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->amaflags, 0, NULL); SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->accountcode), 0, cdr->accountcode, 0, NULL); diff --git a/cdr/cdr_pgsql.c b/cdr/cdr_pgsql.c index 906f0227c7..dc73de4779 100644 --- a/cdr/cdr_pgsql.c +++ b/cdr/cdr_pgsql.c @@ -222,9 +222,9 @@ static int pgsql_log(struct ast_cdr *cdr) AST_RWLIST_RDLOCK(&psql_columns); AST_RWLIST_TRAVERSE(&psql_columns, cur, list) { /* For fields not set, simply skip them */ - ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0); + ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0); if (strcmp(cur->name, "calldate") == 0 && !value) { - ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0); + ast_cdr_format_var(cdr, "start", &value, buf, sizeof(buf), 0); } if (!value) { if (cur->notnull && !cur->hasdefault) { @@ -286,7 +286,7 @@ static int pgsql_log(struct ast_cdr *cdr) } else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) { if (cur->type[0] == 'i') { /* Get integer, no need to escape anything */ - ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0); + ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0); LENGTHEN_BUF2(13); ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value); } else if (strncmp(cur->type, "float", 5) == 0) { @@ -302,18 +302,18 @@ static int pgsql_log(struct ast_cdr *cdr) } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) { if (strncmp(cur->type, "int", 3) == 0) { /* Integer, no need to escape anything */ - ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1); + ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 1); LENGTHEN_BUF2(13); ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value); } else { /* Although this is a char field, there are no special characters in the values for these fields */ - ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0); + ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0); LENGTHEN_BUF2(31); ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value); } } else { /* Arbitrary field, could be anything */ - ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0); + ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0); if (strncmp(cur->type, "int", 3) == 0) { long long whatever; if (value && sscanf(value, "%30lld", &whatever) == 1) { diff --git a/cdr/cdr_radius.c b/cdr/cdr_radius.c index 92ec8a4b48..2bf2002feb 100644 --- a/cdr/cdr_radius.c +++ b/cdr/cdr_radius.c @@ -170,12 +170,12 @@ static int build_radius_record(VALUE_PAIR **tosend, struct ast_cdr *cdr) return -1; /* Disposition */ - tmp = ast_cdr_disp2str(cdr->disposition); + tmp = ast_strdupa(ast_cdr_disp2str(cdr->disposition)); if (!rc_avpair_add(rh, tosend, PW_AST_DISPOSITION, tmp, strlen(tmp), VENDOR_CODE)) return -1; /* AMA Flags */ - tmp = ast_cdr_flags2str(cdr->amaflags); + tmp = ast_strdupa(ast_channel_amaflags2string(cdr->amaflags)); if (!rc_avpair_add(rh, tosend, PW_AST_AMA_FLAGS, tmp, strlen(tmp), VENDOR_CODE)) return -1; diff --git a/cdr/cdr_syslog.c b/cdr/cdr_syslog.c index 8a7f07713a..dec4d65e9d 100644 --- a/cdr/cdr_syslog.c +++ b/cdr/cdr_syslog.c @@ -60,7 +60,7 @@ AST_THREADSTORAGE(syslog_buf); static const char name[] = "cdr-syslog"; -struct cdr_config { +struct cdr_syslog_config { AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(ident); AST_STRING_FIELD(format); @@ -68,14 +68,14 @@ struct cdr_config { int facility; int priority; ast_mutex_t lock; - AST_LIST_ENTRY(cdr_config) list; + AST_LIST_ENTRY(cdr_syslog_config) list; }; -static AST_RWLIST_HEAD_STATIC(sinks, cdr_config); +static AST_RWLIST_HEAD_STATIC(sinks, cdr_syslog_config); static void free_config(void) { - struct cdr_config *sink; + struct cdr_syslog_config *sink; while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) { ast_mutex_destroy(&sink->lock); ast_free(sink); @@ -86,7 +86,7 @@ static int syslog_log(struct ast_cdr *cdr) { struct ast_channel *dummy; struct ast_str *str; - struct cdr_config *sink; + struct cdr_syslog_config *sink; /* Batching saves memory management here. Otherwise, it's the same as doing an allocation and free each time. */ @@ -174,7 +174,7 @@ static int load_config(int reload) } while ((catg = ast_category_browse(cfg, catg))) { - struct cdr_config *sink; + struct cdr_syslog_config *sink; if (!strcasecmp(catg, "general")) { continue; @@ -186,7 +186,7 @@ static int load_config(int reload) continue; } - sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024); + sink = ast_calloc_with_stringfields(1, struct cdr_syslog_config, 1024); if (!sink) { ast_log(AST_LOG_ERROR, diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c index dd75dbb464..aef57b55d1 100644 --- a/cdr/cdr_tds.c +++ b/cdr/cdr_tds.c @@ -176,7 +176,7 @@ retry: settings->table, accountcode, src, dst, dcontext, clid, channel, dstchannel, lastapp, lastdata, start, answer, end, hrduration, - hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid, + hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid, userfield ); } else { @@ -196,7 +196,7 @@ retry: settings->table, accountcode, src, dst, dcontext, clid, channel, dstchannel, lastapp, lastdata, start, answer, end, cdr->duration, - cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid, + cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid, userfield ); } @@ -226,7 +226,7 @@ retry: settings->table, accountcode, src, dst, dcontext, clid, channel, dstchannel, lastapp, lastdata, start, answer, end, hrduration, - hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid + hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid ); } else { erc = dbfcmd(settings->dbproc, @@ -245,7 +245,7 @@ retry: settings->table, accountcode, src, dst, dcontext, clid, channel, dstchannel, lastapp, lastdata, start, answer, end, cdr->duration, - cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid + cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid ); } } diff --git a/cel/cel_manager.c b/cel/cel_manager.c index e1d0dc148c..245c7800d4 100644 --- a/cel/cel_manager.c +++ b/cel/cel_manager.c @@ -129,7 +129,7 @@ static void manager_log(const struct ast_event *event, void *userdata) record.application_name, record.application_data, start_time, - ast_cel_get_ama_flag_name(record.amaflag), + ast_channel_amaflags2string(record.amaflag), record.unique_id, record.linked_id, record.user_field, diff --git a/cel/cel_radius.c b/cel/cel_radius.c index 0edf57f4ce..9067a04918 100644 --- a/cel/cel_radius.c +++ b/cel/cel_radius.c @@ -150,7 +150,7 @@ static int build_radius_record(VALUE_PAIR **send, struct ast_cel_event_record *r return -1; } /* AMA Flags */ - amaflags = ast_strdupa(ast_cel_get_ama_flag_name(record->amaflag)); + amaflags = ast_strdupa(ast_channel_amaflags2string(record->amaflag)); if (!rc_avpair_add(rh, send, PW_AST_AMA_FLAGS, amaflags, strlen(amaflags), VENDOR_CODE)) { return -1; } diff --git a/cel/cel_tds.c b/cel/cel_tds.c index df2b573bf2..1bb4d517e6 100644 --- a/cel/cel_tds.c +++ b/cel/cel_tds.c @@ -206,7 +206,7 @@ retry: ciddnid_ai, exten_ai, context_ai, channel_ai, app_ai, appdata_ai, start, (record.event_type == AST_CEL_USER_DEFINED) ? record.user_defined_name : record.event_name, - ast_cel_get_ama_flag_name(record.amaflag), uniqueid_ai, linkedid_ai, + ast_channel_amaflags2string(record.amaflag), uniqueid_ai, linkedid_ai, userfield_ai, peer_ai); if (erc == FAIL) { diff --git a/channels/chan_agent.c b/channels/chan_agent.c index d72254ee75..57f0914cfd 100644 --- a/channels/chan_agent.c +++ b/channels/chan_agent.c @@ -112,10 +112,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - - - - Resets the Call Data Record. - - - - - - - - - - - - - This application causes the Call Data Record to be reset. - - - ForkCDR - NoCDR - - Indicate ringing tone. @@ -657,9 +627,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") This application will set the channel's AMA Flags for billing purposes. + This application is deprecated. Please use the CHANNEL function instead. CDR + CHANNEL @@ -1139,7 +1111,6 @@ static int pbx_builtin_background(struct ast_channel *, const char *); static int pbx_builtin_wait(struct ast_channel *, const char *); static int pbx_builtin_waitexten(struct ast_channel *, const char *); static int pbx_builtin_incomplete(struct ast_channel *, const char *); -static int pbx_builtin_resetcdr(struct ast_channel *, const char *); static int pbx_builtin_setamaflags(struct ast_channel *, const char *); static int pbx_builtin_ringing(struct ast_channel *, const char *); static int pbx_builtin_proceeding(struct ast_channel *, const char *); @@ -1326,7 +1297,6 @@ static struct pbx_builtin { { "Proceeding", pbx_builtin_proceeding }, { "Progress", pbx_builtin_progress }, { "RaiseException", pbx_builtin_raise_exception }, - { "ResetCDR", pbx_builtin_resetcdr }, { "Ringing", pbx_builtin_ringing }, { "SayAlpha", pbx_builtin_saycharacters }, { "SayDigits", pbx_builtin_saydigits }, @@ -1565,15 +1535,13 @@ int pbx_exec(struct ast_channel *c, /*!< Channel */ const char *saved_c_appl; const char *saved_c_data; - if (ast_channel_cdr(c) && !ast_check_hangup(c)) - ast_cdr_setapp(ast_channel_cdr(c), app->name, data); - /* save channel values */ saved_c_appl= ast_channel_appl(c); saved_c_data= ast_channel_data(c); ast_channel_appl_set(c, app->name); ast_channel_data_set(c, data); + ast_channel_publish_snapshot(c); if (app->module) u = __ast_module_user_add(app->module, c); @@ -5713,10 +5681,6 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context) ast_channel_lock(chan); - if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) { - ast_cdr_end(ast_channel_cdr(chan)); - } - /* Set h exten location */ if (context != ast_channel_context(chan)) { ast_channel_context_set(chan, context); @@ -5797,10 +5761,6 @@ int ast_pbx_hangup_handler_run(struct ast_channel *chan) return 0; } - if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) { - ast_cdr_end(ast_channel_cdr(chan)); - } - /* * Make sure that the channel is marked as hungup since we are * going to run the hangup handlers on it. @@ -6114,12 +6074,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, set_ext_pri(c, "s", 1); } - ast_channel_lock(c); - if (ast_channel_cdr(c)) { - /* allow CDR variables that have been collected after channel was created to be visible during call */ - ast_cdr_update(c); - } - ast_channel_unlock(c); for (;;) { char dst_exten[256]; /* buffer to accumulate digits */ int pos = 0; /* XXX should check bounds */ @@ -6229,11 +6183,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, } /* Call timed out with no special extension to jump to. */ } - ast_channel_lock(c); - if (ast_channel_cdr(c)) { - ast_cdr_update(c); - } - ast_channel_unlock(c); error = 1; break; } @@ -6339,12 +6288,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, } } } - ast_channel_lock(c); - if (ast_channel_cdr(c)) { - ast_verb(2, "CDR updated on %s\n",ast_channel_name(c)); - ast_cdr_update(c); - } - ast_channel_unlock(c); } } @@ -9991,13 +9934,16 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co } dialed = ast_dial_get_channel(outgoing->dial, 0); + if (!dialed) { + return -1; + } ast_set_variables(dialed, vars); if (account) { - ast_cdr_setaccount(dialed, account); + ast_channel_accountcode_set(dialed, account); } - ast_set_flag(ast_channel_cdr(dialed), AST_CDR_FLAG_ORIGINATED); + ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED); if (!ast_strlen_zero(cid_num) && !ast_strlen_zero(cid_name)) { struct ast_party_connected_line connected; @@ -10043,12 +9989,13 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co /* Wait for dialing to complete */ if (channel || synchronous) { if (channel) { + ast_channel_ref(*channel); ast_channel_unlock(*channel); } while (!outgoing->dialed) { ast_cond_wait(&outgoing->cond, &outgoing->lock); } - if (channel) { + if (channel && *channel) { ast_channel_lock(*channel); } } @@ -10078,7 +10025,7 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co } if (account) { - ast_cdr_setaccount(failed, account); + ast_channel_accountcode_set(failed, account); } set_ext_pri(failed, "failed", 1); @@ -10387,8 +10334,8 @@ static int pbx_builtin_busy(struct ast_channel *chan, const char *data) /* Don't change state of an UP channel, just indicate busy in audio */ if (ast_channel_state(chan) != AST_STATE_UP) { + ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY); ast_setstate(chan, AST_STATE_BUSY); - ast_cdr_busy(ast_channel_cdr(chan)); } wait_for_hangup(chan, data); return -1; @@ -10403,8 +10350,8 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data) /* Don't change state of an UP channel, just indicate congestion in audio */ if (ast_channel_state(chan) != AST_STATE_UP) { + ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION); ast_setstate(chan, AST_STATE_BUSY); - ast_cdr_congestion(ast_channel_cdr(chan)); } wait_for_hangup(chan, data); return -1; @@ -10416,7 +10363,6 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data) static int pbx_builtin_answer(struct ast_channel *chan, const char *data) { int delay = 0; - int answer_cdr = 1; char *parse; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(delay); @@ -10424,7 +10370,7 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data) ); if (ast_strlen_zero(data)) { - return __ast_answer(chan, 0, 1); + return __ast_answer(chan, 0); } parse = ast_strdupa(data); @@ -10439,10 +10385,12 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data) } if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) { - answer_cdr = 0; + if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) { + ast_log(AST_LOG_WARNING, "Failed to disable CDR on %s\n", ast_channel_name(chan)); + } } - return __ast_answer(chan, delay, answer_cdr); + return __ast_answer(chan, delay); } static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) @@ -10459,7 +10407,7 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) if (ast_check_hangup(chan)) { return -1; } else if (ast_channel_state(chan) != AST_STATE_UP && answer) { - __ast_answer(chan, 0, 1); + __ast_answer(chan, 0); } ast_indicate(chan, AST_CONTROL_INCOMPLETE); @@ -10467,39 +10415,30 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) return AST_PBX_INCOMPLETE; } -AST_APP_OPTIONS(resetcdr_opts, { - AST_APP_OPTION('w', AST_CDR_FLAG_POSTED), - AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED), - AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), - AST_APP_OPTION('e', AST_CDR_FLAG_POST_ENABLE), -}); - /*! * \ingroup applications */ -static int pbx_builtin_resetcdr(struct ast_channel *chan, const char *data) +static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) { - char *args; - struct ast_flags flags = { 0 }; + ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n"); - if (!ast_strlen_zero(data)) { - args = ast_strdupa(data); - ast_app_parse_options(resetcdr_opts, &flags, NULL, args); + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n"); + return 0; } - - ast_cdr_reset(ast_channel_cdr(chan), &flags); - - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) -{ /* Copy the AMA Flags as specified */ ast_channel_lock(chan); - ast_cdr_setamaflags(chan, data ? data : ""); + if (isdigit(data[0])) { + int amaflags; + if (sscanf(data, "%30d", &amaflags) != 1) { + ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan)); + ast_channel_unlock(chan); + return 0; + } + ast_channel_amaflags_set(chan, amaflags); + } else { + ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data)); + } ast_channel_unlock(chan); return 0; } diff --git a/main/stasis.c b/main/stasis.c index e810dd852d..406a1bb254 100644 --- a/main/stasis.c +++ b/main/stasis.c @@ -32,6 +32,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/astobj2.h" +#include "asterisk/stasis_internal.h" #include "asterisk/stasis.h" #include "asterisk/threadpool.h" #include "asterisk/taskprocessor.h" @@ -170,7 +171,7 @@ static void subscription_invoke(struct stasis_subscription *sub, static void send_subscription_change_message(struct stasis_topic *topic, char *uniqueid, char *description); -static struct stasis_subscription *__stasis_subscribe( +struct stasis_subscription *internal_stasis_subscribe( struct stasis_topic *topic, stasis_subscription_cb callback, void *data, @@ -213,7 +214,7 @@ struct stasis_subscription *stasis_subscribe( stasis_subscription_cb callback, void *data) { - return __stasis_subscribe(topic, callback, data, 1); + return internal_stasis_subscribe(topic, callback, data, 1); } struct stasis_subscription *stasis_unsubscribe(struct stasis_subscription *sub) @@ -476,7 +477,7 @@ struct stasis_subscription *stasis_forward_all(struct stasis_topic *from_topic, * mailbox. Otherwise, messages forwarded to the same topic from * different topics may get reordered. Which is bad. */ - sub = __stasis_subscribe(from_topic, stasis_forward_cb, to_topic, 0); + sub = internal_stasis_subscribe(from_topic, stasis_forward_cb, to_topic, 0); if (sub) { /* hold a ref to to_topic for this forwarding subscription */ ao2_ref(to_topic, +1); diff --git a/main/stasis_cache.c b/main/stasis_cache.c index 115bb7b67b..5757c869a8 100644 --- a/main/stasis_cache.c +++ b/main/stasis_cache.c @@ -33,6 +33,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/astobj2.h" #include "asterisk/hashtab.h" +#include "asterisk/stasis_internal.h" #include "asterisk/stasis.h" #include "asterisk/utils.h" @@ -486,7 +487,7 @@ struct stasis_caching_topic *stasis_caching_topic_create(struct stasis_topic *or caching_topic->id_fn = id_fn; - sub = stasis_subscribe(original_topic, caching_topic_exec, caching_topic); + sub = internal_stasis_subscribe(original_topic, caching_topic_exec, caching_topic, 0); if (sub == NULL) { return NULL; } diff --git a/main/stasis_channels.c b/main/stasis_channels.c index 2a88b00685..e76f258243 100644 --- a/main/stasis_channels.c +++ b/main/stasis_channels.c @@ -156,10 +156,17 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha ast_string_field_set(snapshot, exten, ast_channel_exten(chan)); ast_string_field_set(snapshot, caller_name, - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "")); + S_COR(ast_channel_caller(chan)->ani.name.valid, ast_channel_caller(chan)->ani.name.str, + S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""))); ast_string_field_set(snapshot, caller_number, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "")); - + S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, + S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""))); + ast_string_field_set(snapshot, caller_dnid, S_OR(ast_channel_dialed(chan)->number.str, "")); + ast_string_field_set(snapshot, caller_subaddr, + S_COR(ast_channel_caller(chan)->ani.subaddress.valid, ast_channel_caller(chan)->ani.subaddress.str, + S_COR(ast_channel_caller(chan)->id.subaddress.valid, ast_channel_caller(chan)->id.subaddress.str, ""))); + ast_string_field_set(snapshot, dialed_subaddr, + S_COR(ast_channel_dialed(chan)->subaddress.valid, ast_channel_dialed(chan)->subaddress.str, "")); ast_string_field_set(snapshot, caller_ani, S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, "")); ast_string_field_set(snapshot, caller_rdnis, @@ -493,20 +500,6 @@ struct ast_json *ast_multi_channel_blob_get_json(struct ast_multi_channel_blob * return obj->blob; } -void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_type *type, struct ast_json *blob) -{ - RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); - - if (!blob) { - blob = ast_json_null(); - } - - message = ast_channel_blob_create(chan, type, blob); - if (message) { - stasis_publish(ast_channel_topic(chan), message); - } -} - void ast_channel_publish_snapshot(struct ast_channel *chan) { RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); @@ -526,6 +519,20 @@ void ast_channel_publish_snapshot(struct ast_channel *chan) stasis_publish(ast_channel_topic(chan), message); } +void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_type *type, struct ast_json *blob) +{ + RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); + + if (!blob) { + blob = ast_json_null(); + } + + message = ast_channel_blob_create(chan, type, blob); + if (message) { + stasis_publish(ast_channel_topic(chan), message); + } +} + void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); diff --git a/main/test.c b/main/test.c index 2109c9478b..fdc4916e16 100644 --- a/main/test.c +++ b/main/test.c @@ -48,6 +48,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); #include "asterisk/stasis.h" #include "asterisk/json.h" #include "asterisk/astobj2.h" +#include "asterisk/stasis.h" +#include "asterisk/json.h" /*! \since 12 * \brief The topic for test suite messages @@ -80,9 +82,11 @@ struct ast_test { * CLI in addition to being saved off in status_str. */ struct ast_cli_args *cli; - enum ast_test_result_state state; /*!< current test state */ - unsigned int time; /*!< time in ms test took */ - ast_test_cb_t *cb; /*!< test callback function */ + enum ast_test_result_state state; /*!< current test state */ + unsigned int time; /*!< time in ms test took */ + ast_test_cb_t *cb; /*!< test callback function */ + ast_test_init_cb_t *init_cb; /*!< test init function */ + ast_test_cleanup_cb_t *cleanup_cb; /*!< test cleanup function */ AST_LIST_ENTRY(ast_test) entry; }; @@ -159,6 +163,40 @@ int __ast_test_status_update(const char *file, const char *func, int line, struc return 0; } +int ast_test_register_init(const char *category, ast_test_init_cb_t *cb) +{ + struct ast_test *test; + int registered = 1; + + AST_LIST_LOCK(&tests); + AST_LIST_TRAVERSE(&tests, test, entry) { + if (!(test_cat_cmp(test->info.category, category))) { + test->init_cb = cb; + registered = 0; + } + } + AST_LIST_UNLOCK(&tests); + + return registered; +} + +int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb) +{ + struct ast_test *test; + int registered = 1; + + AST_LIST_LOCK(&tests); + AST_LIST_TRAVERSE(&tests, test, entry) { + if (!(test_cat_cmp(test->info.category, category))) { + test->cleanup_cb = cb; + registered = 0; + } + } + AST_LIST_UNLOCK(&tests); + + return registered; +} + int ast_test_register(ast_test_cb_t *cb) { struct ast_test *test; @@ -203,14 +241,34 @@ int ast_test_unregister(ast_test_cb_t *cb) static void test_execute(struct ast_test *test) { struct timeval begin; + enum ast_test_result_state result; ast_str_reset(test->status_str); begin = ast_tvnow(); - test->state = test->cb(&test->info, TEST_EXECUTE, test); + if (test->init_cb && test->init_cb(&test->info, test)) { + test->state = AST_TEST_FAIL; + goto exit; + } + result = test->cb(&test->info, TEST_EXECUTE, test); + if (test->state != AST_TEST_FAIL) { + test->state = result; + } + if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) { + test->state = AST_TEST_FAIL; + } +exit: test->time = ast_tvdiff_ms(ast_tvnow(), begin); } +void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state) +{ + if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) { + return; + } + test->state = state; +} + static void test_xml_entry(struct ast_test *test, FILE *f) { if (!f || !test || test->state == AST_TEST_NOT_RUN) { diff --git a/main/utils.c b/main/utils.c index fde9b953b7..1007254875 100644 --- a/main/utils.c +++ b/main/utils.c @@ -1546,6 +1546,15 @@ int ast_remaining_ms(struct timeval start, int max_ms) return ms; } +void ast_format_duration_hh_mm_ss(int duration, char *buf, size_t length) +{ + int durh, durm, durs; + durh = duration / 3600; + durm = (duration % 3600) / 60; + durs = duration % 60; + snprintf(buf, length, "%02d:%02d:%02d", durh, durm, durs); +} + #undef ONE_MILLION #ifndef linux diff --git a/res/res_agi.c b/res/res_agi.c index 486310dd6b..a841f36235 100644 --- a/res/res_agi.c +++ b/res/res_agi.c @@ -3625,11 +3625,6 @@ static enum agi_result agi_handle_command(struct ast_channel *chan, AGI *agi, ch the module we are using */ if (c->mod != ast_module_info->self) ast_module_ref(c->mod); - /* If the AGI command being executed is an actual application (using agi exec) - the app field will be updated in pbx_exec via handle_exec */ - if (ast_channel_cdr(chan) && !ast_check_hangup(chan) && strcasecmp(argv[0], "EXEC")) - ast_cdr_setapp(ast_channel_cdr(chan), "AGI", buf); - res = c->handler(chan, agi, argc, argv); if (c->mod != ast_module_info->self) ast_module_unref(c->mod); diff --git a/res/res_config_sqlite.c b/res/res_config_sqlite.c index e648f941b7..7d5fd83e6a 100644 --- a/res/res_config_sqlite.c +++ b/res/res_config_sqlite.c @@ -791,7 +791,7 @@ static int cdr_handler(struct ast_cdr *cdr) AST_RWLIST_TRAVERSE(&(tbl->columns), col, list) { if (col->isint) { - ast_cdr_getvar(cdr, col->name, &tmp, workspace, sizeof(workspace), 0, 1); + ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 1); if (!tmp) { continue; } @@ -800,7 +800,7 @@ static int cdr_handler(struct ast_cdr *cdr) ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", scannum); } } else { - ast_cdr_getvar(cdr, col->name, &tmp, workspace, sizeof(workspace), 0, 0); + ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 0); if (!tmp) { continue; } diff --git a/res/res_monitor.c b/res/res_monitor.c index f1da4ec834..b5225010e2 100644 --- a/res/res_monitor.c +++ b/res/res_monitor.c @@ -692,18 +692,10 @@ static int start_monitor_exec(struct ast_channel *chan, const char *data) } if (!ast_strlen_zero(urlprefix) && !ast_strlen_zero(args.fname_base)) { - struct ast_cdr *chan_cdr; snprintf(tmp, sizeof(tmp), "%s/%s.%s", urlprefix, args.fname_base, ((strcmp(args.format, "gsm")) ? "wav" : "gsm")); ast_channel_lock(chan); - if (!ast_channel_cdr(chan)) { - if (!(chan_cdr = ast_cdr_alloc())) { - ast_channel_unlock(chan); - return -1; - } - ast_channel_cdr_set(chan, chan_cdr); - } - ast_cdr_setuserfield(chan, tmp); + ast_cdr_setuserfield(ast_channel_name(chan), tmp); ast_channel_unlock(chan); } if (waitforbridge) { diff --git a/res/res_stasis_answer.c b/res/res_stasis_answer.c index b7534b93d9..53d4b06e28 100644 --- a/res/res_stasis_answer.c +++ b/res/res_stasis_answer.c @@ -42,10 +42,9 @@ static void *app_control_answer(struct stasis_app_control *control, struct ast_channel *chan, void *data) { const int delay = 0; - const int cdr_answer = 1; ast_debug(3, "%s: Answering", stasis_app_control_get_channel_id(control)); - return __ast_answer(chan, delay, cdr_answer) == 0 ? &OK : &FAIL; + return __ast_answer(chan, delay) == 0 ? &OK : &FAIL; } int stasis_app_control_answer(struct stasis_app_control *control) diff --git a/tests/test_cdr.c b/tests/test_cdr.c new file mode 100644 index 0000000000..c9621a450f --- /dev/null +++ b/tests/test_cdr.c @@ -0,0 +1,2413 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Matt Jordan + * + * 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 CDR unit tests + * + * \author Matt Jordan + * + */ + +/*** MODULEINFO + TEST_FRAMEWORK + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include "asterisk/module.h" +#include "asterisk/test.h" +#include "asterisk/cdr.h" +#include "asterisk/linkedlists.h" +#include "asterisk/chanvars.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/time.h" +#include "asterisk/bridging.h" +#include "asterisk/bridging_basic.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_bridging.h" + +#define EPSILON 0.001 + +#define TEST_CATEGORY "/main/cdr/" + +#define MOCK_CDR_BACKEND "mock_cdr_backend" + +#define CHANNEL_TECH_NAME "CDRTestChannel" + +/*! \brief A placeholder for Asterisk's 'real' CDR configuration */ +static struct ast_cdr_config *saved_config; + +/*! \brief A configuration suitable for 'normal' CDRs */ +static struct ast_cdr_config debug_cdr_config = { + .settings.flags = CDR_ENABLED | CDR_DEBUG, +}; + +/*! \brief A configuration suitable for CDRs with unanswered records */ +static struct ast_cdr_config unanswered_cdr_config = { + .settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG, +}; + +/*! \brief A configuration suitable for CDRs with congestion enabled */ +static struct ast_cdr_config congestion_cdr_config = { + .settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG | CDR_CONGESTION, +}; + +/*! \brief Macro to swap a configuration out from the CDR engine. This should be + * used at the beginning of each test to set the needed configuration for that + * test. + */ +#define SWAP_CONFIG(ao2_config, template) do { \ + *(ao2_config) = (template); \ + ast_cdr_set_config((ao2_config)); \ + } while (0) + +/*! \brief A linked list of received CDR entries from the engine */ +static AST_LIST_HEAD(, test_cdr_entry) actual_cdr_entries = AST_LIST_HEAD_INIT_VALUE; + +/*! \brief The Mock CDR backend condition wait */ +static ast_cond_t mock_cdr_cond; + +/*! \brief A channel technology used for the unit tests */ +static struct ast_channel_tech test_cdr_chan_tech = { + .type = CHANNEL_TECH_NAME, + .description = "Mock channel technology for CDR tests", +}; + +struct test_cdr_entry { + struct ast_cdr *cdr; + AST_LIST_ENTRY(test_cdr_entry) list; +}; + +/*! \brief The number of CDRs the mock backend has received */ +static int global_mock_cdr_count; + +/*! \internal + * \brief Callback function for the mock CDR backend + * + * This function 'processes' a dispatched CDR record by adding it to the + * \ref actual_cdr_entries list. When a test completes, it can verify the + * expected records against this list of actual CDRs created by the engine. + * + * \param cdr The public CDR object created by the engine + * + * \retval -1 on error + * \retval 0 on success + */ +static int mock_cdr_backend_cb(struct ast_cdr *cdr) +{ + struct ast_cdr *cdr_copy, *cdr_prev = NULL; + struct ast_cdr *mock_cdr = NULL; + struct test_cdr_entry *cdr_wrapper; + + cdr_wrapper = ast_calloc(1, sizeof(*cdr_wrapper)); + if (!cdr_wrapper) { + return -1; + } + + for (; cdr; cdr = cdr->next) { + struct ast_var_t *var_entry, *var_copy; + + cdr_copy = ast_calloc(1, sizeof(*cdr_copy)); + if (!cdr_copy) { + return -1; + } + *cdr_copy = *cdr; + cdr_copy->varshead.first = NULL; + cdr_copy->varshead.last = NULL; + cdr_copy->next = NULL; + + AST_LIST_TRAVERSE(&cdr->varshead, var_entry, entries) { + var_copy = ast_var_assign(var_entry->name, var_entry->value); + if (!var_copy) { + return -1; + } + AST_LIST_INSERT_TAIL(&cdr_copy->varshead, var_copy, entries); + } + + if (!mock_cdr) { + mock_cdr = cdr_copy; + } + if (cdr_prev) { + cdr_prev->next = cdr_copy; + } + cdr_prev = cdr_copy; + } + cdr_wrapper->cdr = mock_cdr; + + AST_LIST_LOCK(&actual_cdr_entries); + AST_LIST_INSERT_TAIL(&actual_cdr_entries, cdr_wrapper, list); + global_mock_cdr_count++; + ast_cond_signal(&mock_cdr_cond); + AST_LIST_UNLOCK(&actual_cdr_entries); + + return 0; +} + +/*! \internal + * \brief Remove all entries from \ref actual_cdr_entries + */ +static void clear_mock_cdr_backend(void) +{ + struct test_cdr_entry *cdr_wrapper; + + AST_LIST_LOCK(&actual_cdr_entries); + while ((cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list))) { + ast_cdr_free(cdr_wrapper->cdr); + ast_free(cdr_wrapper); + } + global_mock_cdr_count = 0; + AST_LIST_UNLOCK(&actual_cdr_entries); +} + +/*! \brief Verify a string field. This will set the test status result to fail; + * as such, it assumes that (a) test is the test object variable, and (b) that + * a return variable res exists. + */ +#define VERIFY_STRING_FIELD(field, actual, expected) do { \ + if (strcmp((actual)->field, (expected)->field)) { \ + ast_test_status_update(test, "Field %s failed: actual %s, expected %s\n", #field, (actual)->field, (expected)->field); \ + ast_test_set_result(test, AST_TEST_FAIL); \ + res = AST_TEST_FAIL; \ + } } while (0) + +/*! \brief Verify a numeric field. This will set the test status result to fail; + * as such, it assumes that (a) test is the test object variable, and (b) that + * a return variable res exists. + */ +#define VERIFY_NUMERIC_FIELD(field, actual, expected) do { \ + if ((actual)->field != (expected)->field) { \ + ast_test_status_update(test, "Field %s failed: actual %ld, expected %ld\n", #field, (long)(actual)->field, (long)(expected)->field); \ + ast_test_set_result(test, AST_TEST_FAIL); \ + res = AST_TEST_FAIL; \ + } } while (0) + +/*! \brief Verify a time field. This will set the test status result to fail; + * as such, it assumes that (a) test is the test object variable, and (b) that + * a return variable res exists. + */ +#define VERIFY_TIME_VALUE(field, actual) do { \ + if (ast_tvzero((actual)->field)) { \ + ast_test_status_update(test, "Field %s failed: should not be 0\n", #field); \ + ast_test_set_result(test, AST_TEST_FAIL); \ + res = AST_TEST_FAIL; \ + } } while (0) + +/*! \brief Alice's Caller ID */ +#define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, } + +/*! \brief Bob's Caller ID */ +#define BOB_CALLERID { .id.name.str = "Bob", .id.name.valid = 1, .id.number.str = "200", .id.number.valid = 1, } + +/*! \brief Charlie's Caller ID */ +#define CHARLIE_CALLERID { .id.name.str = "Charlie", .id.name.valid = 1, .id.number.str = "300", .id.number.valid = 1, } + +/*! \brief David's Caller ID */ +#define DAVID_CALLERID { .id.name.str = "David", .id.name.valid = 1, .id.number.str = "400", .id.number.valid = 1, } + +/*! \brief Copy the linkedid and uniqueid from a channel to an expected CDR */ +#define COPY_IDS(channel_var, expected_record) do { \ + ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \ + ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \ + } while (0) + +/*! \brief Create a \ref test_cdr_chan_tech for Alice, and set the expected + * CDR records' linkedid and uniqueid. */ +#define CREATE_ALICE_CHANNEL(channel_var, caller_id, expected_record) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice"); \ + ast_channel_set_caller((channel_var), (caller_id), NULL); \ + ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \ + ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \ + } while (0) + +/*! \brief Create a \ref test_cdr_chan_tech for Bob, and set the expected + * CDR records' linkedid and uniqueid. */ +#define CREATE_BOB_CHANNEL(channel_var, caller_id, expected_record) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob"); \ + ast_channel_set_caller((channel_var), (caller_id), NULL); \ + ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \ + ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \ + } while (0) + +/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected + * CDR records' linkedid and uniqueid. */ +#define CREATE_CHARLIE_CHANNEL(channel_var, caller_id, expected_record) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "300", "Charlie", "300", "300", "default", NULL, 0, CHANNEL_TECH_NAME "/Charlie"); \ + ast_channel_set_caller((channel_var), (caller_id), NULL); \ + ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \ + ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \ + } while (0) + +/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected + * CDR records' linkedid and uniqueid. */ +#define CREATE_DAVID_CHANNEL(channel_var, caller_id, expected_record) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David"); \ + ast_channel_set_caller((channel_var), (caller_id), NULL); \ + ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \ + ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \ + } while (0) + +/*! \brief Emulate a channel entering into an application */ +#define EMULATE_APP_DATA(channel, priority, application, data) do { \ + if ((priority) > 0) { \ + ast_channel_priority_set((channel), (priority)); \ + } \ + ast_channel_appl_set((channel), (application)); \ + ast_channel_data_set((channel), (data)); \ + ast_channel_publish_snapshot((channel)); \ + } while (0) + +/*! \brief Hang up a test channel safely */ +#define HANGUP_CHANNEL(channel, cause) do { \ + ast_channel_hangupcause_set((channel), (cause)); \ + if (!ast_hangup((channel))) { \ + channel = NULL; \ + } } while (0) + +static enum ast_test_result_state verify_mock_cdr_record(struct ast_test *test, struct ast_cdr *expected, int record) +{ + struct ast_cdr *actual = NULL; + struct test_cdr_entry *cdr_wrapper; + int count = 0; + struct timeval wait_now = ast_tvnow(); + struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 5, .tv_nsec = wait_now.tv_usec * 1000 }; + enum ast_test_result_state res = AST_TEST_PASS; + + while (count < record) { + AST_LIST_LOCK(&actual_cdr_entries); + if (global_mock_cdr_count < record) { + ast_cond_timedwait(&mock_cdr_cond, &actual_cdr_entries.lock, &wait_time); + } + cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list); + AST_LIST_UNLOCK(&actual_cdr_entries); + + if (!cdr_wrapper) { + ast_test_status_update(test, "Unable to find actual CDR record at %d\n", count); + return AST_TEST_FAIL; + } + actual = cdr_wrapper->cdr; + + if (!expected && actual) { + ast_test_status_update(test, "CDRs recorded where no record expected\n"); + return AST_TEST_FAIL; + } + + VERIFY_STRING_FIELD(accountcode, actual, expected); + VERIFY_NUMERIC_FIELD(amaflags, actual, expected); + VERIFY_STRING_FIELD(channel, actual, expected); + VERIFY_STRING_FIELD(clid, actual, expected); + VERIFY_STRING_FIELD(dcontext, actual, expected); + VERIFY_NUMERIC_FIELD(disposition, actual, expected); + VERIFY_STRING_FIELD(dst, actual, expected); + VERIFY_STRING_FIELD(dstchannel, actual, expected); + VERIFY_STRING_FIELD(lastapp, actual, expected); + VERIFY_STRING_FIELD(lastdata, actual, expected); + VERIFY_STRING_FIELD(linkedid, actual, expected); + VERIFY_STRING_FIELD(peeraccount, actual, expected); + VERIFY_STRING_FIELD(src, actual, expected); + VERIFY_STRING_FIELD(uniqueid, actual, expected); + VERIFY_STRING_FIELD(userfield, actual, expected); + VERIFY_TIME_VALUE(start, actual); + VERIFY_TIME_VALUE(end, actual); + /* Note: there's no way we can really calculate a duration or + * billsec - the unit tests are too short. However, if billsec is + * non-zero in the expected, then make sure we have an answer time + */ + if (expected->billsec) { + VERIFY_TIME_VALUE(answer, actual); + } + ast_test_debug(test, "Finished expected record %s, %s\n", + expected->channel, S_OR(expected->dstchannel, "")); + expected = expected->next; + ++count; + } + return res; +} + +static void safe_channel_release(struct ast_channel *chan) +{ + if (!chan) { + return; + } + ast_channel_release(chan); +} + +AST_TEST_DEFINE(test_cdr_channel_creation) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_NOANSWER, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test that a CDR is created when a channel is created"; + info->description = + "Test that a CDR is created when a channel is created"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan, (&caller), &expected); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_unanswered_inbound_call) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Wait", + .lastdata = "1", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_NOANSWER, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test inbound unanswered calls"; + info->description = + "Test the properties of a CDR for a call that is\n" + "inbound to Asterisk, executes some dialplan, but\n" + "is never answered.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan, &caller, &expected); + + EMULATE_APP_DATA(chan, 1, "Wait", "1"); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_unanswered_outbound_call) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = { + .id.name.str = "", + .id.name.valid = 1, + .id.number.str = "", + .id.number.valid = 1, }; + struct ast_cdr expected = { + .clid = "\"\" <>", + .dst = "s", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "AppDial", + .lastdata = "(Outgoing Line)", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_NOANSWER, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test outbound unanswered calls"; + info->description = + "Test the properties of a CDR for a call that is\n" + "outbound to Asterisk but is never answered.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan, &caller, &expected); + + ast_channel_exten_set(chan, "s"); + ast_channel_context_set(chan, "default"); + ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED); + EMULATE_APP_DATA(chan, 0, "AppDial", "(Outgoing Line)"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_single_party) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = "", + .lastapp = "VoiceMailMain", + .lastdata = "1", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party"; + info->description = + "Test the properties of a CDR for a call that is\n" + "answered, but only involves a single channel\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan, &caller, &expected); + + EMULATE_APP_DATA(chan, 1, "Answer", ""); + ast_setstate(chan, AST_STATE_UP); + EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1"); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_single_bridge) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party entering/leaving a bridge"; + info->description = + "Test the properties of a CDR for a call that is\n" + "answered, enters a bridge, and leaves it.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan, &caller, &expected); + + EMULATE_APP_DATA(chan, 1, "Answer", ""); + ast_setstate(chan, AST_STATE_UP); + EMULATE_APP_DATA(chan, 2, "Bridge", ""); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_impart(bridge, chan, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_single_bridge_continue) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge_one, NULL, ao2_cleanup); + RAII_VAR(struct ast_bridge *, bridge_two, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected_two = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Wait", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + }; + struct ast_cdr expected_one = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .next = &expected_two, + }; + + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party entering/leaving a bridge"; + info->description = + "Test the properties of a CDR for a call that is\n" + "answered, enters a bridge, and leaves it.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan, &caller, &expected_one); + COPY_IDS(chan, &expected_two); + + EMULATE_APP_DATA(chan, 1, "Answer", ""); + ast_setstate(chan, AST_STATE_UP); + EMULATE_APP_DATA(chan, 2, "Bridge", ""); + + bridge_one = ast_bridge_basic_new(); + ast_test_validate(test, bridge_one != NULL); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_impart(bridge_one, chan, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan); + + EMULATE_APP_DATA(chan, 3, "Wait", ""); + + /* And then it hangs up */ + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected_one, 2); + + return result; +} + +AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_a) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller caller_alice = ALICE_CALLERID; + struct ast_party_caller caller_bob = BOB_CALLERID; + struct ast_cdr bob_expected = { + .clid = "\"Bob\" <200>", + .src = "200", + .dst = "200", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "200", + }; + struct ast_cdr alice_expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + .next = &bob_expected, + }; + + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party entering/leaving a bridge"; + info->description = + "Test the properties of a CDR for a call that is\n" + "answered, enters a bridge, and leaves it. In this scenario, the\n" + "Party A should answer the bridge first.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected); + + CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected); + ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid)); + + EMULATE_APP_DATA(chan_alice, 1, "Answer", ""); + ast_setstate(chan_alice, AST_STATE_UP); + EMULATE_APP_DATA(chan_alice, 2, "Bridge", ""); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + EMULATE_APP_DATA(chan_bob, 1, "Answer", ""); + ast_setstate(chan_bob, AST_STATE_UP); + EMULATE_APP_DATA(chan_bob, 2, "Bridge", ""); + + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan_alice); + ast_bridge_depart(chan_bob); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &alice_expected, 2); + + return result; +} + +AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_b) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller caller_alice = ALICE_CALLERID; + struct ast_party_caller caller_bob = BOB_CALLERID; + struct ast_cdr bob_expected = { + .clid = "\"Bob\" <200>", + .src = "200", + .dst = "200", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "200", + }; + struct ast_cdr alice_expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + .next = &bob_expected, + }; + + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party entering/leaving a bridge"; + info->description = + "Test the properties of a CDR for a call that is\n" + "answered, enters a bridge, and leaves it. In this scenario, the\n" + "Party B should answer the bridge first.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected); + + CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected); + ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid)); + + EMULATE_APP_DATA(chan_alice, 1, "Answer", ""); + ast_setstate(chan_alice, AST_STATE_UP); + EMULATE_APP_DATA(chan_alice, 2, "Bridge", ""); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + EMULATE_APP_DATA(chan_bob, 1, "Answer", ""); + ast_setstate(chan_bob, AST_STATE_UP); + EMULATE_APP_DATA(chan_bob, 2, "Bridge", ""); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan_alice); + ast_bridge_depart(chan_bob); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &alice_expected, 2); + + return result; +} + +AST_TEST_DEFINE(test_cdr_single_multiparty_bridge) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller caller_alice = ALICE_CALLERID; + struct ast_party_caller caller_bob = BOB_CALLERID; + struct ast_party_caller caller_charlie = CHARLIE_CALLERID; + struct ast_cdr charlie_expected = { + .clid = "\"Charlie\" <300>", + .src = "300", + .dst = "300", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Charlie", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "300", + }; + struct ast_cdr bob_expected = { + .clid = "\"Bob\" <200>", + .src = "200", + .dst = "200", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Bob", + .dstchannel = CHANNEL_TECH_NAME "/Charlie", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "200", + .peeraccount = "300", + .next = &charlie_expected, + }; + struct ast_cdr alice_expected_two = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Charlie", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "300", + .next = &bob_expected, + }; + struct ast_cdr alice_expected_one = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Bridge", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + .next = &alice_expected_two, + }; + + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party entering/leaving a multi-party bridge"; + info->description = + "Test the properties of a CDR for a call that is\n" + "answered, enters a bridge, and leaves it. A total of three\n" + "parties perform this action.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected_one); + COPY_IDS(chan_alice, &alice_expected_two); + CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected); + ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid)); + CREATE_CHARLIE_CHANNEL(chan_charlie, &caller_charlie, &charlie_expected); + ast_copy_string(charlie_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected.linkedid)); + + EMULATE_APP_DATA(chan_alice, 1, "Answer", ""); + ast_setstate(chan_alice, AST_STATE_UP); + EMULATE_APP_DATA(chan_alice, 2, "Bridge", ""); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + + EMULATE_APP_DATA(chan_bob, 1, "Answer", ""); + ast_setstate(chan_bob, AST_STATE_UP); + EMULATE_APP_DATA(chan_bob, 2, "Bridge", ""); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + EMULATE_APP_DATA(chan_charlie, 1, "Answer", ""); + ast_setstate(chan_charlie, AST_STATE_UP); + EMULATE_APP_DATA(chan_charlie, 2, "Bridge", ""); + ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan_alice); + ast_bridge_depart(chan_bob); + ast_bridge_depart(chan_charlie); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &alice_expected_one, 4); + + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_unanswered) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_NOANSWER, + .accountcode = "100", + .peeraccount = "200", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CDRs for a dial that isn't answered"; + info->description = + "Test the properties of a CDR for a channel that\n" + "performs a dial operation that isn't answered\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", "CDRTestChannel/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + + +AST_TEST_DEFINE(test_cdr_dial_busy) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_BUSY, + .accountcode = "100", + .peeraccount = "200", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CDRs for a dial that results in a busy"; + info->description = + "Test the properties of a CDR for a channel that\n" + "performs a dial operation to an endpoint that's busy\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "BUSY"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_congestion) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_CONGESTION, + .accountcode = "100", + .peeraccount = "200", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CDRs for a dial that results in congestion"; + info->description = + "Test the properties of a CDR for a channel that\n" + "performs a dial operation to an endpoint that's congested\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, congestion_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CONGESTION"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_unavailable) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_FAILED, + .accountcode = "100", + .peeraccount = "200", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CDRs for a dial that results in unavailable"; + info->description = + "Test the properties of a CDR for a channel that\n" + "performs a dial operation to an endpoint that's unavailable\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CHANUNAVAIL"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_caller_cancel) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_NOANSWER, + .accountcode = "100", + .peeraccount = "200", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CDRs for a dial where the caller cancels"; + info->description = + "Test the properties of a CDR for a channel that\n" + "performs a dial operation to an endpoint but then decides\n" + "to hang up, cancelling the dial\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CANCEL"); + + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_parallel_failed) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr bob_expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_NOANSWER, + .accountcode = "100", + .peeraccount = "200", + }; + struct ast_cdr charlie_expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Charlie", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_BUSY, + .accountcode = "100", + .peeraccount = "300", + }; + struct ast_cdr david_expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/David", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_CONGESTION, + .accountcode = "100", + .peeraccount = "400", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + struct ast_cdr *expected = &bob_expected; + bob_expected.next = &charlie_expected; + charlie_expected.next = &david_expected; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test a parallel dial where all channels fail to answer"; + info->description = + "This tests dialing three parties: Bob, Charlie, David. Charlie\n" + "returns BUSY; David returns CONGESTION; Bob fails to answer and\n" + "Alice hangs up. Three records are created for Alice as a result.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, congestion_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &bob_expected); + COPY_IDS(chan_caller, &charlie_expected); + COPY_IDS(chan_caller, &david_expected); + + /* Channel enters Dial app */ + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David"); + + /* Outbound channels are created */ + chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)"); + + chan_charlie = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "300", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Charlie"); + ast_set_flag(ast_channel_flags(chan_charlie), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_charlie, 0, "AppDial", "(Outgoing Line)"); + + chan_david = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "400", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/David"); + ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)"); + + /* Dial starts */ + ast_channel_publish_dial(chan_caller, chan_bob, "Bob", NULL); + ast_channel_publish_dial(chan_caller, chan_charlie, "Charlie", NULL); + ast_channel_publish_dial(chan_caller, chan_david, "David", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + + /* Charlie is busy */ + ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY"); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY); + + /* David is congested */ + ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION"); + HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION); + + /* Bob is canceled */ + ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL); + + /* Alice hangs up */ + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, expected, 3); + + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_answer_no_bridge) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr bob_expected_one = { + .clid = "\"\" <>", + .src = "", + .dst = "s", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Wait", + .lastdata = "1", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "200", + }; + struct ast_cdr alice_expected_two = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Wait", + .lastdata = "1", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .next = &bob_expected_one, + }; + struct ast_cdr alice_expected_one = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + .next = &alice_expected_two, + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and not going into a bridge."; + info->description = + "This is a weird one, but theoretically possible. You can perform\n" + "a dial, then bounce both channels to different priorities and\n" + "never have them enter a bridge together. Ew. This makes sure that\n" + "when we answer, we get a CDR, it gets ended at that point, and\n" + "that it gets finalized appropriately. We should get three CDRs in\n" + "the end - one for the dial, and one for each CDR as they continued\n" + "on.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, debug_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &alice_expected_one); + COPY_IDS(chan_caller, &alice_expected_two); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + COPY_IDS(chan_callee, &bob_expected_one); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + ast_channel_state_set(chan_caller, AST_STATE_UP); + ast_clear_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + ast_channel_state_set(chan_callee, AST_STATE_UP); + + EMULATE_APP_DATA(chan_caller, 2, "Wait", "1"); + EMULATE_APP_DATA(chan_callee, 1, "Wait", "1"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &alice_expected_one, 3); + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_a) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + }; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and going into a 2-party bridge"; + info->description = + "The most 'basic' of scenarios\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, debug_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + ast_channel_state_set(chan_caller, AST_STATE_UP); + ast_channel_state_set(chan_callee, AST_STATE_UP); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0); + ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_bridge_depart(chan_caller); + ast_bridge_depart(chan_callee); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_b) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + }; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and going into a 2-party bridge"; + info->description = + "The most 'basic' of scenarios\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, debug_cdr_config); + + CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected); + + EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL); + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + ast_channel_state_set(chan_caller, AST_STATE_UP); + ast_channel_state_set(chan_callee, AST_STATE_UP); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_depart(chan_caller); + ast_bridge_depart(chan_callee); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &expected, 1); + return result; +} + +AST_TEST_DEFINE(test_cdr_dial_answer_multiparty) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + struct ast_party_caller alice_caller = ALICE_CALLERID; + struct ast_party_caller charlie_caller = CHARLIE_CALLERID; + struct ast_cdr charlie_expected_two = { + .clid = "\"Charlie\" <300>", + .src = "300", + .dst = "300", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Charlie", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/David", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "300", + .peeraccount = "200", + }; + struct ast_cdr charlie_expected_one = { + .clid = "\"Charlie\" <300>", + .src = "300", + .dst = "300", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Charlie", + .dstchannel = CHANNEL_TECH_NAME "/David", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/David", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "300", + .peeraccount = "400", + .next = &charlie_expected_two, + }; + struct ast_cdr alice_expected_three = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/David", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "400", + .next = &charlie_expected_one, + }; + struct ast_cdr alice_expected_two = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Charlie", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "300", + .next = &alice_expected_three, + }; + struct ast_cdr alice_expected_one = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .dstchannel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Dial", + .lastdata = CHANNEL_TECH_NAME "/Bob", + .amaflags = AST_AMA_DOCUMENTATION, + .billsec = 1, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .peeraccount = "200", + .next = &alice_expected_two, + }; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and going into a multi-party bridge"; + info->description = + "A little tricky to get to do, but possible with some redirects.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, debug_cdr_config); + + CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected_one); + COPY_IDS(chan_alice, &alice_expected_two); + COPY_IDS(chan_alice, &alice_expected_three); + + EMULATE_APP_DATA(chan_alice, 1, "Dial", CHANNEL_TECH_NAME "/Bob"); + + chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob"); + ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)"); + + CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller, &charlie_expected_one); + EMULATE_APP_DATA(chan_charlie, 1, "Dial", CHANNEL_TECH_NAME "/David"); + ast_copy_string(charlie_expected_one.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_one.uniqueid)); + ast_copy_string(charlie_expected_one.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_one.linkedid)); + ast_copy_string(charlie_expected_two.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_two.uniqueid)); + ast_copy_string(charlie_expected_two.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_two.linkedid)); + + chan_david = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David"); + ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING); + EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)"); + + ast_channel_publish_dial(chan_alice, chan_bob, "Bob", NULL); + ast_channel_state_set(chan_alice, AST_STATE_RINGING); + ast_channel_publish_dial(chan_charlie, chan_david, "David", NULL); + ast_channel_state_set(chan_charlie, AST_STATE_RINGING); + ast_channel_publish_dial(chan_alice, chan_bob, NULL, "ANSWER"); + ast_channel_publish_dial(chan_charlie, chan_david, NULL, "ANSWER"); + + ast_channel_state_set(chan_alice, AST_STATE_UP); + ast_channel_state_set(chan_bob, AST_STATE_UP); + ast_channel_state_set(chan_charlie, AST_STATE_UP); + ast_channel_state_set(chan_david, AST_STATE_UP); + + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0)); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_david, NULL, NULL, 0)); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0)); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0)); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_test_validate(test, 0 == ast_bridge_depart(chan_alice)); + ast_test_validate(test, 0 == ast_bridge_depart(chan_bob)); + ast_test_validate(test, 0 == ast_bridge_depart(chan_charlie)); + ast_test_validate(test, 0 == ast_bridge_depart(chan_david)); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &alice_expected_one, 5); + + return result; +} + +AST_TEST_DEFINE(test_cdr_park) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller bob_caller = BOB_CALLERID; + struct ast_party_caller alice_caller = ALICE_CALLERID; + struct ast_cdr bob_expected = { + .clid = "\"Bob\" <200>", + .src = "200", + .dst = "200", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Bob", + .lastapp = "Park", + .lastdata = "701", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "200", + }; + struct ast_cdr alice_expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Park", + .lastdata = "700", + .billsec = 1, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + .next = &bob_expected, + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test cdrs for a single party entering Park"; + info->description = + "Test the properties of a CDR for calls that are\n" + "answered, enters Park, and leaves it.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + SWAP_CONFIG(config, debug_cdr_config); + CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected); + CREATE_BOB_CHANNEL(chan_bob, &bob_caller, &bob_expected); + + EMULATE_APP_DATA(chan_alice, 1, "Park", "700"); + ast_setstate(chan_alice, AST_STATE_UP); + EMULATE_APP_DATA(chan_bob, 1, "Park", "701"); + ast_setstate(chan_bob, AST_STATE_UP); + + bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING, + AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM + | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED); + ast_test_validate(test, bridge != NULL); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_bridge_depart(chan_alice); + ast_bridge_depart(chan_bob); + + /* And then it hangs up */ + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL); + + result = verify_mock_cdr_record(test, &alice_expected, 2); + + return result; +} + + +AST_TEST_DEFINE(test_cdr_fields) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + char varbuffer[128]; + int int_buffer; + double db_buffer; + struct timespec to_sleep = {2, 0}; + struct ast_flags fork_options = { 0, }; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr original = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Wait", + .lastdata = "10", + .billsec = 0, + .amaflags = AST_AMA_OMIT, + .disposition = AST_CDR_FAILED, + .accountcode = "XXX", + .userfield = "yackity", + }; + struct ast_cdr fork_expected_one = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Wait", + .lastdata = "10", + .billsec = 0, + .amaflags = AST_AMA_OMIT, + .disposition = AST_CDR_FAILED, + .accountcode = "XXX", + .userfield = "yackity", + }; + struct ast_cdr fork_expected_two = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .lastapp = "Answer", + .billsec = 0, + .amaflags = AST_AMA_OMIT, + .disposition = AST_CDR_ANSWERED, + .accountcode = "ZZZ", + .userfield = "schmackity", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + struct ast_cdr *expected = &original; + original.next = &fork_expected_one; + fork_expected_one.next = &fork_expected_two; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test field access CDRs"; + info->description = + "This tests setting/retrieving data on CDR records.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan, &caller, &original); + ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid)); + ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid)); + ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid)); + ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid)); + + /* Channel enters Wait app */ + ast_channel_appl_set(chan, "Wait"); + ast_channel_data_set(chan, "10"); + ast_channel_priority_set(chan, 1); + ast_channel_publish_snapshot(chan); + + /* Set properties on the channel that propagate to the CDR */ + ast_channel_amaflags_set(chan, AST_AMA_OMIT); + ast_channel_accountcode_set(chan, "XXX"); + + /* Wait one second so we get a duration. */ + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + ast_cdr_setuserfield(ast_channel_name(chan), "foobar"); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0); + + /* Verify that we can't set read-only fields or other fields directly */ + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "clid", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "src", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dst", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dcontext", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "channel", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dstchannel", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastapp", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastdata", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "start", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "answer", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "end", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "duration", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "billsec", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "disposition", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "amaflags", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "accountcode", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "uniqueid", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "linkedid", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "userfield", "junk") != 0); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "sequence", "junk") != 0); + + /* Verify the values */ + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "userfield", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "foobar") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "record_1") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "amaflags", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%d", &int_buffer); + ast_test_validate(test, int_buffer == AST_AMA_OMIT); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "accountcode", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "XXX") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "clid", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "\"Alice\" <100>") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "src", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "100") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dst", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "100") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dcontext", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "default") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "channel", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, CHANNEL_TECH_NAME "/Alice") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dstchannel", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastapp", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "Wait") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastdata", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "10") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%lf", &db_buffer); + ast_test_validate(test, fabs(db_buffer) > 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%lf", &db_buffer); + ast_test_validate(test, fabs(db_buffer) < EPSILON); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "end", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%lf", &db_buffer); + ast_test_validate(test, fabs(db_buffer) < EPSILON); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "duration", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%lf", &db_buffer); + ast_test_validate(test, fabs(db_buffer) > 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "billsec", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%lf", &db_buffer); + ast_test_validate(test, fabs(db_buffer) < EPSILON); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "disposition", varbuffer, sizeof(varbuffer)) == 0); + sscanf(varbuffer, "%d", &int_buffer); + ast_test_validate(test, int_buffer == AST_CDR_NULL); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "uniqueid", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, ast_channel_uniqueid(chan)) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "linkedid", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, ast_channel_linkedid(chan)) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "sequence", varbuffer, sizeof(varbuffer)) == 0); + + /* Fork the CDR, and check that we change the properties on both CDRs. */ + ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + + /* Change some properties */ + ast_cdr_setuserfield(ast_channel_name(chan), "yackity"); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1b") == 0); + + /* Fork the CDR again, finalizing all current CDRs */ + ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS | AST_CDR_FLAG_FINALIZE); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + + /* Channel enters Answer app */ + ast_channel_appl_set(chan, "Answer"); + ast_channel_data_set(chan, ""); + ast_channel_priority_set(chan, 1); + ast_channel_publish_snapshot(chan); + ast_setstate(chan, AST_STATE_UP); + + /* Set properties on the last record */ + ast_channel_accountcode_set(chan, "ZZZ"); + ast_cdr_setuserfield(ast_channel_name(chan), "schmackity"); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0); + + /* Hang up and verify */ + ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL); + if (!ast_hangup(chan)) { + chan = NULL; + } + result = verify_mock_cdr_record(test, expected, 3); + + return result; +} + +AST_TEST_DEFINE(test_cdr_no_reset_cdr) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + struct ast_flags fork_options = { 0, }; + struct timespec to_sleep = {1, 0}; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr expected = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .billsec = 0, + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_FAILED, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test field access CDRs"; + info->description = + "This tests setting/retrieving data on CDR records.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, unanswered_cdr_config); + + CREATE_ALICE_CHANNEL(chan, &caller, &expected); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + /* Disable the CDR */ + ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0); + + /* Fork the CDR. This should be enabled */ + ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + + /* Disable and enable the forked CDR */ + ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0); + ast_test_validate(test, ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0); + + /* Fork and finalize again. This CDR should be propagated */ + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + + /* Disable all future CDRs */ + ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL) == 0); + + /* Fork a few more */ + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + + ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL); + if (!ast_hangup(chan)) { + chan = NULL; + } + result = verify_mock_cdr_record(test, &expected, 1); + + return result; +} + +AST_TEST_DEFINE(test_cdr_fork_cdr) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL), + ao2_cleanup); + char varbuffer[128]; + char fork_varbuffer[128]; + char answer_time[128]; + char fork_answer_time[128]; + char start_time[128]; + char fork_start_time[128]; + struct ast_flags fork_options = { 0, }; + struct timespec to_sleep = {1, 10000}; + + struct ast_party_caller caller = ALICE_CALLERID; + struct ast_cdr original = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + }; + struct ast_cdr fork_expected_one = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + }; + struct ast_cdr fork_expected_two = { + .clid = "\"Alice\" <100>", + .src = "100", + .dst = "100", + .dcontext = "default", + .channel = CHANNEL_TECH_NAME "/Alice", + .amaflags = AST_AMA_DOCUMENTATION, + .disposition = AST_CDR_ANSWERED, + .accountcode = "100", + }; + enum ast_test_result_state result = AST_TEST_NOT_RUN; + struct ast_cdr *expected = &original; + original.next = &fork_expected_one; + fork_expected_one.next = &fork_expected_two; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test field access CDRs"; + info->description = + "This tests setting/retrieving data on CDR records.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + SWAP_CONFIG(config, debug_cdr_config); + + CREATE_ALICE_CHANNEL(chan, &caller, &original); + ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid)); + ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid)); + ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid)); + ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid)); + + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + + /* Test blowing away variables */ + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "record_1") == 0); + ast_copy_string(varbuffer, "", sizeof(varbuffer)); + + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "record_1") != 0); + + /* Test finalizing previous CDRs */ + ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + + /* Test keep variables; setting a new answer time */ + ast_setstate(chan, AST_STATE_UP); + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); + ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0); + ast_test_validate(test, strcmp(varbuffer, "record_2") == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", answer_time, sizeof(answer_time)) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", start_time, sizeof(start_time)) == 0); + + ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE); + ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS); + ast_set_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0); + ast_test_validate(test, strcmp(fork_varbuffer, varbuffer) == 0); + ast_test_validate(test, strcmp(fork_start_time, start_time) == 0); + ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0); + + ast_clear_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER); + ast_set_flag(&fork_options, AST_CDR_FLAG_RESET); + ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0); + ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0); + ast_test_validate(test, strcmp(fork_start_time, start_time) != 0); + ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0); + + ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL); + if (!ast_hangup(chan)) { + chan = NULL; + } + result = verify_mock_cdr_record(test, expected, 3); + + return result; +} + +/*! + * \internal \brief Callback function called before each test executes + */ +static int test_cdr_init_cb(struct ast_test_info *info, struct ast_test *test) +{ + /* Back up the real config */ + saved_config = ast_cdr_get_config(); + clear_mock_cdr_backend(); + return 0; +} + +/*! + * \internal \brief Callback function called after each test executes + */ +static int test_cdr_cleanup_cb(struct ast_test_info *info, struct ast_test *test) +{ + /* Restore the real config */ + ast_cdr_set_config(saved_config); + ao2_cleanup(saved_config); + saved_config = NULL; + clear_mock_cdr_backend(); + + return 0; +} + + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(test_cdr_channel_creation); + AST_TEST_UNREGISTER(test_cdr_unanswered_inbound_call); + AST_TEST_UNREGISTER(test_cdr_unanswered_outbound_call); + AST_TEST_UNREGISTER(test_cdr_single_party); + AST_TEST_UNREGISTER(test_cdr_single_bridge); + AST_TEST_UNREGISTER(test_cdr_single_bridge_continue); + AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_a); + AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_b); + AST_TEST_UNREGISTER(test_cdr_single_multiparty_bridge); + + AST_TEST_UNREGISTER(test_cdr_dial_unanswered); + AST_TEST_UNREGISTER(test_cdr_dial_congestion); + AST_TEST_UNREGISTER(test_cdr_dial_busy); + AST_TEST_UNREGISTER(test_cdr_dial_unavailable); + AST_TEST_UNREGISTER(test_cdr_dial_caller_cancel); + AST_TEST_UNREGISTER(test_cdr_dial_parallel_failed); + AST_TEST_UNREGISTER(test_cdr_dial_answer_no_bridge); + AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_a); + AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_b); + AST_TEST_UNREGISTER(test_cdr_dial_answer_multiparty); + + AST_TEST_UNREGISTER(test_cdr_park); + + AST_TEST_UNREGISTER(test_cdr_fields); + AST_TEST_UNREGISTER(test_cdr_no_reset_cdr); + AST_TEST_UNREGISTER(test_cdr_fork_cdr); + + ast_cdr_unregister(MOCK_CDR_BACKEND); + ast_channel_unregister(&test_cdr_chan_tech); + clear_mock_cdr_backend(); + + return 0; +} + +static int load_module(void) +{ + ast_cond_init(&mock_cdr_cond, NULL); + + AST_TEST_REGISTER(test_cdr_channel_creation); + AST_TEST_REGISTER(test_cdr_unanswered_inbound_call); + AST_TEST_REGISTER(test_cdr_unanswered_outbound_call); + + AST_TEST_REGISTER(test_cdr_single_party); + AST_TEST_REGISTER(test_cdr_single_bridge); + AST_TEST_REGISTER(test_cdr_single_bridge_continue); + AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_a); + AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_b); + AST_TEST_REGISTER(test_cdr_single_multiparty_bridge); + + AST_TEST_REGISTER(test_cdr_dial_unanswered); + AST_TEST_REGISTER(test_cdr_dial_congestion); + AST_TEST_REGISTER(test_cdr_dial_busy); + AST_TEST_REGISTER(test_cdr_dial_unavailable); + AST_TEST_REGISTER(test_cdr_dial_caller_cancel); + AST_TEST_REGISTER(test_cdr_dial_parallel_failed); + AST_TEST_REGISTER(test_cdr_dial_answer_no_bridge); + AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_a); + AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_b); + AST_TEST_REGISTER(test_cdr_dial_answer_multiparty); + + AST_TEST_REGISTER(test_cdr_park); + + AST_TEST_REGISTER(test_cdr_fields); + AST_TEST_REGISTER(test_cdr_no_reset_cdr); + AST_TEST_REGISTER(test_cdr_fork_cdr); + + ast_test_register_init(TEST_CATEGORY, test_cdr_init_cb); + ast_test_register_cleanup(TEST_CATEGORY, test_cdr_cleanup_cb); + + ast_channel_register(&test_cdr_chan_tech); + ast_cdr_register(MOCK_CDR_BACKEND, "Mock CDR backend", mock_cdr_backend_cb); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "CDR unit tests");