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 +CDR (Call Detail Records) ------------------ - * 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. + * 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). -chan_local ------------------- - * The /b option is removed. + * 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_local moved into the system core and is no longer a loadable module. - -chan_mobile ------------------- - * Added general support for busy detection. - - * Added ECAM command support for Sony Ericsson phones. - -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,55 +296,6 @@ 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. - -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. - - * 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 ------------------- - * 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. - - * For DTMF blind and attended transfers, the channel variable TRANSFER_CONTEXT - must be on the channel initiating the transfer to have any effect. - - * 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. - - * 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. - - * The channel variable BRIDGEPVTCALLID is only valid for two party bridges - and will contain a value if the BRIDGEPEER's channel driver supports it. - - * 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. - Realtime ------------------ * Dynamic realtime tables for SIP Users can now include a 'path' field. This @@ -294,6 +305,88 @@ Realtime * 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. + + +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_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. + + * chan_local moved into the system core and is no longer a loadable module. + +chan_mobile +------------------ + * Added general support for busy detection. + + * Added ECAM command support for Sony Ericsson phones. + +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. + + +Functions +------------------ + +JITTERBUFFER +------------------ + * 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. + +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 ------------------ * ICE/STUN/TURN support in res_rtp_asterisk has been made optional. To enable 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; + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(forkcdr_exec_options, &flags, NULL, args.options); } - argcopy = ast_strdupa(data); - - AST_STANDARD_APP_ARGS(arglist, argcopy); - - 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(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; - } - } else { - start = 0; - connect = 0; - end = 0; + 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 { + 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) -{ - char *args; - struct ast_flags flags = { 0 }; - - if (!ast_strlen_zero(data)) { - args = ast_strdupa(data); - ast_app_parse_options(resetcdr_opts, &flags, NULL, args); - } - - ast_cdr_reset(ast_channel_cdr(chan), &flags); - - return 0; -} - /*! * \ingroup applications */ static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) { + ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n"); + + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n"); + return 0; + } /* 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");