diff --git a/CHANGES b/CHANGES index f3d420bc8f6c72499a62bc69b842555829863bc5..b81c2da287b2ee51f6e652454bd77bbfd7f4f013 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,94 @@ --- Functionality changes from Asterisk 11 to Asterisk 12 -------------------- ------------------------------------------------------------------------------ +Applications +------------------ + +AgentMonitorOutgoing +------------------ + * The 'c' option has been removed. It is not possible to modify the name of a + channel involved in a CDR. + +ForkCDR +------------------ + * ForkCDR no longer automatically resets the forked CDR. See the 'r' option + for more information. + + * Variables are no longer purged from the original CDR. See the 'v' option for + more information. + + * The 'A' option has been removed. The Answer time on a CDR is never updated + once set. + + * The 'd' option has been removed. The disposition on a CDR is a function of + the state of the channel and cannot be altered. + + * The 'D' option has been removed. Who the Party B is on a CDR is a function + of the state of the respective channels, and cannot be altered. + + * The 'r' option has been changed. Previously, ForkCDR always reset the CDR + such that the start time and, if applicable, the answer time was updated. + Now, by default, ForkCDR simply forks the CDR, maintaining any times. The + 'r' option now triggers the Reset, setting the start time (and answer time + if applicable) to the current time. + + * The 's' option has been removed. A variable can be set on the original CDR + if desired using the CDR function, and removed from a forked CDR using the + same function. + + * The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no + longer applies in the CDR engine. + + * The 'v' option now prevents the copy of the variables from the original CDR + to the forked CDR. Previously the variables were always copied but were + removed from the original. Removing variables from a CDR can have unintended + side effects - this option allows the user to prevent propagation of + variables from the original to the forked without modifying the original. + +MeetMe +------------------- +* Added the 'n' option to MeetMe to prevent application of the DENOISE function + to a channel joining a conference. Some channel drivers that vary the number + of audio samples in a voice frame will experience significant quality problems + if a denoiser is attached to the channel; this option gives them the ability + to remove the denoiser without having to unload func_speex. + +NoCDR +------------------ + * The NoCDR application is deprecated. Please use the CDR_PROP function to + disable CDRs. + * While the NoCDR application will prevent CDRs for a channel from being + propagated to registered CDR backends, it will not prevent that data from + being collected. Hence, a subsequent call to ResetCDR or the CDR_PROP + function that enables CDRs on a channel will restore those records that have + not yet been finalized. + +Queue +------------------- + * Add queue available hint. exten => 8501,hint,Queue:markq_avail + Note: the suffix '_avail' after the queuename. + Reports 'InUse' for no logged in agents or no free agents. + Reports 'Idle' when an agent is free. + +ResetCDR +------------------ + * The 'e' option has been deprecated. Use the CDR_PROP function to re-enable + CDRs when they were previously disabled on a channel. + * The 'w' and 'a' options have been removed. Dispatching CDRs to registered + backends occurs on an as-needed basis in order to preserve linkedid + propagation and other needed behavior. + +SetAMAFlags +------------------ + * This application is deprecated in favor of the CHANNEL function. + + +Core +------------------ + * Redirecting reasons can now be set to arbitrary strings. This means + that the REDIRECTING dialplan function can be used to set the redirecting + reason to any string. It also allows for custom strings to be read as the + redirecting reason from SIP Diversion headers. AMI (Asterisk Manager Interface) ------------------ @@ -72,6 +160,9 @@ AMI (Asterisk Manager Interface) event, the various ChanVariable fields will contain a suffix that specifies which channel they correspond to. +* The NewPeerAccount AMI event is no longer raised. The NewAccountCode AMI + event always conveys the AMI event for a particular channel. + * All "Reload" events have been consolidated into a single event type. This event will always contain a Module field specifying the name of the module and a Status field denoting the result of the reload. All modules now issue @@ -118,33 +209,21 @@ AGI (Asterisk Gateway Interface) * The manager event AsyncAGI has been split into AsyncAGIStart, AsyncAGIExec, and AsyncAGIEnd. -Channel Drivers ------------------- - * When a channel driver is configured to enable jiterbuffers, they are now - applied unconditionally when a channel joins a bridge. If a jitterbuffer - is already set for that channel when it enters, such as by the JITTERBUFFER - function, then the existing jitterbuffer will be used and the one set by - the channel driver will not be applied. - -chan_local ------------------- - * The /b option is removed. - - * chan_local moved into the system core and is no longer a loadable module. - -chan_mobile +CDR (Call Detail Records) ------------------ - * Added general support for busy detection. + * Significant changes have been made to the behavior of CDRs. For a full + definition of CDR behavior in Asterisk 12, please read the specification + on the Asterisk wiki (wiki.asterisk.org). - * Added ECAM command support for Sony Ericsson phones. + * CDRs will now be created between all participants in a bridge. For each + pair of channels in a bridge, a CDR is created to represent the path of + communication between those two endpoints. This lets an end user choose who + to bill for what during multi-party bridges or bridge operations during + transfer scenarios. -chan_sip ------------------- - * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf - using the 'supportpath' setting, either on a global basis or on a peer basis. - This setting enables Asterisk to route outgoing out-of-dialog requests via a - set of proxies by using a pre-loaded route-set defined by the Path headers in - the REGISTER request. See Realtime updates for more configuration information. + * When a CDR is dispatched, user defined CDR variables from both parties are + included in the resulting CDR. If both parties have the same variable, only + the Party A value is provided. Features ------------------- @@ -163,12 +242,6 @@ Features and FEATUREMAP() functions inherited to child channels by setting FEATURE(inherit)=yes. -Functions ------------------- - * JITTERBUFFER now accepts an argument of 'disabled' which can be used - to remove jitterbuffers previously set on a channel with JITTERBUFFER. - The value of this setting is ignored when disabled is used for the argument. - Logging ------------------- * When performing queue pause/unpause on an interface without specifying an @@ -178,25 +251,12 @@ Logging * Added the 'queue_log_realtime_use_gmt' option to have timestamps in GMT for realtime queue log entries. -MeetMe -------------------- -* Added the 'n' option to MeetMe to prevent application of the DENOISE function - to a channel joining a conference. Some channel drivers that vary the number - of audio samples in a voice frame will experience significant quality problems - if a denoiser is attached to the channel; this option gives them the ability - to remove the denoiser without having to unload func_speex. - Parking ------------------- * Parking is now implemented as a module instead of as core functionality. The preferred way to configure parking is now through res_parking.conf while configuration through features.conf is not currently supported. - * res_parking uses the configuration framework. If an invalid configuration is - supplied, res_parking will fail to load or fail to reload. Previously parking - lots that were misconfigured would generally be accepted with certain - configuration problems leading to individual disabled parking lots. - * Parked calls are now placed in bridges. This is a largely architectural change, but it could have some implications in allowing for new parked call retrieval methods and the contents of parking lots will be visible though certain bridge @@ -236,63 +296,96 @@ Parking by default. Instead, it will follow the timeout rules of the parking lot. The old behavior can be reproduced by using the 'c' option. - * Added a channel variable PARKER_FLAT which stores the name of the extension - that would be used to come back to if comebacktoorigin was set to use. This can - be useful when comebacktoorigin is off if you still want to use the extensions - in the park-dial context that are generated to redial the parker on timeout. +Realtime +------------------ + * Dynamic realtime tables for SIP Users can now include a 'path' field. This + will store the path information for that peer when it registers. Realtime + tables can also use the 'supportpath' field to enable Path header support. -Queue -------------------- - * Add queue available hint. exten => 8501,hint,Queue:markq_avail - Note: the suffix '_avail' after the queuename. - Reports 'InUse' for no logged in agents or no free agents. - Reports 'Idle' when an agent is free. + * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport + objectIdentifier. This maps to the supportpath option in sip.conf. + +Sorcery +------------------ + * All future modules which utilize Sorcery for object persistence must have a + column named "id" within their schema when using the Sorcery realtime module. + This column must be able to contain a string of up to 128 characters in length. - * The configuration options eventwhencalled and eventmemberstatus have been - removed. As a result, the AMI events QueueMemberStatus, AgentCalled, - AgentConnect, AgentComplete, AgentDump, and AgentRingNoAnswer will always be - sent. The "Variable" fields will also no longer exist on the Agent* events. -Core +Channel Drivers ------------------ - * Redirecting reasons can now be set to arbitrary strings. This means - that the REDIRECTING dialplan function can be used to set the redirecting - reason to any string. It also allows for custom strings to be read as the - redirecting reason from SIP Diversion headers. + * When a channel driver is configured to enable jiterbuffers, they are now + applied unconditionally when a channel joins a bridge. If a jitterbuffer + is already set for that channel when it enters, such as by the JITTERBUFFER + function, then the existing jitterbuffer will be used and the one set by + the channel driver will not be applied. + +chan_agent +------------------ + * The updatecdr option has been removed. Altering the names of channels on a + CDR is not supported - the name of the channel is the name of the channel, + and pretending otherwise helps no one. + * The AGENTUPDATECDR channel variable has also been removed, for the same + reason as the updatecdr option. + +chan_local +------------------ + * The /b option is removed. - * For DTMF blind and attended transfers, the channel variable TRANSFER_CONTEXT - must be on the channel initiating the transfer to have any effect. + * chan_local moved into the system core and is no longer a loadable module. - * The channel variable ATTENDED_TRANSFER_COMPLETE_SOUND is no longer channel - driver specific. If the channel variable is set on the transferrer channel, - the sound will be played to the target of an attended transfer. +chan_mobile +------------------ + * Added general support for busy detection. - * The channel variable BRIDGEPEER becomes a comma separated list of peers in - a multi-party bridge. The BRIDGEPEER value can have a maximum of 10 peers - listed. Any more peers in the bridge will not be included in the list. - BRIDGEPEER is not valid in holding bridges like parking since those channels - do not talk to each other even though they are in a bridge. + * Added ECAM command support for Sony Ericsson phones. - * The channel variable BRIDGEPVTCALLID is only valid for two party bridges - and will contain a value if the BRIDGEPEER's channel driver supports it. +chan_sip +------------------ + * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf + using the 'supportpath' setting, either on a global basis or on a peer basis. + This setting enables Asterisk to route outgoing out-of-dialog requests via a + set of proxies by using a pre-loaded route-set defined by the Path headers in + the REGISTER request. See Realtime updates for more configuration information. - * The channel variable DYNAMIC_PEERNAME is redundant with BRIDGEPEER and is - removed. The more useful DYNAMIC_WHO_ACTIVATED gives the channel name that - activated the dynamic feature. - * The channel variables DYNAMIC_FEATURENAME and DYNAMIC_WHO_ACTIVATED are set - only on the channel executing the dynamic feature. Executing a dynamic - feature on the bridge peer in a multi-party bridge will execute it on all - peers of the activating channel. +Functions +------------------ -Realtime +JITTERBUFFER ------------------ - * Dynamic realtime tables for SIP Users can now include a 'path' field. This - will store the path information for that peer when it registers. Realtime - tables can also use the 'supportpath' field to enable Path header support. + * JITTERBUFFER now accepts an argument of 'disabled' which can be used + to remove jitterbuffers previously set on a channel with JITTERBUFFER. + The value of this setting is ignored when disabled is used for the argument. - * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport - objectIdentifier. This maps to the supportpath option in sip.conf. +CDR (function) +------------------ + * The 'amaflags' and 'accountcode' attributes for the CDR function are + deprecated. Use the CHANNEL function instead to access these attributes. + * The 'l' option has been removed. When reading a CDR attribute, the most + recent record is always used. When writing a CDR attribute, all non-finalized + CDRs are updated. + * The 'r' option has been removed, for the same reason as the 'l' option. + * The 's' option has been removed, as LOCKED semantics no longer exist in the + CDR engine. + +CDR_PROP +------------------ + * A new function CDR_PROP has been added. This function lets you set properties + on a channel's active CDRs. This function is write-only. Properties accept + boolean values to set/clear them on the channel's CDRs. Valid properties + include: + * 'party_a' - make this channel the preferred Party A in any CDR between two + channels. If two channels have this property set, the creation time of the + channel is used to determine who is Party A. Note that dialed channels are + never Party A in a CDR. + * 'disable' - disable CDRs on this channel. This is analogous to the NoCDR + application when set to True, and analogous to the 'e' option in ResetCDR + when set to False. + + +Resources +------------------ RTP ------------------ diff --git a/UPGRADE.txt b/UPGRADE.txt index dfe808141ee32f875bd8c7ef98523d0a373a1997..7a5261b94d7da4c2f4fbb45f38617b13f99e296d 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 b87d8c6aa14d59a4bfbcccacdf3469c023e36e7f..23e96c562f5e533e6cf97dc7e48c389ee8e349e4 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 a4f62acd8993032f0c14a5be0576981a080163fa..303b06857a4cfb4dce5bab22e8f1843c08e0f9e7 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 fbb430089524a38723f1bf789aabdc1a8462131a..a8370588b8fbf9829e6fb91264f200e9fd817f7f 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 3c244712bb8a217b3fbd03c552fc06b598768abe..ba7139cf1a5b77da99559ab089d3e87b6de49095 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 <application name="NoCDR" language="en_US"> <synopsis> - Tell Asterisk to not maintain a CDR for the current call + Tell Asterisk to not maintain a CDR for this channel. </synopsis> <syntax /> <description> - <para>This application will tell Asterisk not to maintain a CDR for the current call.</para> + <para>This application will tell Asterisk not to maintain a CDR for + the current channel. This does <emphasis>NOT</emphasis> mean that + information is not tracked; rather, if the channel is hung up no + CDRs will be created for that channel.</para> + <para>If a subsequent call to ResetCDR occurs, all non-finalized + CDRs created for the channel will be enabled.</para> + <note><para>This application is deprecated. Please use the CDR_PROP + function to disable CDRs on a channel.</para></note> </description> + <see-also> + <ref type="application">ResetCDR</ref> + <ref type="function">CDR_PROP</ref> + </see-also> + </application> + <application name="ResetCDR" language="en_US"> + <synopsis> + Resets the Call Data Record. + </synopsis> + <syntax> + <parameter name="options"> + <optionlist> + <option name="v"> + <para>Save the CDR variables during the reset.</para> + </option> + <option name="e"> + <para>Enable the CDRs for this channel only (negate + effects of NoCDR).</para> + </option> + </optionlist> + </parameter> + </syntax> + <description> + <para>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:</para> + <para>1. The <literal>start</literal> time is set to the current time.</para> + <para>2. If the channel is answered, the <literal>answer</literal> time is set to the + current time.</para> + <para>3. All variables are wiped from the CDR. Note that this step + can be prevented with the <literal>v</literal> option.</para> + <para>On the other hand, if the <literal>e</literal> option is + specified, the effects of the NoCDR application will be lifted. CDRs + will be re-enabled for this channel.</para> + <note><para>The <literal>e</literal> option is deprecated. Please + use the CDR_PROP function instead.</para></note> + </description> + <see-also> + <ref type="application">ForkCDR</ref> + <ref type="application">NoCDR</ref> + <ref type="function">CDR_PROP</ref> + </see-also> </application> ***/ 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 b0caa5c6be4535bce95ed68a8f66bf3852f1e29a..c75eb2d3a4f7ea1bbf70af28a85d539aace85704 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 c43370c95ee3717885f74822fe2f2b1b9951f022..fe53772f1e7dd2d083ed825f7c91bb26ce5816da 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 722f15541757cd9545900d0cfa11a61b8eeaba6c..7613832d4d614bbc4ee2c09ffc1f17d0b7abdd14 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 66980009d4e36243271cde4e861c13ca1dcc8347..d12de3c1a9d044b8e44920566fe8b30d7e335e8c 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 354792fb9a49d80b8b6e7154a95aaff66894a8d4..6231d381f7dad9635234c56605fbbe07ec7cee4c 100644 --- a/apps/app_forkcdr.c +++ b/apps/app_forkcdr.c @@ -44,98 +44,46 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*** DOCUMENTATION <application name="ForkCDR" language="en_US"> <synopsis> - Forks the Call Data Record. + Forks the current Call Data Record for this channel. </synopsis> <syntax> <parameter name="options"> <optionlist> <option name="a"> - <para>Update the answer time on the NEW CDR just after it's been inited. - The new CDR may have been answered already. The reset that forkcdr does - will erase the answer time. This will bring it back, but the answer time - will be a copy of the fork/start time. It will only do this if the initial - cdr was indeed already answered.</para> - </option> - <option name="A"> - <para>Lock the original CDR against the answer time being updated. This - will allow the disposition on the original CDR to remain the same.</para> - </option> - <option name="d"> - <para>Copy the disposition forward from the old cdr, after the init.</para> - </option> - <option name="D"> - <para>Clear the <literal>dstchannel</literal> on the new CDR after - reset.</para> + <para>If the channel is answered, set the answer time on + the forked CDR to the current time. If this option is + not used, the answer time on the forked CDR will be the + answer time on the original CDR. If the channel is not + answered, this option has no effect.</para> + <para>Note that this option is implicitly assumed if the + <literal>r</literal> option is used.</para> </option> <option name="e"> - <para>End the original CDR. Do this after all the necessary data is copied - from the original CDR to the new forked CDR.</para> + <para>End (finalize) the original CDR.</para> </option> <option name="r"> - <para>Do <emphasis>NOT</emphasis> reset the new cdr.</para> - </option> - <option name="s(name=val)"> - <para>Set the CDR var <replaceable>name</replaceable> in the original CDR, - with value <replaceable>val</replaceable>.</para> - </option> - <option name="T"> - <para>Mark the original CDR with a DONT_TOUCH flag. setvar, answer, and end - cdr funcs will obey this flag; normally they don't honor the LOCKED flag - set on the original CDR record.</para> - <note><para>Using this flag may cause CDR's not to have their end times - updated! It is suggested that if you specify this flag, you might wish - to use the <literal>e</literal> flag as well!.</para></note> + <para>Reset the start and answer times on the forked CDR. + This will set the start and answer times (if the channel + is answered) to be set to the current time.</para> + <para>Note that this option implicitly assumes the + <literal>a</literal> option.</para> </option> <option name="v"> - <para>When the new CDR is forked, it gets a copy of the vars attached to - the current CDR. The vars attached to the original CDR are removed unless - this option is specified.</para> + <para>Do not copy CDR variables and attributes from the + original CDR to the forked CDR.</para> + <warning><para>This option has changed. Previously, the + variables were removed from the original CDR. This no + longer occurs - this option now controls whether or not + a forked CDR inherits the variables from the original + CDR.</para></warning> </option> </optionlist> </parameter> </syntax> <description> - <para> 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.</para> - <para>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.</para> - <para>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)</para> - <para>This means that:</para> - <para> 1. All flags are unset on the cdr record</para> - <para> 2. the start, end, and answer times are all set to zero.</para> - <para> 3. the billsec and duration fields are set to zero.</para> - <para> 4. the start time is set to the current time.</para> - <para> 5. the disposition is set to NULL.</para> - <para>Next, unless you specified the <literal>v</literal> option, all variables will be removed from - the original cdr record. Thus, the <literal>v</literal> option allows any CDR variables to be replicated - to all new forked cdr records. Without the <literal>v</literal> option, the variables on the original - are effectively moved to the new forked cdr record.</para> - <para>Next, if the <literal>s</literal> option is set, the provided variable and value are set on the - original cdr record.</para> - <para>Next, if the <literal>a</literal> 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.</para> - <para>If the <literal>d</literal> option was specified, the disposition is copied from - the original cdr record to the new forked cdr. If the <literal>D</literal> option was specified, - the destination channel field in the new forked CDR is erased. If the <literal>e</literal> 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 <literal>A</literal> 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 <literal>ANSWERED</literal> event would mark all cdr - records in the chain as <literal>ANSWERED</literal>. If the <literal>T</literal> option is specified, - the original cdr record will have its <literal>DONT_TOUCH</literal> flag set, which will force the - cdr_answer, cdr_end, and cdr_setvar functions to leave that cdr record alone.</para> - <para>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.</para> + <para>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.</para> </description> <see-also> <ref type="function">CDR</ref> @@ -147,126 +95,34 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") static char *app = "ForkCDR"; -enum { - OPT_SETANS = (1 << 0), - OPT_SETDISP = (1 << 1), - OPT_RESETDEST = (1 << 2), - OPT_ENDCDR = (1 << 3), - OPT_NORESET = (1 << 4), - OPT_KEEPVARS = (1 << 5), - OPT_VARSET = (1 << 6), - OPT_ANSLOCK = (1 << 7), - OPT_DONTOUCH = (1 << 8), -}; - -enum { - OPT_ARG_VARSET = 0, - /* note: this entry _MUST_ be the last one in the enum */ - OPT_ARG_ARRAY_SIZE, -}; - AST_APP_OPTIONS(forkcdr_exec_options, { - AST_APP_OPTION('a', OPT_SETANS), - AST_APP_OPTION('A', OPT_ANSLOCK), - AST_APP_OPTION('d', OPT_SETDISP), - AST_APP_OPTION('D', OPT_RESETDEST), - AST_APP_OPTION('e', OPT_ENDCDR), - AST_APP_OPTION('R', OPT_NORESET), - AST_APP_OPTION_ARG('s', OPT_VARSET, OPT_ARG_VARSET), - AST_APP_OPTION('T', OPT_DONTOUCH), - AST_APP_OPTION('v', OPT_KEEPVARS), + AST_APP_OPTION('a', AST_CDR_FLAG_SET_ANSWER), + AST_APP_OPTION('e', AST_CDR_FLAG_FINALIZE), + AST_APP_OPTION('r', AST_CDR_FLAG_RESET), + AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), }); -static void ast_cdr_fork(struct ast_channel *chan, struct ast_flags optflags, char *set) -{ - struct ast_cdr *cdr; - struct ast_cdr *newcdr; - struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS }; - - cdr = ast_channel_cdr(chan); - - while (cdr->next) - cdr = cdr->next; - - if (!(newcdr = ast_cdr_dup_unique(cdr))) - return; - - /* - * End the original CDR if requested BEFORE appending the new CDR - * otherwise we incorrectly end the new CDR also. - */ - if (ast_test_flag(&optflags, OPT_ENDCDR)) { - ast_cdr_end(cdr); - } - - ast_cdr_append(cdr, newcdr); - - if (!ast_test_flag(&optflags, OPT_NORESET)) - ast_cdr_reset(newcdr, &flags); - - if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS)) - ast_cdr_free_vars(cdr, 0); - - if (!ast_strlen_zero(set)) { - char *varname = ast_strdupa(set), *varval; - varval = strchr(varname,'='); - if (varval) { - *varval = 0; - varval++; - ast_cdr_setvar(cdr, varname, varval, 0); - } - } - - if (ast_test_flag(&optflags, OPT_SETANS) && !ast_tvzero(cdr->answer)) - newcdr->answer = newcdr->start; - - if (ast_test_flag(&optflags, OPT_SETDISP)) - newcdr->disposition = cdr->disposition; - - if (ast_test_flag(&optflags, OPT_RESETDEST)) - newcdr->dstchannel[0] = 0; - - if (ast_test_flag(&optflags, OPT_ANSLOCK)) - ast_set_flag(cdr, AST_CDR_FLAG_ANSLOCKED); - - if (ast_test_flag(&optflags, OPT_DONTOUCH)) - ast_set_flag(cdr, AST_CDR_FLAG_DONT_TOUCH); - - ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED); -} - static int forkcdr_exec(struct ast_channel *chan, const char *data) { - int res = 0; - char *argcopy = NULL; - struct ast_flags flags = {0}; - char *opts[OPT_ARG_ARRAY_SIZE]; - AST_DECLARE_APP_ARGS(arglist, + char *parse; + struct ast_flags flags = { 0, }; + AST_DECLARE_APP_ARGS(args, AST_APP_ARG(options); ); - if (!ast_channel_cdr(chan)) { - ast_log(LOG_WARNING, "Channel does not have a CDR\n"); - return 0; - } - - argcopy = ast_strdupa(data); + parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(arglist, argcopy); + AST_STANDARD_APP_ARGS(args, parse); - opts[OPT_ARG_VARSET] = 0; - - if (!ast_strlen_zero(arglist.options)) - ast_app_parse_options(forkcdr_exec_options, &flags, opts, arglist.options); + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(forkcdr_exec_options, &flags, NULL, args.options); + } - if (!ast_strlen_zero(data)) { - int keepvars = ast_test_flag(&flags, OPT_KEEPVARS) ? 1 : 0; - ast_set2_flag(ast_channel_cdr(chan), keepvars, AST_CDR_FLAG_KEEP_VARS); + if (ast_cdr_fork(ast_channel_name(chan), &flags)) { + ast_log(AST_LOG_WARNING, "Failed to fork CDR for channel %s\n", ast_channel_name(chan)); } - - ast_cdr_fork(chan, flags, opts[OPT_ARG_VARSET]); - return res; + return 0; } static int unload_module(void) diff --git a/apps/app_osplookup.c b/apps/app_osplookup.c index b37a2ae63a91d161e7d60e006c9e85c61fcf5f8a..5ab497f5e43be274ed0500866c4d7237454de5d7 100644 --- a/apps/app_osplookup.c +++ b/apps/app_osplookup.c @@ -2814,7 +2814,7 @@ static int ospfinished_exec( int inhandle = OSP_INVALID_HANDLE; int outhandle = OSP_INVALID_HANDLE; int recorded = 0; - time_t start, connect, end; + time_t start = 0, connect = 0, end = 0; unsigned int release; char buffer[OSP_SIZE_INTSTR]; char inqos[OSP_SIZE_QOSSTR] = { 0 }; @@ -2866,19 +2866,18 @@ static int ospfinished_exec( } ast_debug(1, "OSPFinish: cause '%d'\n", cause); - if (ast_channel_cdr(chan)) { - start = ast_channel_cdr(chan)->start.tv_sec; - connect = ast_channel_cdr(chan)->answer.tv_sec; - if (connect) { - end = time(NULL); - } else { - end = connect; - } + if (!ast_tvzero(ast_channel_creationtime(chan))) { + start = ast_channel_creationtime(chan).tv_sec; + } + if (!ast_tvzero(ast_channel_answertime(chan))) { + connect = ast_channel_answertime(chan).tv_sec; + } + if (connect) { + end = time(NULL); } else { - start = 0; - connect = 0; - end = 0; + end = connect; } + ast_debug(1, "OSPFinish: start '%ld'\n", start); ast_debug(1, "OSPFinish: connect '%ld'\n", connect); ast_debug(1, "OSPFinish: end '%ld'\n", end); diff --git a/apps/app_queue.c b/apps/app_queue.c index 4c96583137dfa5b076756580b3e804073a4edfaa..724ea47ff3cb74e448e9b5e666ac8656ac6ba697 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 4bf3602cbc278eeca609860527a7f7d5db6415fa..a590fb32a1ec1dccc49604d090a17b4993d4de53 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 5cfde82d7da7086b303146c8a07f05aaabe7f51c..a6f8a4dc0a33cb508fd96bb4b69e61b9f4a8f424 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 290e5344da31f3100ef857ff240b57e96ea5ea6a..2a3b1a1dd06c23b2986bc5de6a5df7ccb4754017 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 a82bcf9895497a682973bf5b6003345dbe55bc57..e3ae7a57d215da46cce019925a4feaf294eb4193 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 7ea2f041febe1b6a7b4e09aab8124a8964684998..022d75210e5bbe59712d0a85e227cff936e012cf 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 906f0227c7094d97f916c683bab8de903ebc90fc..dc73de4779385b069a569a19a490a83bc7282bba 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 92ec8a4b48dc83b61709fb6e79861ab0ddfcaae1..2bf2002feb52d9f42dc74e5b253c1063361b8f25 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 8a7f07713a4e0eb300ae634a2a4803b4748d76a3..dec4d65e9d3225e560b89c6e0d1d3cb84097a965 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 dd75dbb464741f4da1ffa3f3ae9c35eced32f765..aef57b55d15aa19d15632550fb6aebe2f4f59acd 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 e1d0dc148c03102c421484f5a18f6d70a0e2f929..245c7800d463e1a3b1e2e793857b429c7a2f47b2 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 0edf57f4ce97c6236e6250a9e629d5dcba19b5af..9067a049188d68cb60e2fc6dede07433bbbf21af 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 df2b573bf252c7cd61ab10b3b158581bc0e91981..1bb4d517e65b6f4597d413bac957ddf707980bc7 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 d72254ee75d914504ef411d4345caf985ba12898..57f0914cfd1d4cf693e166934f875c3f0109d925 100644 --- a/channels/chan_agent.c +++ b/channels/chan_agent.c @@ -112,10 +112,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <option name="d"> <para>make the app return <literal>-1</literal> if there is an error condition.</para> </option> - <option name="c"> - <para>change the CDR so that the source of the call is - <literal>Agent/agent_id</literal></para> - </option> <option name="n"> <para>don't generate the warnings when there is no callerid or the agentid is not known. It's handy if you want to have one context @@ -234,7 +230,6 @@ static char recordformat[AST_MAX_BUF] = ""; static char recordformatext[AST_MAX_BUF] = ""; static char urlprefix[AST_MAX_BUF] = ""; static char savecallsin[AST_MAX_BUF] = ""; -static int updatecdr = 0; static char beep[AST_MAX_BUF] = "beep"; #define GETAGENTBYCALLERID "AGENTBYCALLERID" @@ -573,7 +568,7 @@ static int agent_answer(struct ast_channel *ast) static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p, int needlock) { - char tmp[AST_MAX_BUF],tmp2[AST_MAX_BUF], *pointer; + char tmp[AST_MAX_BUF], tmp2[AST_MAX_BUF], *pointer; char filename[AST_MAX_BUF]; int res = -1; if (!p) @@ -590,9 +585,7 @@ static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p #if 0 ast_verbose("name is %s, link is %s\n",tmp, tmp2); #endif - if (!ast_channel_cdr(ast)) - ast_channel_cdr_set(ast, ast_cdr_alloc()); - ast_cdr_setuserfield(ast, tmp2); + ast_cdr_setuserfield(ast_channel_name(ast), tmp2); res = 0; } else ast_log(LOG_ERROR, "Recording already started on that call.\n"); @@ -1199,11 +1192,6 @@ static int read_agent_config(int reload) strcpy(agentgoodbye,v->value); } else if (!strcasecmp(v->name, "musiconhold")) { ast_copy_string(moh, v->value, sizeof(moh)); - } else if (!strcasecmp(v->name, "updatecdr")) { - if (ast_true(v->value)) - updatecdr = 1; - else - updatecdr = 0; } else if (!strcasecmp(v->name, "autologoffunavail")) { if (ast_true(v->value)) autologoffunavail = 1; @@ -1898,7 +1886,6 @@ static int login_exec(struct ast_channel *chan, const char *data) const char *tmpoptions = NULL; int play_announcement = 1; char agent_goodbye[AST_MAX_FILENAME_LEN]; - int update_cdr = updatecdr; user[0] = '\0'; xpass[0] = '\0'; @@ -1918,14 +1905,6 @@ static int login_exec(struct ast_channel *chan, const char *data) tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES"); ast_verb(3, "Saw variable AGENTMAXLOGINTRIES=%s, setting max_login_tries to: %d on Channel '%s'.\n",tmpoptions,max_login_tries,ast_channel_name(chan)); } - if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR"))) { - if (ast_true(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR"))) - update_cdr = 1; - else - update_cdr = 0; - tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR"); - ast_verb(3, "Saw variable AGENTUPDATECDR=%s, setting update_cdr to: %d on Channel '%s'.\n",tmpoptions,update_cdr,ast_channel_name(chan)); - } if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"))) { strcpy(agent_goodbye, pbx_builtin_getvar_helper(chan, "AGENTGOODBYE")); tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"); @@ -2093,8 +2072,6 @@ static int login_exec(struct ast_channel *chan, const char *data) "Channel: %s\r\n" "Uniqueid: %s\r\n", p->agent, ast_channel_name(chan), ast_channel_uniqueid(chan)); - if (update_cdr && ast_channel_cdr(chan)) - snprintf(ast_channel_cdr(chan)->channel, sizeof(ast_channel_cdr(chan)->channel), "%s", agent); ast_queue_log("NONE", ast_channel_uniqueid(chan), agent, "AGENTLOGIN", "%s", ast_channel_name(chan)); ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", p->agent, ast_getformatname(ast_channel_readformat(chan)), ast_getformatname(ast_channel_writeformat(chan))); @@ -2242,17 +2219,16 @@ static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data) { int exitifnoagentid = 0; int nowarnings = 0; - int changeoutgoing = 0; int res = 0; char agent[AST_MAX_AGENT]; if (data) { - if (strchr(data, 'd')) + if (strchr(data, 'd')) { exitifnoagentid = 1; - if (strchr(data, 'n')) + } + if (strchr(data, 'n')) { nowarnings = 1; - if (strchr(data, 'c')) - changeoutgoing = 1; + } } if (ast_channel_caller(chan)->id.number.valid && !ast_strlen_zero(ast_channel_caller(chan)->id.number.str)) { @@ -2266,7 +2242,6 @@ static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data) AST_LIST_LOCK(&agents); AST_LIST_TRAVERSE(&agents, p, list) { if (!strcasecmp(p->agent, tmp)) { - if (changeoutgoing) snprintf(ast_channel_cdr(chan)->channel, sizeof(ast_channel_cdr(chan)->channel), "Agent/%s", p->agent); __agent_start_monitoring(chan, p, 1); break; } diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c index 109fac06d36f772aebf202a9cb53c4675fb921b5..24337e385747a1aea941d7b67e992ef4fde05f6e 100644 --- a/channels/chan_dahdi.c +++ b/channels/chan_dahdi.c @@ -107,7 +107,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/callerid.h" #include "asterisk/adsi.h" #include "asterisk/cli.h" -#include "asterisk/cdr.h" #include "asterisk/cel.h" #include "asterisk/features.h" #include "asterisk/musiconhold.h" @@ -17726,7 +17725,7 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct } else if (!strcasecmp(v->name, "accountcode")) { ast_copy_string(confp->chan.accountcode, v->value, sizeof(confp->chan.accountcode)); } else if (!strcasecmp(v->name, "amaflags")) { - y = ast_cdr_amaflags2int(v->value); + y = ast_channel_string2amaflag(v->value); if (y < 0) ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d.\n", v->value, v->lineno); else diff --git a/channels/chan_h323.c b/channels/chan_h323.c index 61817dbff9c78ef78349898020e126327d7bc84f..e26bb5fb643715c62421c570e9a0a301f2632086 100644 --- a/channels/chan_h323.c +++ b/channels/chan_h323.c @@ -1482,7 +1482,7 @@ static struct oh323_user *build_user(const char *name, struct ast_variable *v, s /* Let us know we need to use ip authentication */ user->host = 1; } else if (!strcasecmp(v->name, "amaflags")) { - format = ast_cdr_amaflags2int(v->value); + format = ast_channel_string2amaflag(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno); } else { diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index 7c0de993633b5dbcbe6575c8eaf026c5a4ccf2c0..486af52a77639fb00a47576ce41b4f08c762d027 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -77,7 +77,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/translate.h" #include "asterisk/md5.h" -#include "asterisk/cdr.h" #include "asterisk/crypto.h" #include "asterisk/acl.h" #include "asterisk/manager.h" @@ -12802,7 +12801,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st } else if (!strcasecmp(v->name, "language")) { ast_string_field_set(user, language, v->value); } else if (!strcasecmp(v->name, "amaflags")) { - format = ast_cdr_amaflags2int(v->value); + format = ast_channel_string2amaflag(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno); } else { @@ -13273,7 +13272,7 @@ static int set_config(const char *config_file, int reload, int forced) } else if (!strcasecmp(v->name, "mohsuggest")) { ast_copy_string(mohsuggest, v->value, sizeof(mohsuggest)); } else if (!strcasecmp(v->name, "amaflags")) { - format = ast_cdr_amaflags2int(v->value); + format = ast_channel_string2amaflag(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno); } else { @@ -14513,7 +14512,7 @@ static int users_data_provider_get(const struct ast_data_search *search, continue; } ast_data_add_int(data_enum_node, "value", user->amaflags); - ast_data_add_str(data_enum_node, "text", ast_cdr_flags2str(user->amaflags)); + ast_data_add_str(data_enum_node, "text", ast_channel_amaflags2string(user->amaflags)); ast_data_add_bool(data_user, "access-control", ast_acl_list_is_empty(user->acl) ? 0 : 1); diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c index 9080dbaf0380741fd371ab52195b2a0997eed7b4..5a0b7ad84c80f0a65da58e2996e598bdeda9ec25 100644 --- a/channels/chan_mgcp.c +++ b/channels/chan_mgcp.c @@ -67,7 +67,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/callerid.h" #include "asterisk/cli.h" #include "asterisk/say.h" -#include "asterisk/cdr.h" #include "asterisk/astdb.h" #include "asterisk/features.h" #include "asterisk/app.h" @@ -4087,7 +4086,7 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v) } else if (!strcasecmp(v->name, "accountcode")) { ast_copy_string(accountcode, v->value, sizeof(accountcode)); } else if (!strcasecmp(v->name, "amaflags")) { - y = ast_cdr_amaflags2int(v->value); + y = ast_channel_string2amaflag(v->value); if (y < 0) { ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno); } else { diff --git a/channels/chan_sip.c b/channels/chan_sip.c index eb79d237ef8c9dd2be84e7f317398189e629d41b..fbd7f1c221326290eb37a47bb6d0405f00cb1d52 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -20224,7 +20224,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct ast_cli(fd, " Tonezone : %s\n", peer->zone[0] != '\0' ? peer->zone : "<Not set>"); if (!ast_strlen_zero(peer->accountcode)) ast_cli(fd, " Accountcode : %s\n", peer->accountcode); - ast_cli(fd, " AMA flags : %s\n", ast_cdr_flags2str(peer->amaflags)); + ast_cli(fd, " AMA flags : %s\n", ast_channel_amaflags2string(peer->amaflags)); ast_cli(fd, " Transfer mode: %s\n", transfermode2str(peer->allowtransfer)); ast_cli(fd, " CallingPres : %s\n", ast_describe_caller_presentation(peer->callingpres)); if (!ast_strlen_zero(peer->fromuser)) @@ -20362,7 +20362,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct astman_append(s, "ToneZone: %s\r\n", peer->zone[0] != '\0' ? peer->zone : "<Not set>"); if (!ast_strlen_zero(peer->accountcode)) astman_append(s, "Accountcode: %s\r\n", peer->accountcode); - astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(peer->amaflags)); + astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(peer->amaflags)); astman_append(s, "CID-CallingPres: %s\r\n", ast_describe_caller_presentation(peer->callingpres)); if (!ast_strlen_zero(peer->fromuser)) astman_append(s, "SIP-FromUser: %s\r\n", peer->fromuser); @@ -20537,7 +20537,7 @@ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args ast_cli(a->fd, " Language : %s\n", user->language); if (!ast_strlen_zero(user->accountcode)) ast_cli(a->fd, " Accountcode : %s\n", user->accountcode); - ast_cli(a->fd, " AMA flags : %s\n", ast_cdr_flags2str(user->amaflags)); + ast_cli(a->fd, " AMA flags : %s\n", ast_channel_amaflags2string(user->amaflags)); ast_cli(a->fd, " Tonezone : %s\n", user->zone[0] != '\0' ? user->zone : "<Not set>"); ast_cli(a->fd, " Transfer mode: %s\n", transfermode2str(user->allowtransfer)); ast_cli(a->fd, " MaxCallBR : %d kbps\n", user->maxcallbitrate); @@ -20724,8 +20724,6 @@ static int show_chanstats_cb(void *__cur, void *__arg, int flags) struct sip_pvt *cur = __cur; struct ast_rtp_instance_stats stats; char durbuf[10]; - int duration; - int durh, durm, durs; struct ast_channel *c; struct __show_chan_arg *arg = __arg; int fd = arg->fd; @@ -20756,12 +20754,8 @@ static int show_chanstats_cb(void *__cur, void *__arg, int flags) return 0; } - if (c && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) { - duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000); - durh = duration / 3600; - durm = (duration % 3600) / 60; - durs = duration % 60; - snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs); + if (c) { + ast_format_duration_hh_mm_ss(ast_channel_get_duration(c), durbuf, sizeof(durbuf)); } else { durbuf[0] = '\0'; } @@ -21694,11 +21688,8 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req) } else if (!ast_strlen_zero(c = sip_get_header(req, "X-ClientCode"))) { /* Client code (from SNOM phone) */ if (ast_test_flag(&p->flags[0], SIP_USECLIENTCODE)) { - if (p->owner && ast_channel_cdr(p->owner)) { - ast_cdr_setuserfield(p->owner, c); - } - if (p->owner && ast_bridged_channel(p->owner) && ast_channel_cdr(ast_bridged_channel(p->owner))) { - ast_cdr_setuserfield(ast_bridged_channel(p->owner), c); + if (p->owner) { + ast_cdr_setuserfield(ast_channel_name(p->owner), c); } transmit_response(p, "200 OK", req); } else { @@ -24831,7 +24822,7 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, ast_channel_unlock(c); sip_pvt_unlock(p); - ast_raw_answer(c, 1); + ast_raw_answer(c); ast_channel_lock(replaces_chan); bridge = ast_channel_get_bridge(replaces_chan); @@ -30458,7 +30449,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str } else if (!strcasecmp(v->name, "callbackextension")) { ast_string_field_set(peer, callback, v->value); } else if (!strcasecmp(v->name, "amaflags")) { - format = ast_cdr_amaflags2int(v->value); + format = ast_channel_string2amaflag(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags for peer: %s at line %d\n", v->value, v->lineno); } else { @@ -34023,7 +34014,7 @@ static int peers_data_provider_get(const struct ast_data_search *search, continue; } ast_data_add_int(enum_node, "value", peer->amaflags); - ast_data_add_str(enum_node, "text", ast_cdr_flags2str(peer->amaflags)); + ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(peer->amaflags)); /* sip options */ data_sip_options = ast_data_add_node(data_peer, "sipoptions"); diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c index 5e0756654273a29e20c5abd0f2435d1b453e68bf..ad51edf10279efb2b4a5edfe80d8f5ae8703592c 100644 --- a/channels/chan_skinny.c +++ b/channels/chan_skinny.c @@ -67,7 +67,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/manager.h" #include "asterisk/say.h" -#include "asterisk/cdr.h" #include "asterisk/astdb.h" #include "asterisk/features.h" #include "asterisk/app.h" @@ -4477,7 +4476,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str ast_str_reset(tmp_str); ast_cli(fd, "Language: %s\n", S_OR(l->language, "<not set>")); ast_cli(fd, "Accountcode: %s\n", S_OR(l->accountcode, "<not set>")); - ast_cli(fd, "AmaFlag: %s\n", ast_cdr_flags2str(l->amaflags)); + ast_cli(fd, "AmaFlag: %s\n", ast_channel_amaflags2string(l->amaflags)); ast_cli(fd, "CallerId Number: %s\n", S_OR(l->cid_num, "<not set>")); ast_cli(fd, "CallerId Name: %s\n", S_OR(l->cid_name, "<not set>")); ast_cli(fd, "Hide CallerId: %s\n", (l->hidecallerid ? "Yes" : "No")); @@ -4535,7 +4534,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str ast_str_reset(tmp_str); astman_append(s, "Language: %s\r\n", S_OR(l->language, "<not set>")); astman_append(s, "Accountcode: %s\r\n", S_OR(l->accountcode, "<not set>")); - astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(l->amaflags)); + astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(l->amaflags)); astman_append(s, "Callerid: %s\r\n", ast_callerid_merge(cbuf, sizeof(cbuf), l->cid_name, l->cid_num, "")); astman_append(s, "HideCallerId: %s\r\n", (l->hidecallerid ? "Yes" : "No")); astman_append(s, "CFwdAll: %s\r\n", S_COR((l->cfwdtype & SKINNY_CFWD_ALL), l->call_forward_all, "<not set>")); @@ -7809,7 +7808,7 @@ static void config_parse_variables(int type, void *item, struct ast_variable *vp } } else if (!strcasecmp(v->name, "amaflags")) { if (type & (TYPE_DEF_LINE | TYPE_LINE)) { - int tempamaflags = ast_cdr_amaflags2int(v->value); + int tempamaflags = ast_channel_string2amaflag(v->value); if (tempamaflags < 0) { ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno); } else { diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c index fbc6adda24181ee8181cc2c166af6e035d3ec47a..f512b0f6f7e97c133f5f924521d5832d6f16ed69 100644 --- a/channels/chan_unistim.c +++ b/channels/chan_unistim.c @@ -6392,7 +6392,7 @@ static struct unistim_device *build_device(const char *cat, const struct ast_var ast_copy_string(lt->accountcode, v->value, sizeof(lt->accountcode)); } else if (!strcasecmp(v->name, "amaflags")) { int y; - y = ast_cdr_amaflags2int(v->value); + y = ast_channel_string2amaflag(v->value); if (y < 0) { ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno); diff --git a/funcs/func_callerid.c b/funcs/func_callerid.c index 30026af3d30e890a2c6753fa4ac7d722221e27bb..b4649c17765327464e84c397a9419f5aadf39c3d 100644 --- a/funcs/func_callerid.c +++ b/funcs/func_callerid.c @@ -1142,9 +1142,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, ast_channel_redirecting(chan)->from.number.valid = 1; ast_free(ast_channel_redirecting(chan)->from.number.str); ast_channel_redirecting(chan)->from.number.str = ast_strdup(value); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } } else if (!strcasecmp("dnid", member.argv[0])) { ast_party_dialed_set_init(&dialed, ast_channel_dialed(chan)); if (member.argc == 1) { @@ -1162,9 +1159,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, dialed.number.str = ast_strdup(value); ast_trim_blanks(dialed.number.str); ast_party_dialed_set(ast_channel_dialed(chan), &dialed); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } } else if (member.argc == 3 && !strcasecmp("plan", member.argv[2])) { /* dnid-num-plan */ val = ast_strdupa(value); @@ -1172,9 +1166,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, if (('0' <= val[0]) && (val[0] <= '9')) { ast_channel_dialed(chan)->number.plan = atoi(val); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } } else { ast_log(LOG_ERROR, "Unknown type-of-number/numbering-plan '%s', value unchanged\n", val); @@ -1192,9 +1183,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, switch (status) { case ID_FIELD_VALID: ast_party_dialed_set(ast_channel_dialed(chan), &dialed); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } break; case ID_FIELD_INVALID: break; @@ -1212,9 +1200,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, if (('0' <= val[0]) && (val[0] <= '9')) { ast_channel_caller(chan)->ani2 = atoi(val); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } } else { ast_log(LOG_ERROR, "Unknown callerid ani2 '%s', value unchanged\n", val); } @@ -1229,9 +1214,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, switch (status) { case ID_FIELD_VALID: ast_party_caller_set(ast_channel_caller(chan), &caller, NULL); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } break; case ID_FIELD_INVALID: break; @@ -1246,9 +1228,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, switch (status) { case ID_FIELD_VALID: ast_party_caller_set(ast_channel_caller(chan), &caller, NULL); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } break; case ID_FIELD_INVALID: break; @@ -1263,9 +1242,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data, switch (status) { case ID_FIELD_VALID: ast_channel_set_caller_event(chan, &caller, NULL); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } break; case ID_FIELD_INVALID: break; diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c index adb42742b628fe99db9080c6dd591e8c31119a66..0f900feda8d28284b87a82bb7b520054f034865c 100644 --- a/funcs/func_cdr.c +++ b/funcs/func_cdr.c @@ -56,7 +56,27 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Last application arguments.</para> </enum> <enum name="disposition"> - <para>ANSWERED, NO ANSWER, BUSY, FAILED.</para> + <para>The final state of the CDR.</para> + <enumlist> + <enum name="0"> + <para><literal>NO ANSWER</literal></para> + </enum> + <enum name="1"> + <para><literal>NO ANSWER</literal> (NULL record)</para> + </enum> + <enum name="2"> + <para><literal>FAILED</literal></para> + </enum> + <enum name="4"> + <para><literal>BUSY</literal></para> + </enum> + <enum name="8"> + <para><literal>ANSWERED</literal></para> + </enum> + <enum name="16"> + <para><literal>CONGESTION</literal></para> + </enum> + </enumlist> </enum> <enum name="src"> <para>Source.</para> @@ -65,7 +85,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Time the call started.</para> </enum> <enum name="amaflags"> - <para>DOCUMENTATION, BILL, IGNORE, etc.</para> + <para>R/W the Automatic Message Accounting (AMA) flags on the channel. + When read from a channel, the integer value will always be returned. + When written to a channel, both the string format or integer value + is accepted.</para> + <enumlist> + <enum name="1"><para><literal>OMIT</literal></para></enum> + <enum name="2"><para><literal>BILLING</literal></para></enum> + <enum name="3"><para><literal>DOCUMENTATION</literal></para></enum> + </enumlist> + <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning> </enum> <enum name="dst"> <para>Destination.</para> @@ -75,6 +104,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </enum> <enum name="accountcode"> <para>The channel's account code.</para> + <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning> </enum> <enum name="dcontext"> <para>Destination context.</para> @@ -113,16 +143,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <option name="f"> <para>Returns billsec or duration fields as floating point values.</para> </option> - <option name="l"> - <para>Uses the most recent CDR on a channel with multiple records</para> - </option> - <option name="r"> - <para>Searches the entire stack of CDRs on the channel.</para> - </option> - <option name="s"> - <para>Skips any CDR's that are marked 'LOCKED' due to forkCDR() calls. - (on setting/writing CDR vars only)</para> - </option> <option name="u"> <para>Retrieves the raw, unprocessed value.</para> <para>For example, 'start', 'answer', and 'end' will be retrieved as epoch @@ -137,138 +157,132 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>All of the CDR field names are read-only, except for <literal>accountcode</literal>, <literal>userfield</literal>, and <literal>amaflags</literal>. You may, however, supply a name not on the above list, and create your own variable, whose value can be changed - with this function, and this variable will be stored on the cdr.</para> - <note><para>For setting CDR values, the <literal>l</literal> flag does not apply to - setting the <literal>accountcode</literal>, <literal>userfield</literal>, or - <literal>amaflags</literal>.</para><para>CDRs can only be modified before the bridge - between two channels is torn down. For example, CDRs may not be modified after the - <literal>Dial</literal> application has returned.</para></note> - <para>Raw values for <literal>disposition</literal>:</para> - <enumlist> - <enum name="0"> - <para>NO ANSWER</para> - </enum> - <enum name="1"> - <para>NO ANSWER (NULL record)</para> - </enum> - <enum name="2"> - <para>FAILED</para> - </enum> - <enum name="4"> - <para>BUSY</para> - </enum> - <enum name="8"> - <para>ANSWERED</para> - </enum> - </enumlist> - <para>Raw values for <literal>amaflags</literal>:</para> - <enumlist> - <enum name="1"> - <para>OMIT</para> - </enum> - <enum name="2"> - <para>BILLING</para> - </enum> - <enum name="3"> - <para>DOCUMENTATION</para> - </enum> - </enumlist> + with this function, and this variable will be stored on the CDR.</para> + <note><para>CDRs can only be modified before the bridge between two channels is + torn down. For example, CDRs may not be modified after the <literal>Dial</literal> + application has returned.</para></note> <para>Example: exten => 1,1,Set(CDR(userfield)=test)</para> </description> </function> + <function name="CDR_PROP" language="en_US"> + <synopsis> + Set a property on a channel's CDR. + </synopsis> + <syntax> + <parameter name="name" required="true"> + <para>The property to set on the CDR.</para> + <enumlist> + <enum name="party_a"> + <para>Set this channel as the preferred Party A when + channels are associated together.</para> + <para>Write-Only</para> + </enum> + <enum name="disable"> + <para>Disable CDRs for this channel.</para> + <para>Write-Only</para> + </enum> + </enumlist> + </parameter> + </syntax> + <description> + <para>This function sets a property on a channel's CDR. Properties + alter the behavior of how the CDR operates for that channel.</para> + </description> + </function> ***/ enum cdr_option_flags { - OPT_RECURSIVE = (1 << 0), OPT_UNPARSED = (1 << 1), - OPT_LAST = (1 << 2), - OPT_SKIPLOCKED = (1 << 3), - OPT_FLOAT = (1 << 4), + OPT_FLOAT = (1 << 2), }; AST_APP_OPTIONS(cdr_func_options, { AST_APP_OPTION('f', OPT_FLOAT), - AST_APP_OPTION('l', OPT_LAST), - AST_APP_OPTION('r', OPT_RECURSIVE), - AST_APP_OPTION('s', OPT_SKIPLOCKED), AST_APP_OPTION('u', OPT_UNPARSED), }); static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse, char *buf, size_t len) { - char *ret = NULL; + char format_buf[128]; struct ast_flags flags = { 0 }; - struct ast_cdr *cdr; + char tempbuf[128]; + char *info; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(variable); AST_APP_ARG(options); ); - if (ast_strlen_zero(parse) || !chan) + if (!chan) { return -1; + } - ast_channel_lock(chan); - cdr = ast_channel_cdr(chan); - if (!cdr) { - ast_channel_unlock(chan); + if (ast_strlen_zero(parse)) { + ast_log(AST_LOG_WARNING, "FUNC_CDR requires a variable (FUNC_CDR(variable[,option]))\n)"); return -1; } + info = ast_strdupa(parse); + AST_STANDARD_APP_ARGS(args, info); - AST_STANDARD_APP_ARGS(args, parse); - - if (!ast_strlen_zero(args.options)) + if (!ast_strlen_zero(args.options)) { ast_app_parse_options(cdr_func_options, &flags, NULL, args.options); + } - if (ast_test_flag(&flags, OPT_LAST)) - while (cdr->next) - cdr = cdr->next; - - if (ast_test_flag(&flags, OPT_SKIPLOCKED)) - while (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED) && cdr->next) - cdr = cdr->next; - - if (!strcasecmp("billsec", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) { - if (!ast_tvzero(cdr->answer)) { - double hrtime; - - if(!ast_tvzero(cdr->end)) - hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0); - else - hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->answer) / 1000000.0); + if (ast_cdr_getvar(ast_channel_name(chan), args.variable, tempbuf, sizeof(tempbuf))) { + return 0; + } - snprintf(buf, len, "%lf", hrtime); - } else { - snprintf(buf, len, "%lf", 0.0); + if (ast_test_flag(&flags, OPT_FLOAT) && (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) { + long ms; + double dtime; + if (sscanf(tempbuf, "%30ld", &ms) != 1) { + ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n", + args.variable, tempbuf, ast_channel_name(chan)); + return 0; } - ret = buf; - } else if (!strcasecmp("duration", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) { - double hrtime; - - if(!ast_tvzero(cdr->end)) - hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0); - else - hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->start) / 1000000.0); - - snprintf(buf, len, "%lf", hrtime); - - if (!ast_strlen_zero(buf)) { - ret = buf; + dtime = (double)(ms / 1000.0); + sprintf(tempbuf, "%lf", dtime); + } else if (!ast_test_flag(&flags, OPT_UNPARSED)) { + if (!strcasecmp("start", args.variable) + || !strcasecmp("end", args.variable) + || !strcasecmp("answer", args.variable)) { + struct timeval fmt_time; + struct ast_tm tm; + if (sscanf(tempbuf, "%ld.%ld", &fmt_time.tv_sec, &fmt_time.tv_usec) != 2) { + ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n", + args.variable, tempbuf, ast_channel_name(chan)); + return 0; + } + ast_localtime(&fmt_time, &tm, NULL); + ast_strftime(tempbuf, sizeof(*tempbuf), "%Y-%m-%d %T", &tm); + } else if (!strcasecmp("disposition", args.variable)) { + int disposition; + if (sscanf(tempbuf, "%8d", &disposition) != 1) { + ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n", + args.variable, tempbuf, ast_channel_name(chan)); + return 0; + } + sprintf(format_buf, "%s", ast_cdr_disp2str(disposition)); + strcpy(tempbuf, format_buf); + } else if (!strcasecmp("amaflags", args.variable)) { + int amaflags; + if (sscanf(tempbuf, "%8d", &amaflags) != 1) { + ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n", + args.variable, tempbuf, ast_channel_name(chan)); + return 0; + } + sprintf(format_buf, "%s", ast_channel_amaflags2string(amaflags)); + strcpy(tempbuf, format_buf); } - } else { - ast_cdr_getvar(cdr, args.variable, &ret, buf, len, - ast_test_flag(&flags, OPT_RECURSIVE), - ast_test_flag(&flags, OPT_UNPARSED)); } - ast_channel_unlock(chan); - return ret ? 0 : -1; + ast_copy_string(buf, tempbuf, len); + return 0; } static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse, const char *value) { - struct ast_cdr *cdr; struct ast_flags flags = { 0 }; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(variable); @@ -278,36 +292,59 @@ static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse, if (ast_strlen_zero(parse) || !value || !chan) return -1; - ast_channel_lock(chan); - cdr = ast_channel_cdr(chan); - if (!cdr) { - ast_channel_unlock(chan); - return -1; - } - AST_STANDARD_APP_ARGS(args, parse); if (!ast_strlen_zero(args.options)) ast_app_parse_options(cdr_func_options, &flags, NULL, args.options); - if (ast_test_flag(&flags, OPT_LAST)) - while (cdr->next) - cdr = cdr->next; - - if (!strcasecmp(args.variable, "accountcode")) /* the 'l' flag doesn't apply to setting the accountcode, userfield, or amaflags */ - ast_cdr_setaccount(chan, value); - else if (!strcasecmp(args.variable, "peeraccount")) - ast_cdr_setpeeraccount(chan, value); - else if (!strcasecmp(args.variable, "userfield")) - ast_cdr_setuserfield(chan, value); - else if (!strcasecmp(args.variable, "amaflags")) - ast_cdr_setamaflags(chan, value); - else - ast_cdr_setvar(cdr, args.variable, value, ast_test_flag(&flags, OPT_RECURSIVE)); - /* No need to worry about the u flag, as all fields for which setting - * 'u' would do anything are marked as readonly. */ - - ast_channel_unlock(chan); + if (!strcasecmp(args.variable, "accountcode")) { + ast_log(AST_LOG_WARNING, "Using the CDR function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n"); + ast_channel_lock(chan); + ast_channel_accountcode_set(chan, value); + ast_channel_unlock(chan); + } else if (!strcasecmp(args.variable, "peeraccount")) { + ast_log(AST_LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n"); + } else if (!strcasecmp(args.variable, "userfield")) { + ast_cdr_setuserfield(ast_channel_name(chan), value); + } else if (!strcasecmp(args.variable, "amaflags")) { + ast_log(AST_LOG_WARNING, "Using the CDR function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n"); + if (isdigit(*value)) { + int amaflags; + sscanf(value, "%30d", &amaflags); + ast_channel_lock(chan); + ast_channel_amaflags_set(chan, amaflags); + ast_channel_unlock(chan); + } else { + ast_channel_lock(chan); + ast_channel_amaflags_set(chan, ast_channel_string2amaflag(value)); + ast_channel_unlock(chan); + } + } else { + ast_cdr_setvar(ast_channel_name(chan), args.variable, value); + } + + return 0; +} + +static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse, + const char *value) +{ + enum ast_cdr_options option; + + if (!strcasecmp("party_a", cmd)) { + option = AST_CDR_FLAG_PARTY_A; + } else if (!strcasecmp("disable", cmd)) { + option = AST_CDR_FLAG_DISABLE_ALL; + } else { + ast_log(AST_LOG_WARNING, "Unknown option %s used with CDR_PROP\n", cmd); + return 0; + } + + if (ast_true(value)) { + ast_cdr_set_property(ast_channel_name(chan), option); + } else { + ast_cdr_clear_property(ast_channel_name(chan), option); + } return 0; } @@ -317,14 +354,30 @@ static struct ast_custom_function cdr_function = { .write = cdr_write, }; +static struct ast_custom_function cdr_prop_function = { + .name = "CDR_PROP", + .read = NULL, + .write = cdr_prop_write, +}; + static int unload_module(void) { - return ast_custom_function_unregister(&cdr_function); + int res = 0; + + res |= ast_custom_function_unregister(&cdr_function); + res |= ast_custom_function_unregister(&cdr_prop_function); + + return res; } static int load_module(void) { - return ast_custom_function_register(&cdr_function); + int res = 0; + + res |= ast_custom_function_register(&cdr_function); + res |= ast_custom_function_register(&cdr_prop_function); + + return res; } -AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan function"); +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan functions"); diff --git a/funcs/func_channel.c b/funcs/func_channel.c index 93792440074295aebeb9082f5348d69ecd1a89d5..4327fd257b179bf4124ed901e24458bf9da6bf25 100644 --- a/funcs/func_channel.c +++ b/funcs/func_channel.c @@ -343,13 +343,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </function> ***/ -/* - * BUGBUG add CHANNEL(after_bridge_goto)=<parseable-goto> Sets an after bridge goto datastore property on the channel. - * CHANNEL(after_bridge_goto)=<empty> Deletes any after bridge goto datastore property on the channel. - * - * BUGBUG add CHANNEL(dtmf_features)=tkhwx sets channel dtmf features to specified. (transfer, park, hangup, monitor, mixmonitor) - */ - #define locked_copy_string(chan, dest, source, len) \ do { \ ast_channel_lock(chan); \ @@ -450,7 +443,7 @@ static int func_channel_read(struct ast_channel *chan, const char *function, ast_channel_lock(chan); p = ast_bridged_channel(chan); - if (p || ast_channel_tech(chan) || ast_channel_cdr(chan)) /* dummy channel? if so, we hid the peer name in the language */ + if (p || ast_channel_tech(chan)) /* dummy channel? if so, we hid the peer name in the language */ ast_copy_string(buf, (p ? ast_channel_name(p) : ""), len); else { /* a dummy channel can still pass along bridged peer info via @@ -525,7 +518,7 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio locked_string_field_set(chan, userfield, value); else if (!strcasecmp(data, "amaflags")) { ast_channel_lock(chan); - if(isdigit(*value)) { + if (isdigit(*value)) { int amaflags; sscanf(value, "%30d", &amaflags); ast_channel_amaflags_set(chan, amaflags); diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h index 9d3f5b3cf21fd903393993a08d7f5d1a78720780..0bfcc325495f3d47dc151e05bde1328241f64cfe 100644 --- a/include/asterisk/bridging.h +++ b/include/asterisk/bridging.h @@ -1031,6 +1031,37 @@ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast */ int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action); +/*! + * \brief Update the linkedid for all channels in a bridge + * \since 12.0.0 + * + * \param bridge The bridge to update the linkedids on + * \param bridge_channel The channel joining the bridge + * \param swap The channel being swapped out of the bridge. May be NULL. + * + * \note The bridge must be locked prior to calling this function. + * \note This API call is meant for internal bridging operations. + */ +void ast_bridge_update_linkedids(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap); + +/*! + * \brief Update the accountcodes for a channel entering a bridge + * \since 12.0.0 + * + * This function updates the accountcode and peeraccount on channels in two-party + * bridges. In multi-party bridges, peeraccount is not set - it doesn't make much sense - + * however accountcode propagation will still occur if the channel joining has an + * accountcode. + * + * \param bridge The bridge to update the accountcodes in + * \param bridge_channel The channel joining the bridge + * \param swap The channel being swapped out of the bridge. May be NULL. + * + * \note The bridge must be locked prior to calling this function. + * \note This API call is meant for internal bridging operations. + */ +void ast_bridge_update_accountcodes(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap); + /*! * \brief Write a frame to the specified bridge_channel. * \since 12.0.0 diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h index c2268e312148c959dfdbcb114eb67cdee2ec13fe..548ca994dee702ed9fa5d4182cf79a568d7e3983 100644 --- a/include/asterisk/cdr.h +++ b/include/asterisk/cdr.h @@ -26,33 +26,216 @@ #ifndef _ASTERISK_CDR_H #define _ASTERISK_CDR_H -#include <sys/time.h> +#include "asterisk/channel.h" + +/*! \file + * + * \since 12 + * + * \brief Call Detail Record Engine. + * + * \page CDR Call Detail Record Engine + * + * \par Intro + * + * The Call Detail Record (CDR) engine uses the \ref stasis Stasis Message Bus + * to build records for the channels in Asterisk. As the state of a channel and + * the bridges it participates in changes, notifications are sent over the + * Stasis Message Bus. The CDR engine consumes these notifications and builds + * records that reflect that state. Over the lifetime of a channel, many CDRs + * may be generated for that channel or that involve that channel. + * + * CDRs have a lifecycle that is a subset of the channel that they reflect. A + * single CDR for a channel represents a path of communication between the + * endpoint behind a channel and Asterisk, or between two endpoints. When a + * channel establishes a new path of communication, a new CDR is created for the + * channel. Likewise, when a path of communication is terminated, a CDR is + * finalized. Finally, when a channel is no longer present in Asterisk, all CDRs + * for that channel are dispatched for recording. + * + * Dispatching of CDRs occurs to registered CDR backends. CDR backends register + * through \ref ast_cdr_register and are responsible for taking the produced + * CDRs and storing them in permanent storage. + * + * \par CDR attributes + * + * While a CDR can have many attributes, all CDRs have two parties: a Party A + * and a Party B. The Party A is \em always the channel that owns the CDR. A CDR + * may or may not have a Party B, depending on its state. + * + * For the most part, attributes on a CDR are reflective of those same + * attributes on the channel at the time when the CDR was finalized. Specific + * CDR attributes include: + * \li \c start The time when the CDR was created + * \li \c answer The time when the Party A was answered, or when the path of + * communication between Party A and Party B was established + * \li \c end The time when the CDR was finalized + * \li \c duration \c end - \c start. If \c end is not known, the current time + * is used + * \li \c billsec \c end - \c answer. If \c end is not known, the current time + * is used + * \li \c userfield User set data on some party in the CDR + * + * Note that \c accountcode and \c amaflags are actually properties of a + * channel, not the CDR. + * + * \par CDR States + * + * CDRs go through various states during their lifetime. State transitions occur + * due to messages received over the \ref stasis Stasis Message Bus. The + * following describes the possible states a CDR can be in, and how it + * transitions through the states. + * + * \par Single + * + * When a CDR is created, it is put into the Single state. The Single state + * represents a CDR for a channel that has no Party B. CDRs can be unanswered + * or answered while in the Single state. + * + * The following transitions can occur while in the Single state: + * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, the + * state transitions to Dial + * \li If a \ref ast_channel_snapshot is received indicating that the channel + * has hung up, the state transitions to Finalized + * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the + * state transitions to Bridge + * + * \par Dial + * + * This state represents a dial that is occurring within Asterisk. The Party A + * can either be the caller for a two party dial, or it can be the dialed party + * if the calling party is Asterisk (that is, an Originated channel). In the + * first case, the Party B is \em always the dialed channel; in the second case, + * the channel is not considered to be a "dialed" channel as it is alone in the + * dialed operation. + * + * While in the Dial state, multiple CDRs can be created for the Party A if a + * parallel dial occurs. Each dialed party receives its own CDR with Party A. + * + * The following transitions can occur while in the Dial state: + * \li If a \ref ast_channel_dial_type indicating a Dial End is received where + * the \ref dial_status is not ANSWER, the state transitions to Finalized + * \li If a \ref ast_channel_snapshot is received indicating that the channel + * has hung up, the state transitions to Finalized + * \li If a \ref ast_channel_dial_type indicating a Dial End is received where + * the \ref dial_status is ANSWER, the state transitions to DialedPending + * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the + * state transitions to Bridge + * + * \par DialedPending + * + * Technically, after being dialed, a CDR does not have to transition to the + * Bridge state. If the channel being dialed was originated, the channel may + * being executing dialplan. Strangely enough, it is also valid to have both + * Party A and Party B - after a dial - to not be bridged and instead execute + * dialplan. DialedPending handles the state where we figure out if the CDR + * showing the dial needs to move to the Bridge state; if the CDR should show + * that we started executing dialplan; of if we need a new CDR. + * + * The following transition can occur while in the DialedPending state: + * \li If a \ref ast_channel_snapshot is received that indicates that the + * channel has begun executing dialplan, we transition to the Finalized state + * if we have a Party B. Otherwise, we transition to the Single state. + * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the + * state transitions to Bridge (through the Dial state) + * + * \par Bridge + * + * The Bridge state represents a path of communication between Party A and one + * or more other parties. When a CDR enters into the Bridge state, the following + * occurs: + * \li The CDR attempts to find a Party B. If the CDR has a Party B, it looks + * for that channel in the bridge and updates itself accordingly. If the CDR + * does not yet have a Party B, it attempts to find a channel that can be its + * Party B. If it finds one, it updates itself; otherwise, the CDR is + * temporarily finalized. + * \li Once the CDR has a Party B or it is determined that it cannot have a + * Party B, new CDRs are created for each pairing of channels with the CDR's + * Party A. + * + * As an example, consider the following: + * \li A Dials B - both answer + * \li B joins a bridge. Since no one is in the bridge and it was a dialed + * channel, it cannot have a Party B. + * \li A joins the bridge. Since A's Party B is B, A updates itself with B. + * \li Now say an Originated channel, C, joins the bridge. The bridge becomes + * a multi-party bridge. + * \li C attempts to get a Party B. A cannot be C's Party B, as it was created + * before it. B is a dialed channel and can thus be C's Party B, so C's CDR + * updates its Party B to B. + * \li New CDRs are now generated. A gets a new CDR for A -> C. B is dialed, and + * hence cannot get any CDR. + * \li Now say another Originated channel, D, joins the bridge. Say D has the + * \ref party_a flag set on it, such that it is always the preferred Party A. + * As such, it takes A as its Party B. + * \li New CDRs are generated. D gets new CDRs for D -> B and D -> C. + * + * The following transitions can occur while in the Bridge state: + * \li If a \ref ast_bridge_blob_type message indicating a leave is received, + * the state transitions to the Pending state + * + * \par Pending + * + * After a channel leaves a bridge, we often don't know what's going to happen + * to it. It can enter another bridge; it can be hung up; it can continue on + * in the dialplan. It can even enter into limbo! Pending holds the state of the + * CDR until we get a subsequent Stasis message telling us what should happen. + * + * The following transitions can occur while in the Pending state: + * \li If a \ref ast_bridge_blob_type message is received, a new CDR is created + * and it is transitioned to the Bridge state + * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, a + * new CDR is created and it is transitioned to the Dial state + * \li If a \ref ast_channel_cache_update is received indicating a change in + * Context/Extension/Priority, a new CDR is created and transitioned to the + * Single state. If the update indicates that the party has been hung up, the + * CDR is transitioned to the Finalized state. + * + * \par Finalized + * + * Once a CDR enters the finalized state, it is finished. No further updates + * can be made to the party information, and the CDR cannot be changed. + * + * One exception to this occurs during linkedid propagation, in which the CDRs + * linkedids are updated based on who the channel is bridged with. In general, + * however, a finalized CDR is waiting for dispatch to the CDR backends. + */ + +/*! \brief CDR engine settings */ +enum ast_cdr_settings { + CDR_ENABLED = 1 << 0, /*< Enable CDRs */ + CDR_BATCHMODE = 1 << 1, /*< Whether or not we should dispatch CDRs in batches */ + CDR_UNANSWERED = 1 << 2, /*< Log unanswered CDRs */ + CDR_CONGESTION = 1 << 3, /*< Treat congestion as if it were a failed call */ + CDR_END_BEFORE_H_EXTEN = 1 << 4, /*< End the CDR before the 'h' extension runs */ + CDR_INITIATED_SECONDS = 1 << 5, /*< Include microseconds into the billing time */ + CDR_DEBUG = 1 << 6, /*< Enables extra debug statements */ +}; -#include "asterisk/data.h" +/*! \brief CDR Batch Mode settings */ +enum ast_cdr_batch_mode_settings { + BATCH_MODE_SCHEDULER_ONLY = 1 << 0, /*< Don't spawn a thread to handle the batches - do it on the scheduler */ + BATCH_MODE_SAFE_SHUTDOWN = 1 << 1, /*< During safe shutdown, submit the batched CDRs */ +}; /*! - * \brief CDR Flags + * \brief CDR manipulation options. Certain function calls will manipulate the + * state of a CDR object based on these flags. */ -enum { - AST_CDR_FLAG_KEEP_VARS = (1 << 0), - AST_CDR_FLAG_POSTED = (1 << 1), - AST_CDR_FLAG_LOCKED = (1 << 2), - AST_CDR_FLAG_CHILD = (1 << 3), - AST_CDR_FLAG_POST_DISABLED = (1 << 4), - AST_CDR_FLAG_BRIDGED = (1 << 5), - AST_CDR_FLAG_MAIN = (1 << 6), - AST_CDR_FLAG_ENABLE = (1 << 7), - AST_CDR_FLAG_ANSLOCKED = (1 << 8), - AST_CDR_FLAG_DONT_TOUCH = (1 << 9), - AST_CDR_FLAG_POST_ENABLE = (1 << 10), - AST_CDR_FLAG_DIALED = (1 << 11), - AST_CDR_FLAG_ORIGINATED = (1 << 12), +enum ast_cdr_options { + AST_CDR_FLAG_KEEP_VARS = (1 << 0), /*< Copy variables during the operation */ + AST_CDR_FLAG_DISABLE = (1 << 1), /*< Disable the current CDR */ + AST_CDR_FLAG_DISABLE_ALL = (3 << 1), /*< Disable the CDR and all future CDRs */ + AST_CDR_FLAG_PARTY_A = (1 << 3), /*< Set the channel as party A */ + AST_CDR_FLAG_FINALIZE = (1 << 4), /*< Finalize the current CDRs */ + AST_CDR_FLAG_SET_ANSWER = (1 << 5), /*< If the channel is answered, set the answer time to now */ + AST_CDR_FLAG_RESET = (1 << 6), /*< If set, set the start and answer time to now */ }; /*! * \brief CDR Flags - Disposition */ -enum { +enum ast_cdr_disposition { AST_CDR_NOANSWER = 0, AST_CDR_NULL = (1 << 0), AST_CDR_FAILED = (1 << 1), @@ -61,21 +244,16 @@ enum { AST_CDR_CONGESTION = (1 << 4), }; -/*! - * \brief CDR AMA Flags - */ -enum { - AST_CDR_OMIT = 1, - AST_CDR_BILLING = 2, - AST_CDR_DOCUMENTATION = 3, -}; - -#define AST_MAX_USER_FIELD 256 -#define AST_MAX_ACCOUNT_CODE 20 -/* Include channel.h after relevant declarations it will need */ -#include "asterisk/channel.h" -#include "asterisk/utils.h" +/*! \brief The global options available for CDRs */ +struct ast_cdr_config { + struct ast_flags settings; /*< CDR settings */ + struct batch_settings { + unsigned int time; /*< Time between batches */ + unsigned int size; /*< Size to trigger a batch */ + struct ast_flags settings; /*< Settings for batches */ + } batch_settings; +}; /*! * \brief Responsible for call detail data @@ -133,249 +311,186 @@ struct ast_cdr { struct ast_cdr *next; }; -int ast_cdr_isset_unanswered(void); -int ast_cdr_isset_congestion(void); -void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw); -int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur); -int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur); -void ast_cdr_free_vars(struct ast_cdr *cdr, int recur); -int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr); - -/*! - * \brief CDR backend callback - * \warning CDR backends should NOT attempt to access the channel associated - * with a CDR record. This channel is not guaranteed to exist when the CDR - * backend is invoked. - */ -typedef int (*ast_cdrbe)(struct ast_cdr *cdr); - -/*! \brief Return TRUE if CDR subsystem is enabled */ -int check_cdr_enabled(void); - /*! - * \brief Allocate a CDR record - * \retval a malloc'd ast_cdr structure - * \retval NULL on error (malloc failure) - */ -struct ast_cdr *ast_cdr_alloc(void); - -/*! - * \brief Duplicate a record and increment the sequence number. - * \param cdr the record to duplicate - * \retval a malloc'd ast_cdr structure, - * \retval NULL on error (malloc failure) - * \see ast_cdr_dup() - * \see ast_cdr_dup_unique_swap() - */ -struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr); - -/*! - * \brief Duplicate a record and increment the sequence number of the old - * record. - * \param cdr the record to duplicate - * \retval a malloc'd ast_cdr structure, - * \retval NULL on error (malloc failure) - * \note This version increments the original CDR's sequence number rather than - * the duplicate's sequence number. The effect is as if the original CDR's - * sequence number was swapped with the duplicate's sequence number. + * \since 12 + * \brief Obtain the current CDR configuration * - * \see ast_cdr_dup() - * \see ast_cdr_dup_unique() - */ -struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr); - -/*! - * \brief Duplicate a record - * \param cdr the record to duplicate - * \retval a malloc'd ast_cdr structure, - * \retval NULL on error (malloc failure) - * \see ast_cdr_dup_unique() - * \see ast_cdr_dup_unique_swap() - */ -struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr); - -/*! - * \brief Free a CDR record - * \param cdr ast_cdr structure to free - * Returns nothing - */ -void ast_cdr_free(struct ast_cdr *cdr); - -/*! - * \brief Discard and free a CDR record - * \param cdr ast_cdr structure to free - * Returns nothing -- same as free, but no checks or complaints + * The configuration is a ref counted object. The caller of this function must + * decrement the ref count when finished with the configuration. + * + * \retval NULL on error + * \retval The current CDR configuration */ -void ast_cdr_discard(struct ast_cdr *cdr); +struct ast_cdr_config *ast_cdr_get_config(void); /*! - * \brief Initialize based on a channel - * \param cdr Call Detail Record to use for channel - * \param chan Channel to bind CDR with - * Initializes a CDR and associates it with a particular channel - * \note The channel should be locked before calling. - * \return 0 by default + * \since 12 + * \brief Set the current CDR configuration + * + * \param config The new CDR configuration */ -int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *chan); +void ast_cdr_set_config(struct ast_cdr_config *config); /*! - * \brief Initialize based on a channel - * \param cdr Call Detail Record to use for channel - * \param chan Channel to bind CDR with - * Initializes a CDR and associates it with a particular channel - * \note The channel should be locked before calling. - * \return 0 by default + * \since 12 + * \brief Format a CDR variable from an already posted CDR + * + * \param cdr The dispatched CDR to process + * \param name The name of the variable + * \param ret Pointer to the formatted buffer + * \param workspace A pointer to the buffer to use to format the variable + * \param workspacelen The size of \ref workspace + * \param raw If non-zero and a date/time is extraced, provide epoch seconds. Otherwise format as a date/time stamp */ -int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *chan); +void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw); /*! - * \brief Register a CDR handling engine - * \param name name associated with the particular CDR handler - * \param desc description of the CDR handler - * \param be function pointer to a CDR handler - * Used to register a Call Detail Record handler. - * \retval 0 on success. - * \retval -1 on error + * \since 12 + * \brief Retrieve a CDR variable from a channel's current CDR + * + * \param channel_name The name of the party A channel that the CDR is associated with + * \param name The name of the variable to retrieve + * \param value Buffer to hold the value + * \param length The size of the buffer + * + * \retval 0 on success + * \retval non-zero on failure */ -int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be); +int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length); /*! - * \brief Unregister a CDR handling engine - * \param name name of CDR handler to unregister - * Unregisters a CDR by it's name + * \since 12 + * \brief Set a variable on a CDR + * + * \param channel_name The channel to set the variable on + * \param name The name of the variable to set + * \param value The value of the variable to set + * + * \retval 0 on success + * \retval non-zero on failure */ -void ast_cdr_unregister(const char *name); +int ast_cdr_setvar(const char *channel_name, const char *name, const char *value); /*! - * \brief Start a call - * \param cdr the cdr you wish to associate with the call - * Starts all CDR stuff necessary for monitoring a call - * Returns nothing - */ -void ast_cdr_start(struct ast_cdr *cdr); - -/*! \brief Answer a call - * \param cdr the cdr you wish to associate with the call - * Starts all CDR stuff necessary for doing CDR when answering a call - * \note NULL argument is just fine. + * \since 12 + * \brief Fork a CDR + * + * \param channel_name The name of the channel whose CDR should be forked + * \param options Options to control how the fork occurs. + * + * \retval 0 on success + * \retval -1 on failure */ -void ast_cdr_answer(struct ast_cdr *cdr); +int ast_cdr_fork(const char *channel_name, struct ast_flags *options); /*! - * \brief A call wasn't answered - * \param cdr the cdr you wish to associate with the call - * Marks the channel disposition as "NO ANSWER" - * Will skip CDR's in chain with ANS_LOCK bit set. (see - * forkCDR() application. + * \since 12 + * \brief Set a property on a CDR for a channel + * + * This function sets specific administrative properties on a CDR for a channel. + * This includes properties like preventing a CDR from being dispatched, to + * setting the channel as the preferred Party A in future CDRs. See + * \ref enum ast_cdr_options for more information. + * + * \param channel_name The CDR's channel + * \param option Option to apply to the CDR + * + * \retval 0 on success + * \retval 1 on error */ -extern void ast_cdr_noanswer(struct ast_cdr *cdr); +int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option); /*! - * \brief A call was set to congestion - * \param cdr the cdr you wish to associate with the call - * Markst he channel disposition as "CONGESTION" - * Will skip CDR's in chain with ANS_LOCK bit set. (see - * forkCDR() application + * \since 12 + * \brief Clear a property on a CDR for a channel + * + * Clears a flag previously set by \ref ast_cdr_set_property + * + * \param channel_name The CDR's channel + * \param option Option to clear from the CDR + * + * \retval 0 on success + * \retval 1 on error */ -extern void ast_cdr_congestion(struct ast_cdr *cdr); +int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option); /*! - * \brief Busy a call - * \param cdr the cdr you wish to associate with the call - * Marks the channel disposition as "BUSY" - * Will skip CDR's in chain with ANS_LOCK bit set. (see - * forkCDR() application. - * Returns nothing + * \brief Reset the detail record + * \param channel_name The channel that the CDR is associated with + * \param options Options that control what the reset operation does. + * + * Valid options are: + * \ref AST_CDR_FLAG_KEEP_VARS - keep the variables during the reset + * \ref AST_CDR_FLAG_DISABLE_ALL - when used with \ref ast_cdr_reset, re-enables + * the CDR + * + * \retval 0 on success + * \retval -1 on failure */ -void ast_cdr_busy(struct ast_cdr *cdr); +int ast_cdr_reset(const char *channel_name, struct ast_flags *options); /*! - * \brief Fail a call - * \param cdr the cdr you wish to associate with the call - * Marks the channel disposition as "FAILED" - * Will skip CDR's in chain with ANS_LOCK bit set. (see - * forkCDR() application. - * Returns nothing + * \brief Serializes all the data and variables for a current CDR record + * \param channel_name The channel to get the CDR for + * \param buf A buffer to use for formatting the data + * \param delim A delimeter to use to separate variable keys/values + * \param sep A separator to use between nestings + * \retval the total number of serialized variables */ -void ast_cdr_failed(struct ast_cdr *cdr); +int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep); /*! - * \brief Save the result of the call based on the AST_CAUSE_* - * \param cdr the cdr you wish to associate with the call - * \param cause the AST_CAUSE_* - * Returns nothing + * \brief CDR backend callback + * \warning CDR backends should NOT attempt to access the channel associated + * with a CDR record. This channel is not guaranteed to exist when the CDR + * backend is invoked. */ -int ast_cdr_disposition(struct ast_cdr *cdr, int cause); +typedef int (*ast_cdrbe)(struct ast_cdr *cdr); -/*! - * \brief End a call - * \param cdr the cdr you have associated the call with - * Registers the end of call time in the cdr structure. - * Returns nothing - */ -void ast_cdr_end(struct ast_cdr *cdr); +/*! \brief Return TRUE if CDR subsystem is enabled */ +int ast_cdr_is_enabled(void); /*! - * \brief Detaches the detail record for posting (and freeing) either now or at a - * later time in bulk with other records during batch mode operation. - * \param cdr Which CDR to detach from the channel thread - * Prevents the channel thread from blocking on the CDR handling - * Returns nothing + * \brief Allocate a CDR record + * \retval a malloc'd ast_cdr structure + * \retval NULL on error (malloc failure) */ -void ast_cdr_detach(struct ast_cdr *cdr); +struct ast_cdr *ast_cdr_alloc(void); -/*! - * \brief Spawns (possibly) a new thread to submit a batch of CDRs to the backend engines - * \param shutdown Whether or not we are shutting down - * Blocks the asterisk shutdown procedures until the CDR data is submitted. - * Returns nothing - */ -void ast_cdr_submit_batch(int shutdown); /*! - * \brief Set the destination channel, if there was one - * \param cdr Which cdr it's applied to - * \param chan Channel to which dest will be - * Sets the destination channel the CDR is applied to - * Returns nothing + * \brief Duplicate a public CDR + * \param cdr the record to duplicate + * + * \retval a malloc'd ast_cdr structure, + * \retval NULL on error (malloc failure) */ -void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chan); +struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr); /*! - * \brief Set the last executed application - * \param cdr which cdr to act upon - * \param app the name of the app you wish to change it to - * \param data the data you want in the data field of app you set it to - * Changes the value of the last executed app + * \brief Free a CDR record + * \param cdr ast_cdr structure to free * Returns nothing */ -void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data); - -/*! - * \brief Set the answer time for a call - * \param cdr the cdr you wish to associate with the call - * \param t the answer time - * Starts all CDR stuff necessary for doing CDR when answering a call - * NULL argument is just fine. - */ -void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t); +void ast_cdr_free(struct ast_cdr *cdr); /*! - * \brief Set the disposition for a call - * \param cdr the cdr you wish to associate with the call - * \param disposition the new disposition - * Set the disposition on a call. - * NULL argument is just fine. + * \brief Register a CDR handling engine + * \param name name associated with the particular CDR handler + * \param desc description of the CDR handler + * \param be function pointer to a CDR handler + * Used to register a Call Detail Record handler. + * \retval 0 on success. + * \retval -1 on error */ -void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition); +int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be); /*! - * \brief Convert a string to a detail record AMA flag - * \param flag string form of flag - * Converts the string form of the flag to the binary form. - * \return the binary form of the flag + * \brief Unregister a CDR handling engine + * \param name name of CDR handler to unregister + * Unregisters a CDR by it's name */ -int ast_cdr_amaflags2int(const char *flag); +void ast_cdr_unregister(const char *name); /*! * \brief Disposition to a string @@ -383,81 +498,15 @@ int ast_cdr_amaflags2int(const char *flag); * Converts the binary form of a disposition to string form. * \return a pointer to the string form */ -char *ast_cdr_disp2str(int disposition); - -/*! - * \brief Reset the detail record, optionally posting it first - * \param cdr which cdr to act upon - * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it - * |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's - */ -void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *flags); - -/*! Reset the detail record times, flags */ -/*! - * \param cdr which cdr to act upon - * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it - * |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's - */ -void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *flags); - -/*! Flags to a string */ -/*! - * \param flags binary flag - * Converts binary flags to string flags - * Returns string with flag name - */ -char *ast_cdr_flags2str(int flags); - -/*! - * \brief Move the non-null data from the "from" cdr to the "to" cdr - * \param to the cdr to get the goodies - * \param from the cdr to give the goodies - */ -void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from); - -/*! - * \brief Set account code, will generate AMI event - * \note The channel should be locked before calling. - */ -int ast_cdr_setaccount(struct ast_channel *chan, const char *account); - -/*! - * \brief Set the peer account - * \note The channel should be locked before calling. - */ -int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account); - -/*! - * \brief Set AMA flags for channel - * \note The channel should be locked before calling. - */ -int ast_cdr_setamaflags(struct ast_channel *chan, const char *amaflags); +const char *ast_cdr_disp2str(int disposition); /*! * \brief Set CDR user field for channel (stored in CDR) - * \note The channel should be locked before calling. - */ -int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield); -/*! - * \brief Append to CDR user field for channel (stored in CDR) - * \note The channel should be locked before calling. - */ -int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield); - - -/*! - * \brief Update CDR on a channel - * \note The channel should be locked before calling. + * + * \param channel_name The name of the channel that owns the CDR + * \param userfield The user field to set */ -int ast_cdr_update(struct ast_channel *chan); - - -extern int ast_default_amaflags; - -extern char ast_default_accountcode[AST_MAX_ACCOUNT_CODE]; - -struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr); +void ast_cdr_setuserfield(const char *channel_name, const char *userfield); /*! \brief Reload the configuration file cdr.conf and start/stop CDR scheduling thread */ int ast_cdr_engine_reload(void); @@ -468,14 +517,4 @@ int ast_cdr_engine_init(void); /*! Submit any remaining CDRs and prepare for shutdown */ void ast_cdr_engine_term(void); -/*! - * \brief - * \param[in] tree Where to insert the cdr. - * \param[in] cdr The cdr structure to insert in 'tree'. - * \param[in] recur Go throw all the cdr levels. - * \retval <0 on error. - * \retval 0 on success. - */ -int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur); - #endif /* _ASTERISK_CDR_H */ diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h index 034a96ab9bb48cfa421871988b48fc93ca5e9ecd..914037d4c164c75e16967d20064320419b6b0f9d 100644 --- a/include/asterisk/cel.h +++ b/include/asterisk/cel.h @@ -35,20 +35,6 @@ extern "C" { #include "asterisk/event.h" -/*! - * \brief AMA Flags - * - * \note This must much up with the AST_CDR_* defines for AMA flags. - */ -enum ast_cel_ama_flag { - AST_CEL_AMA_FLAG_NONE, - AST_CEL_AMA_FLAG_OMIT, - AST_CEL_AMA_FLAG_BILLING, - AST_CEL_AMA_FLAG_DOCUMENTATION, - /*! \brief Must be final entry */ - AST_CEL_AMA_FLAG_TOTAL, -}; - /*! * \brief CEL event types */ @@ -162,17 +148,6 @@ const char *ast_cel_get_type_name(enum ast_cel_event_type type); */ enum ast_cel_event_type ast_cel_str_to_event_type(const char *name); -/*! - * \brief Convert AMA flag to printable string - * - * \param[in] flag the flag to convert to a string - * - * \since 1.8 - * - * \return the string representation of the flag - */ -const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag); - /*! * \brief Check and potentially retire a Linked ID * diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 42d50f21bf599505ae2b5bc49cd6a18ef745a80a..1b36c14ae30b2003127888dd6b2256732cdd0a20 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -131,11 +131,13 @@ References: extern "C" { #endif -#define AST_MAX_EXTENSION 80 /*!< Max length of an extension */ -#define AST_MAX_CONTEXT 80 /*!< Max length of a context */ -#define AST_CHANNEL_NAME 80 /*!< Max length of an ast_channel name */ -#define MAX_LANGUAGE 40 /*!< Max length of the language setting */ -#define MAX_MUSICCLASS 80 /*!< Max length of the music class setting */ +#define AST_MAX_EXTENSION 80 /*!< Max length of an extension */ +#define AST_MAX_CONTEXT 80 /*!< Max length of a context */ +#define AST_MAX_ACCOUNT_CODE 20 /*!< Max length of an account code */ +#define AST_CHANNEL_NAME 80 /*!< Max length of an ast_channel name */ +#define MAX_LANGUAGE 40 /*!< Max length of the language setting */ +#define MAX_MUSICCLASS 80 /*!< Max length of the music class setting */ +#define AST_MAX_USER_FIELD 256 /*!< Max length of the channel user field */ #include "asterisk/frame.h" #include "asterisk/chanvars.h" @@ -915,6 +917,10 @@ enum { * to continue. */ AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT = (1 << 22), + /*! + * This flag indicates that the channel was originated. + */ + AST_FLAG_ORIGINATED = (1 << 23), }; /*! \brief ast_bridge_config flags */ @@ -1027,6 +1033,16 @@ enum channelreloadreason { CHANNEL_ACL_RELOAD, }; +/*! + * \brief Channel AMA Flags + */ +enum ama_flags { + AST_AMA_NONE = 0, + AST_AMA_OMIT, + AST_AMA_BILLING, + AST_AMA_DOCUMENTATION, +}; + /*! * \note None of the datastore API calls lock the ast_channel they are using. * So, the channel should be locked before calling the functions that @@ -1100,7 +1116,7 @@ struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 14))) __ast_channel_alloc(int needqueue, int state, const char *cid_num, const char *cid_name, const char *acctcode, const char *exten, const char *context, - const char *linkedid, const int amaflag, + const char *linkedid, enum ama_flags amaflag, const char *file, int line, const char *function, const char *name_fmt, ...); @@ -1591,7 +1607,6 @@ int ast_answer(struct ast_channel *chan); * \brief Answer a channel * * \param chan channel to answer - * \param cdr_answer flag to control whether any associated CDR should be marked as 'answered' * * This function answers a channel and handles all necessary call * setup functions. @@ -1607,14 +1622,13 @@ int ast_answer(struct ast_channel *chan); * \retval 0 on success * \retval non-zero on failure */ -int ast_raw_answer(struct ast_channel *chan, int cdr_answer); +int ast_raw_answer(struct ast_channel *chan); /*! * \brief Answer a channel, with a selectable delay before returning * * \param chan channel to answer * \param delay maximum amount of time to wait for incoming media - * \param cdr_answer flag to control whether any associated CDR should be marked as 'answered' * * This function answers a channel and handles all necessary call * setup functions. @@ -1630,7 +1644,7 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer); * \retval 0 on success * \retval non-zero on failure */ -int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer); +int __ast_answer(struct ast_channel *chan, unsigned int delay); /*! * \brief Execute a Gosub call on the channel before a call is placed. @@ -2196,6 +2210,28 @@ int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen, /*! Deactivate an active generator */ void ast_deactivate_generator(struct ast_channel *chan); +/*! + * \since 12 + * \brief Obtain how long the channel since the channel was created + * + * \param chan The channel object + * + * \retval 0 if the time value cannot be computed (or you called this really fast) + * \retval The number of seconds the channel has been up + */ +int ast_channel_get_duration(struct ast_channel *chan); + +/*! + * \since 12 + * \brief Obtain how long it has been since the channel was answered + * + * \param chan The channel object + * + * \retval 0 if the channel isn't answered (or you called this really fast) + * \retval The number of seconds the channel has been up + */ +int ast_channel_get_up_time(struct ast_channel *chan); + /*! * \brief Set caller ID number, name and ANI and generate AMI event. * @@ -2727,12 +2763,6 @@ struct ast_channel *ast_channel_get_by_exten(const char *exten, const char *cont /*! @} End channel search functions. */ -/*! - \brief propagate the linked id between chan and peer - */ -void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer); - - /*! * \brief Initialize the given name structure. * \since 1.8 @@ -3806,6 +3836,26 @@ void ast_channel_unlink(struct ast_channel *chan); */ void ast_channel_hangupcause_hash_set(struct ast_channel *chan, const struct ast_control_pvt_cause_code *cause_code, int datalen); +/*! + * \since 12 + * \brief Convert a string to a detail record AMA flag + * + * \param flag string form of flag + * + * \retval the enum (integer) form of the flag + */ +enum ama_flags ast_channel_string2amaflag(const char *flag); + +/*! + * \since 12 + * \brief Convert the enum representation of an AMA flag to a string representation + * + * \param flags integer flag + * + * \retval A string representation of the flag + */ +const char *ast_channel_amaflags2string(enum ama_flags flags); + /* ACCESSOR FUNTIONS */ /*! \brief Set the channel name */ void ast_channel_name_set(struct ast_channel *chan, const char *name); @@ -3863,8 +3913,8 @@ char ast_channel_sending_dtmf_digit(const struct ast_channel *chan); void ast_channel_sending_dtmf_digit_set(struct ast_channel *chan, char value); struct timeval ast_channel_sending_dtmf_tv(const struct ast_channel *chan); void ast_channel_sending_dtmf_tv_set(struct ast_channel *chan, struct timeval value); -int ast_channel_amaflags(const struct ast_channel *chan); -void ast_channel_amaflags_set(struct ast_channel *chan, int value); +enum ama_flags ast_channel_amaflags(const struct ast_channel *chan); +void ast_channel_amaflags_set(struct ast_channel *chan, enum ama_flags value); int ast_channel_epfd(const struct ast_channel *chan); void ast_channel_epfd_set(struct ast_channel *chan, int value); int ast_channel_fdno(const struct ast_channel *chan); @@ -3988,6 +4038,8 @@ void ast_channel_whentohangup_set(struct ast_channel *chan, struct timeval *valu void ast_channel_varshead_set(struct ast_channel *chan, struct varshead *value); struct timeval ast_channel_creationtime(struct ast_channel *chan); void ast_channel_creationtime_set(struct ast_channel *chan, struct timeval *value); +struct timeval ast_channel_answertime(struct ast_channel *chan); +void ast_channel_answertime_set(struct ast_channel *chan, struct timeval *value); /* List getters */ struct ast_hangup_handler_list *ast_channel_hangup_handlers(struct ast_channel *chan); @@ -4278,4 +4330,27 @@ int ast_channel_move(struct ast_channel *dest, struct ast_channel *source); */ int ast_channel_forward_endpoint(struct ast_channel *chan, struct ast_endpoint *endpoint); +/*! + * \brief Return the oldest linkedid between two channels. + * + * A channel linkedid is derived from the channel uniqueid which is formed like this: + * [systemname-]ctime.seq + * + * The systemname, and the dash are optional, followed by the epoch time followed by an + * integer sequence. Note that this is not a decimal number, since 1.2 is less than 1.11 + * in uniqueid land. + * + * To compare two uniqueids, we parse out the integer values of the time and the sequence + * numbers and compare them, with time trumping sequence. + * + * \param a The linkedid value of the first channel to compare + * \param b The linkedid value of the second channel to compare + * + * \retval NULL on failure + * \retval The oldest linkedid value + * \since 12.0.0 +*/ +const char *ast_channel_oldest_linkedid(const char *a, const char *b); + + #endif /* _ASTERISK_CHANNEL_H */ diff --git a/include/asterisk/channel_internal.h b/include/asterisk/channel_internal.h index 38776c1e1dfd4ecadf7c6c15b8d2bc152c7b93ab..a54a1b8483b36a46183d3d96699fc6d928f93660 100644 --- a/include/asterisk/channel_internal.h +++ b/include/asterisk/channel_internal.h @@ -18,8 +18,8 @@ * \brief Internal channel functions for channel.c to use */ -#define ast_channel_internal_alloc(destructor) __ast_channel_internal_alloc(destructor, __FILE__, __LINE__, __PRETTY_FUNCTION__) -struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *file, int line, const char *function); +#define ast_channel_internal_alloc(destructor, linkedid) __ast_channel_internal_alloc(destructor, linkedid, __FILE__, __LINE__, __PRETTY_FUNCTION__) +struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *linkedid, const char *file, int line, const char *function); void ast_channel_internal_finalize(struct ast_channel *chan); int ast_channel_internal_is_finalized(struct ast_channel *chan); void ast_channel_internal_cleanup(struct ast_channel *chan); diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h index 339c77274781084f0e856dbc2aeeb10719706901..ca075ae6902540313d1df3bfb19bfaa7eda1f385 100644 --- a/include/asterisk/stasis_channels.h +++ b/include/asterisk/stasis_channels.h @@ -38,36 +38,38 @@ */ struct ast_channel_snapshot { AST_DECLARE_STRING_FIELDS( - AST_STRING_FIELD(name); /*!< ASCII unique channel name */ - AST_STRING_FIELD(accountcode); /*!< Account code for billing */ - AST_STRING_FIELD(peeraccount); /*!< Peer account code for billing */ - AST_STRING_FIELD(userfield); /*!< Userfield for CEL billing */ - AST_STRING_FIELD(uniqueid); /*!< Unique Channel Identifier */ - AST_STRING_FIELD(linkedid); /*!< Linked Channel Identifier -- gets propagated by linkage */ - AST_STRING_FIELD(parkinglot); /*!< Default parking lot, if empty, default parking lot */ - AST_STRING_FIELD(hangupsource); /*!< Who is responsible for hanging up this channel */ - AST_STRING_FIELD(appl); /*!< Current application */ - AST_STRING_FIELD(data); /*!< Data passed to current application */ - AST_STRING_FIELD(context); /*!< Dialplan: Current extension context */ - AST_STRING_FIELD(exten); /*!< Dialplan: Current extension number */ - AST_STRING_FIELD(caller_name); /*!< Caller ID Name */ - AST_STRING_FIELD(caller_number); /*!< Caller ID Number */ - AST_STRING_FIELD(caller_ani); /*!< Caller ID ANI Number */ - AST_STRING_FIELD(caller_rdnis); /*!< Caller ID RDNIS Number */ - AST_STRING_FIELD(caller_dnid); /*!< Caller ID DNID Number */ - AST_STRING_FIELD(connected_name); /*!< Connected Line Name */ - AST_STRING_FIELD(connected_number); /*!< Connected Line Number */ - AST_STRING_FIELD(language); /*!< The default spoken language for the channel */ + AST_STRING_FIELD(name); /*!< ASCII unique channel name */ + AST_STRING_FIELD(accountcode); /*!< Account code for billing */ + AST_STRING_FIELD(peeraccount); /*!< Peer account code for billing */ + AST_STRING_FIELD(userfield); /*!< Userfield for CEL billing */ + AST_STRING_FIELD(uniqueid); /*!< Unique Channel Identifier */ + AST_STRING_FIELD(linkedid); /*!< Linked Channel Identifier -- gets propagated by linkage */ + AST_STRING_FIELD(parkinglot); /*!< Default parking lot, if empty, default parking lot */ + AST_STRING_FIELD(hangupsource); /*!< Who is responsible for hanging up this channel */ + AST_STRING_FIELD(appl); /*!< Current application */ + AST_STRING_FIELD(data); /*!< Data passed to current application */ + AST_STRING_FIELD(context); /*!< Dialplan: Current extension context */ + AST_STRING_FIELD(exten); /*!< Dialplan: Current extension number */ + AST_STRING_FIELD(caller_name); /*!< Caller ID Name */ + AST_STRING_FIELD(caller_number); /*!< Caller ID Number */ + AST_STRING_FIELD(caller_dnid); /*!< Dialed ID Number */ + AST_STRING_FIELD(caller_ani); /*< Caller ID ANI Number */ + AST_STRING_FIELD(caller_rdnis); /*!< Caller ID RDNIS Number */ + AST_STRING_FIELD(caller_subaddr); /*!< Caller subaddress */ + AST_STRING_FIELD(dialed_subaddr); /*!< Dialed subaddress */ + AST_STRING_FIELD(connected_name); /*!< Connected Line Name */ + AST_STRING_FIELD(connected_number); /*!< Connected Line Number */ + AST_STRING_FIELD(language); /*!< The default spoken language for the channel */ ); - struct timeval creationtime; /*!< The time of channel creation */ - enum ast_channel_state state; /*!< State of line */ - int priority; /*!< Dialplan: Current extension priority */ - int amaflags; /*!< AMA flags for billing */ - int hangupcause; /*!< Why is the channel hanged up. See causes.h */ - int caller_pres; /*!< Caller ID presentation. */ - struct ast_flags flags; /*!< channel flags of AST_FLAG_ type */ - struct varshead *manager_vars; /*!< Variables to be appended to manager events */ + struct timeval creationtime; /*!< The time of channel creation */ + enum ast_channel_state state; /*!< State of line */ + int priority; /*!< Dialplan: Current extension priority */ + int amaflags; /*!< AMA flags for billing */ + int hangupcause; /*!< Why is the channel hanged up. See causes.h */ + int caller_pres; /*!< Caller ID presentation. */ + struct ast_flags flags; /*!< channel flags of AST_FLAG_ type */ + struct varshead *manager_vars; /*!< Variables to be appended to manager events */ }; /*! @@ -300,7 +302,7 @@ void ast_channel_publish_snapshot(struct ast_channel *chan); * \since 12 * \brief Publish a \ref ast_channel_varset for a channel. * - * \param chan Channel to pulish the event for, or \c NULL for 'none'. + * \param chan Channel to publish the event for, or \c NULL for 'none'. * \param variable Name of the variable being set * \param value Value. */ diff --git a/include/asterisk/stasis_internal.h b/include/asterisk/stasis_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..67ab88ff04d1d8a9e73abf03688c7d5ee8cf91b4 --- /dev/null +++ b/include/asterisk/stasis_internal.h @@ -0,0 +1,69 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Matt Jordan <mjordan@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +#ifndef STASIS_INTERNAL_H_ +#define STASIS_INTERNAL_H_ + +/*! \file + * + * \brief Internal Stasis APIs. + * + * This header file is used to define functions that are shared between files that make + * up \ref stasis. Functions declared here should not be used by any module outside of + * Stasis. + * + * If you find yourself needing to call one of these functions directly, something has + * probably gone horribly wrong. + * + * \author Matt Jordan <mjordan@digium.com> + */ + +struct stasis_topic; +struct stasis_subscription; +struct stasis_message; + +/*! + * \brief Create a subscription. + * + * In addition to being AO2 managed memory (requiring an ao2_cleanup() to free + * up this reference), the subscription must be explicitly unsubscribed from its + * topic using stasis_unsubscribe(). + * + * The invocations of the callback are serialized, but may not always occur on + * the same thread. The invocation order of different subscriptions is + * unspecified. + * + * Note: modules outside of Stasis should use \ref stasis_subscribe. + * + * \param topic Topic to subscribe to. + * \param callback Callback function for subscription messages. + * \param data Data to be passed to the callback, in addition to the message. + * \param needs_mailbox Determines whether or not the subscription requires a mailbox. + * Subscriptions with mailboxes will be delivered on a thread in the Stasis threadpool; + * subscriptions without mailboxes will be delivered on the publisher thread. + * \return New \ref stasis_subscription object. + * \return \c NULL on error. + * \since 12 + */ +struct stasis_subscription *internal_stasis_subscribe( + struct stasis_topic *topic, + void (*stasis_subscription_cb)(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message), + void *data, + int needs_mailbox); + +#endif /* STASIS_INTERNAL_H_ */ diff --git a/include/asterisk/test.h b/include/asterisk/test.h index d890b7a3e6de24690ac4ec8c7470bbca44881e14..d29969f8bb7edb95cf2782e044ffca5f5cc0d528 100644 --- a/include/asterisk/test.h +++ b/include/asterisk/test.h @@ -238,7 +238,9 @@ struct ast_test_info { /*! * \brief Generic test callback function * - * \param error buffer string for failure results + * \param info The test info object + * \param cmd What to perform in the test + * \param test The actual test object being manipulated * * \retval AST_TEST_PASS for pass * \retval AST_TEST_FAIL for failure @@ -246,6 +248,30 @@ struct ast_test_info { typedef enum ast_test_result_state (ast_test_cb_t)(struct ast_test_info *info, enum ast_test_command cmd, struct ast_test *test); +/*! + * \since 12 + * \brief A test initialization callback function + * + * \param info The test info object + * \param test The actual test object that will be manipulated + * + * \retval 0 success + * \retval other failure. This will fail the test. + */ +typedef int (ast_test_init_cb_t)(struct ast_test_info *info, struct ast_test *test); + +/*! + * \since 12 + * \brief A test cleanup callback function + * + * \param info The test info object + * \param test The actual test object that was executed + * + * \retval 0 success + * \retval other failure. This will fail the test. + */ +typedef int (ast_test_cleanup_cb_t)(struct ast_test_info *info, struct ast_test *test); + /*! * \brief unregisters a test with the test framework * @@ -266,6 +292,40 @@ int ast_test_unregister(ast_test_cb_t *cb); */ int ast_test_register(ast_test_cb_t *cb); +/*! + * \since 12 + * \brief Register an initialization function to be run before each test + * executes + * + * This function lets a registered test have an initialization function that + * will be run prior to test execution. Each category may have a single init + * function. + * + * If the initialization function returns a non-zero value, the test will not + * be executed and the result will be set to \ref AST_TEST_FAIL. + * + * \retval 0 success + * \retval other failure + */ +int ast_test_register_init(const char *category, ast_test_init_cb_t *cb); + +/*! + * \since 12 + * \brief Register a cleanup function to be run after each test executes + * + * This function lets a registered test have a cleanup function that will be + * run immediately after test execution. Each category may have a single + * cleanup function. + * + * If the cleanup function returns a non-zero value, the test result will be + * set to \ref AST_TEST_FAIL. + * + * \retval 0 success + * \retval other failure + */ +int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb); + + /*! * \brief Unit test debug output. * \since 12.0.0 @@ -277,6 +337,17 @@ int ast_test_register(ast_test_cb_t *cb); */ void ast_test_debug(struct ast_test *test, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +/*! + * \brief Set the result of a test. + * + * If the caller of this function sets the result to AST_TEST_FAIL, returning + * AST_TEST_PASS from the test will not pass the test. This lets a test writer + * end and fail a test and continue on with logic, catching multiple failure + * conditions within a single test. + */ +void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state); + + /*! * \brief update test's status during testing. * diff --git a/include/asterisk/time.h b/include/asterisk/time.h index dd68db7044e50498c845ba9ef18876183cac100d..f2382df338ce953e97ffc75620bae99a1868020c 100644 --- a/include/asterisk/time.h +++ b/include/asterisk/time.h @@ -151,6 +151,17 @@ struct timeval ast_tvadd(struct timeval a, struct timeval b); */ struct timeval ast_tvsub(struct timeval a, struct timeval b); +/*! + * \since 12 + * \brief Formats a duration into HH:MM:SS + * + * \param duration The time (in seconds) to format + * \param buf A buffer to hold the formatted string' + * \param length The size of the buffer + */ +void ast_format_duration_hh_mm_ss(int duration, char *buf, size_t length); + + /*! * \brief Calculate remaining milliseconds given a starting timestamp * and upper bound diff --git a/main/asterisk.c b/main/asterisk.c index 99da006a52fe57b0df8701e8e4a304b56fa4b81d..871cd979ab9e2e29c6b0dcfa015949e1b52a0e70 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -643,7 +643,7 @@ static char *handle_show_settings(struct ast_cli_entry *e, int cmd, struct ast_c ast_cli(a->fd, " -------------\n"); ast_cli(a->fd, " Manager (AMI): %s\n", check_manager_enabled() ? "Enabled" : "Disabled"); ast_cli(a->fd, " Web Manager (AMI/HTTP): %s\n", check_webmanager_enabled() ? "Enabled" : "Disabled"); - ast_cli(a->fd, " Call data records: %s\n", check_cdr_enabled() ? "Enabled" : "Disabled"); + ast_cli(a->fd, " Call data records: %s\n", ast_cdr_is_enabled() ? "Enabled" : "Disabled"); ast_cli(a->fd, " Realtime Architecture (ARA): %s\n", ast_realtime_enabled() ? "Enabled" : "Disabled"); /*! \todo we could check musiconhold, voicemail, smdi, adsi, queues */ @@ -4318,50 +4318,50 @@ int main(int argc, char *argv[]) ast_http_init(); /* Start the HTTP server, if needed */ - if (ast_cdr_engine_init()) { + if (ast_indications_init()) { printf("%s", term_quit()); exit(1); } - if (ast_device_state_engine_init()) { + if (ast_features_init()) { printf("%s", term_quit()); exit(1); } - if (ast_presence_state_engine_init()) { + if (ast_bridging_init()) { printf("%s", term_quit()); exit(1); } - ast_dsp_init(); - ast_udptl_init(); - - if (ast_image_init()) { + if (ast_cdr_engine_init()) { printf("%s", term_quit()); exit(1); } - if (ast_file_init()) { + if (ast_device_state_engine_init()) { printf("%s", term_quit()); exit(1); } - if (load_pbx()) { + if (ast_presence_state_engine_init()) { printf("%s", term_quit()); exit(1); } - if (ast_indications_init()) { + ast_dsp_init(); + ast_udptl_init(); + + if (ast_image_init()) { printf("%s", term_quit()); exit(1); } - if (ast_features_init()) { + if (ast_file_init()) { printf("%s", term_quit()); exit(1); } - if (ast_bridging_init()) { + if (load_pbx()) { printf("%s", term_quit()); exit(1); } diff --git a/main/bridging.c b/main/bridging.c index 73005047340dd6d2b468f92eae0d68b20144e70b..93e4ec2d808e10abe048dda856fa61b7ceebf913 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -292,6 +292,75 @@ int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action) return 0; } +void ast_bridge_update_accountcodes(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap) +{ + struct ast_bridge_channel *other = NULL; + + AST_LIST_TRAVERSE(&bridge->channels, other, entry) { + if (other == swap) { + continue; + } + + if (!ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan)) && ast_strlen_zero(ast_channel_peeraccount(other->chan))) { + ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n", + ast_channel_accountcode(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan)); + ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan)); + } + if (!ast_strlen_zero(ast_channel_accountcode(other->chan)) && ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan))) { + ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n", + ast_channel_accountcode(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan)); + ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan)); + } + if (!ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan)) && ast_strlen_zero(ast_channel_accountcode(other->chan))) { + ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n", + ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan)); + ast_channel_accountcode_set(other->chan, ast_channel_peeraccount(bridge_channel->chan)); + } + if (!ast_strlen_zero(ast_channel_peeraccount(other->chan)) && ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan))) { + ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n", + ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan)); + ast_channel_accountcode_set(bridge_channel->chan, ast_channel_peeraccount(other->chan)); + } + if (bridge->num_channels == 2) { + if (strcmp(ast_channel_accountcode(bridge_channel->chan), ast_channel_peeraccount(other->chan))) { + ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n", + ast_channel_peeraccount(other->chan), ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan)); + ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan)); + } + if (strcmp(ast_channel_accountcode(other->chan), ast_channel_peeraccount(bridge_channel->chan))) { + ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n", + ast_channel_peeraccount(bridge_channel->chan), ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan)); + ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan)); + } + } + } +} + +void ast_bridge_update_linkedids(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap) +{ + struct ast_bridge_channel *other = NULL; + const char *oldest_linkedid = ast_channel_linkedid(bridge_channel->chan); + + AST_LIST_TRAVERSE(&bridge->channels, other, entry) { + if (other == swap) { + continue; + } + oldest_linkedid = ast_channel_oldest_linkedid(oldest_linkedid, ast_channel_linkedid(other->chan)); + } + + if (ast_strlen_zero(oldest_linkedid)) { + return; + } + + ast_channel_linkedid_set(bridge_channel->chan, oldest_linkedid); + AST_LIST_TRAVERSE(&bridge->channels, other, entry) { + if (other == swap) { + continue; + } + ast_channel_linkedid_set(other->chan, oldest_linkedid); + } +} + int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr) { struct ast_frame *dup; @@ -529,6 +598,14 @@ static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel) ast_bridge_channel_clear_roles(bridge_channel); + /* If we are not going to be hung up after leaving a bridge, and we were an + * outgoing channel, clear the outgoing flag. + */ + if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING) + && (ast_channel_softhangup_internal_flag(bridge_channel->chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))) { + ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING); + } + bridge_dissolve_check(bridge_channel); bridge->reconfigured = 1; diff --git a/main/bridging_basic.c b/main/bridging_basic.c index 43862a01396e1c87e22a054933ae0e9d5d980c44..09f2ca55665623a23c55c25d4f02fc3199ee78e3 100644 --- a/main/bridging_basic.c +++ b/main/bridging_basic.c @@ -131,6 +131,9 @@ static int bridge_basic_push(struct ast_bridge *self, struct ast_bridge_channel return -1; } + ast_bridge_update_accountcodes(self, bridge_channel, swap); + ast_bridge_update_linkedids(self, bridge_channel, swap); + return ast_bridge_base_v_table.push(self, bridge_channel, swap); } diff --git a/main/cdr.c b/main/cdr.c index a0560676a546191918225d6e8dd231f722ccf81b..9f710fe0d3f5f710165de8a55e635ab00a71ffe8 100644 --- a/main/cdr.c +++ b/main/cdr.c @@ -48,6 +48,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <signal.h> +#include <inttypes.h> #include "asterisk/lock.h" #include "asterisk/channel.h" @@ -62,88 +63,2381 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/stringfields.h" #include "asterisk/data.h" +#include "asterisk/config_options.h" +#include "asterisk/json.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_bridging.h" +#include "asterisk/stasis_message_router.h" +#include "asterisk/astobj2.h" /*** DOCUMENTATION + <configInfo name="cdr" language="en_US"> + <synopsis>Call Detail Record configuration</synopsis> + <description> + <para>CDR is Call Detail Record, which provides logging services via a variety of + pluggable backend modules. Detailed call information can be recorded to + databases, files, etc. Useful for billing, fraud prevention, compliance with + Sarbanes-Oxley aka The Enron Act, QOS evaluations, and more.</para> + </description> + <configFile name="cdr.conf"> + <configObject name="general"> + <synopsis>Global settings applied to the CDR engine.</synopsis> + <configOption name="debug"> + <synopsis>Enable/disable verbose CDR debugging.</synopsis> + <description><para>When set to <literal>True</literal>, verbose updates + of changes in CDR information will be logged. Note that this is only + of use when debugging CDR behavior.</para> + </description> + </configOption> + <configOption name="enable"> + <synopsis>Enable/disable CDR logging.</synopsis> + <description><para>Define whether or not to use CDR logging. Setting this to "no" will override + any loading of backend CDR modules. Default is "yes".</para> + </description> + </configOption> + <configOption name="unanswered"> + <synopsis>Log calls that are never answered.</synopsis> + <description><para>Define whether or not to log unanswered calls. Setting this to "yes" will + report every attempt to ring a phone in dialing attempts, when it was not + answered. For example, if you try to dial 3 extensions, and this option is "yes", + you will get 3 CDR's, one for each phone that was rung. Some find this information horribly + useless. Others find it very valuable. Note, in "yes" mode, you will see one CDR, with one of + the call targets on one side, and the originating channel on the other, and then one CDR for + each channel attempted. This may seem redundant, but cannot be helped.</para> + <para>In brief, this option controls the reporting of unanswered calls which only have an A + party. Calls which get offered to an outgoing line, but are unanswered, are still + logged, and that is the intended behavior. (It also results in some B side CDRs being + output, as they have the B side channel as their source channel, and no destination + channel.)</para> + </description> + </configOption> + <configOption name="congestion"> + <synopsis>Log congested calls.</synopsis> + <description><para>Define whether or not to log congested calls. Setting this to "yes" will + report each call that fails to complete due to congestion conditions.</para> + </description> + </configOption> + <configOption name="endbeforehexten"> + <synopsis>End the CDR before executing the "h" extension</synopsis> + <description><para>Normally, CDR's are not closed out until after all extensions are finished + executing. By enabling this option, the CDR will be ended before executing + the <literal>h</literal> extension and hangup handlers so that CDR values such as <literal>end</literal> and + <literal>"billsec"</literal> may be retrieved inside of this extension. + The default value is "no".</para> + </description> + </configOption> + <configOption name="initiatedseconds"> + <synopsis>Count microseconds for billsec purposes</synopsis> + <description><para>Normally, the <literal>billsec</literal> field logged to the CDR backends + is simply the end time (hangup time) minus the answer time in seconds. Internally, + asterisk stores the time in terms of microseconds and seconds. By setting + initiatedseconds to <literal>yes</literal>, you can force asterisk to report any seconds + that were initiated (a sort of round up method). Technically, this is + when the microsecond part of the end time is greater than the microsecond + part of the answer time, then the billsec time is incremented one second.</para> + </description> + </configOption> + <configOption name="batch"> + <synopsis>Submit CDRs to the backends for processing in batches</synopsis> + <description><para>Define the CDR batch mode, where instead of posting the CDR at the end of + every call, the data will be stored in a buffer to help alleviate load on the + asterisk server.</para> + <warning><para>Use of batch mode may result in data loss after unsafe asterisk termination, + i.e., software crash, power failure, kill -9, etc.</para> + </warning> + </description> + </configOption> + <configOption name="size"> + <synopsis>The maximum number of CDRs to accumulate before triggering a batch</synopsis> + <description><para>Define the maximum number of CDRs to accumulate in the buffer before posting + them to the backend engines. batch must be set to <literal>yes</literal>.</para> + </description> + </configOption> + <configOption name="time"> + <synopsis>The maximum time to accumulate CDRs before triggering a batch</synopsis> + <description><para>Define the maximum time to accumulate CDRs before posting them in a batch to the + backend engines. If this time limit is reached, then it will post the records, regardless of the value + defined for size. batch must be set to <literal>yes</literal>.</para> + <note><para>Time is expressed in seconds.</para></note> + </description> + </configOption> + <configOption name="scheduleronly"> + <synopsis>Post batched CDRs on their own thread instead of the scheduler</synopsis> + <description><para>The CDR engine uses the internal asterisk scheduler to determine when to post + records. Posting can either occur inside the scheduler thread, or a new + thread can be spawned for the submission of every batch. For small batches, + it might be acceptable to just use the scheduler thread, so set this to <literal>yes</literal>. + For large batches, say anything over size=10, a new thread is recommended, so + set this to <literal>no</literal>.</para> + </description> + </configOption> + <configOption name="safeshutdown"> + <synopsis>Block shutdown of Asterisk until CDRs are submitted</synopsis> + <description><para>When shutting down asterisk, you can block until the CDRs are submitted. If + you don't, then data will likely be lost. You can always check the size of + the CDR batch buffer with the CLI <astcli>cdr status</astcli> command. To enable blocking on + submission of CDR data during asterisk shutdown, set this to <literal>yes</literal>.</para> + </description> + </configOption> + </configObject> + </configFile> + </configInfo> ***/ -/*! Default AMA flag for billing records (CDR's) */ -int ast_default_amaflags = AST_CDR_DOCUMENTATION; -char ast_default_accountcode[AST_MAX_ACCOUNT_CODE]; -struct ast_cdr_beitem { +#define DEFAULT_ENABLED "1" +#define DEFAULT_BATCHMODE "0" +#define DEFAULT_UNANSWERED "0" +#define DEFAULT_CONGESTION "0" +#define DEFAULT_END_BEFORE_H_EXTEN "0" +#define DEFAULT_INITIATED_SECONDS "0" + +#define DEFAULT_BATCH_SIZE "100" +#define MAX_BATCH_SIZE 1000 +#define DEFAULT_BATCH_TIME "300" +#define MAX_BATCH_TIME 86400 +#define DEFAULT_BATCH_SCHEDULER_ONLY "0" +#define DEFAULT_BATCH_SAFE_SHUTDOWN "1" + +#define CDR_DEBUG(mod_cfg, fmt, ...) \ + do { \ + if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \ + ast_verb(1, (fmt), ##__VA_ARGS__); \ + } } while (0) + +static void cdr_detach(struct ast_cdr *cdr); +static void cdr_submit_batch(int shutdown); + +/*! \brief The configuration settings for this module */ +struct module_config { + struct ast_cdr_config *general; /*< CDR global settings */ +}; + +/*! \brief The container for the module configuration */ +static AO2_GLOBAL_OBJ_STATIC(module_configs); + +/*! \brief The type definition for general options */ +static struct aco_type general_option = { + .type = ACO_GLOBAL, + .name = "general", + .item_offset = offsetof(struct module_config, general), + .category = "^general$", + .category_match = ACO_WHITELIST, +}; + +static void *module_config_alloc(void); +static void module_config_destructor(void *obj); + +/*! \brief The file definition */ +static struct aco_file module_file_conf = { + .filename = "cdr.conf", + .skip_category = "(^csv$|^custom$|^manager$|^odbc$|^pgsql$|^radius$|^sqlite$|^tds$|^mysql$)", + .types = ACO_TYPES(&general_option), +}; + +CONFIG_INFO_CORE("cdr", cfg_info, module_configs, module_config_alloc, + .files = ACO_FILES(&module_file_conf), +); + +static struct aco_type *general_options[] = ACO_TYPES(&general_option); + +/*! \brief Dispose of a module config object */ +static void module_config_destructor(void *obj) +{ + struct module_config *cfg = obj; + + if (!cfg) { + return; + } + ao2_ref(cfg->general, -1); +} + +/*! \brief Create a new module config object */ +static void *module_config_alloc(void) +{ + struct module_config *mod_cfg; + struct ast_cdr_config *cdr_config; + + mod_cfg = ao2_alloc(sizeof(*mod_cfg), module_config_destructor); + if (!mod_cfg) { + return NULL; + } + + cdr_config = ao2_alloc(sizeof(*cdr_config), NULL); + if (!cdr_config) { + ao2_ref(cdr_config, -1); + return NULL; + } + mod_cfg->general = cdr_config; + + return mod_cfg; +} + +/*! \brief Registration object for CDR backends */ +struct cdr_beitem { char name[20]; char desc[80]; ast_cdrbe be; - AST_RWLIST_ENTRY(ast_cdr_beitem) list; + AST_RWLIST_ENTRY(cdr_beitem) list; +}; + +/*! \brief List of registered backends */ +static AST_RWLIST_HEAD_STATIC(be_list, cdr_beitem); + +/*! \brief Queued CDR waiting to be batched */ +struct cdr_batch_item { + struct ast_cdr *cdr; + struct cdr_batch_item *next; +}; + +/*! \brief The actual batch queue */ +static struct cdr_batch { + int size; + struct cdr_batch_item *head; + struct cdr_batch_item *tail; +} *batch = NULL; + +/*! \brief The global sequence counter used for CDRs */ +static int global_cdr_sequence = 0; + +/*! \brief Scheduler items */ +static struct ast_sched_context *sched; +static int cdr_sched = -1; +AST_MUTEX_DEFINE_STATIC(cdr_sched_lock); +static pthread_t cdr_thread = AST_PTHREADT_NULL; + +/*! \brief Lock protecting modifications to the batch queue */ +AST_MUTEX_DEFINE_STATIC(cdr_batch_lock); + +/*! \brief These are used to wake up the CDR thread when there's work to do */ +AST_MUTEX_DEFINE_STATIC(cdr_pending_lock); +static ast_cond_t cdr_pending_cond; + +/*! \brief A container of the active CDRs indexed by Party A channel name */ +static struct ao2_container *active_cdrs_by_channel; + +/*! \brief A container of the active CDRs indexed by the bridge ID */ +static struct ao2_container *active_cdrs_by_bridge; + +/*! \brief Message router for stasis messages regarding channel state */ +static struct stasis_message_router *stasis_router; + +/*! \brief Our subscription for bridges */ +static struct stasis_subscription *bridge_subscription; + +/*! \brief Our subscription for channels */ +static struct stasis_subscription *channel_subscription; + +/*! \brief The parent topic for all topics we want to aggregate for CDRs */ +static struct stasis_topic *cdr_topic; + +struct cdr_object; + +/*! + * \brief A virtual table used for \ref cdr_object. + * + * Note that all functions are optional - if a subclass does not need an + * implementation, it is safe to leave it NULL. + */ +struct cdr_object_fn_table { + /*! \brief Name of the subclass */ + const char *name; + + /*! + * \brief An initialization function. This will be called automatically + * when a \ref cdr_object is switched to this type in + * \ref cdr_object_transition_state + * + * \param cdr The \ref cdr_object that was just transitioned + */ + void (* const init_function)(struct cdr_object *cdr); + + /*! + * \brief Process a Party A update for the \ref cdr_object + * + * \param cdr The \ref cdr_object to process the update + * \param snapshot The snapshot for the CDR's Party A + * \retval 0 the CDR handled the update or ignored it + * \retval 1 the CDR is finalized and a new one should be made to handle it + */ + int (* const process_party_a)(struct cdr_object *cdr, + struct ast_channel_snapshot *snapshot); + + /*! + * \brief Process a Party B update for the \ref cdr_object + * + * \param cdr The \ref cdr_object to process the update + * \param snapshot The snapshot for the CDR's Party B + */ + void (* const process_party_b)(struct cdr_object *cdr, + struct ast_channel_snapshot *snapshot); + + /*! + * \brief Process the beginning of a dial. A dial message implies one of two + * things: + * The \ref cdr_object's Party A has been originated + * The \ref cdr_object's Party A is dialing its Party B + * + * \param cdr The \ref cdr_object + * \param caller The originator of the dial attempt + * \param peer The destination of the dial attempt + * + * \retval 0 if the parties in the dial were handled by this CDR + * \retval 1 if the parties could not be handled by this CDR + */ + int (* const process_dial_begin)(struct cdr_object *cdr, + struct ast_channel_snapshot *caller, + struct ast_channel_snapshot *peer); + + /*! + * \brief Process the end of a dial. At the end of a dial, a CDR can be + * transitioned into one of two states - DialedPending + * (\ref dialed_pending_state_fn_table) or Finalized + * (\ref finalized_state_fn_table). + * + * \param cdr The \ref cdr_object + * \param caller The originator of the dial attempt + * \param peer the Destination of the dial attempt + * \param dial_status What happened + * + * \retval 0 if the parties in the dial were handled by this CDR + * \retval 1 if the parties could not be handled by this CDR + */ + int (* const process_dial_end)(struct cdr_object *cdr, + struct ast_channel_snapshot *caller, + struct ast_channel_snapshot *peer, + const char *dial_status); + + /*! + * \brief Process the entering of a bridge by this CDR. The purpose of this + * callback is to have the CDR prepare itself for the bridge and attempt to + * find a valid Party B. The act of creating new CDRs based on the entering + * of this channel into the bridge is handled by the higher level message + * handler. + * + * \param cdr The \ref cdr_object + * \param bridge The bridge that the Party A just entered into + * \param channel The \ref ast_channel_snapshot for this CDR's Party A + * + * \retval 0 This CDR found a Party B for itself and updated it, or there + * was no Party B to find (we're all alone) + * \retval 1 This CDR couldn't find a Party B, and there were options + */ + int (* const process_bridge_enter)(struct cdr_object *cdr, + struct ast_bridge_snapshot *bridge, + struct ast_channel_snapshot *channel); + + /*! + * \brief Process the leaving of a bridge by this CDR. + * + * \param cdr The \ref cdr_object + * \param bridge The bridge that the Party A just left + * \param channel The \ref ast_channel_snapshot for this CDR's Party A + * + * \retval 0 This CDR left successfully + * \retval 1 Error + */ + int (* const process_bridge_leave)(struct cdr_object *cdr, + struct ast_bridge_snapshot *bridge, + struct ast_channel_snapshot *channel); +}; + +static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); +static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel); +static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status); + +static void single_state_init_function(struct cdr_object *cdr); +static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); +static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer); +static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel); + +/*! + * \brief The virtual table for the Single state. + * + * A \ref cdr_object starts off in this state. This represents a channel that + * has no Party B information itself. + * + * A \ref cdr_object from this state can go into any of the following states: + * * \ref dial_state_fn_table + * * \ref bridge_state_fn_table + * * \ref finalized_state_fn_table + */ +struct cdr_object_fn_table single_state_fn_table = { + .name = "Single", + .init_function = single_state_init_function, + .process_party_a = base_process_party_a, + .process_party_b = single_state_process_party_b, + .process_dial_begin = single_state_process_dial_begin, + .process_dial_end = base_process_dial_end, + .process_bridge_enter = single_state_process_bridge_enter, + .process_bridge_leave = base_process_bridge_leave, +}; + +static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); +static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer); +static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status); +static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel); + +/*! + * \brief The virtual table for the Dial state. + * + * A \ref cdr_object that has begun a dial operation. This state is entered when + * the Party A for a CDR is determined to be dialing out to a Party B or when + * a CDR is for an originated channel (in which case the Party A information is + * the originated channel, and there is no Party B). + * + * A \ref cdr_object from this state can go in any of the following states: + * * \ref dialed_pending_state_fn_table + * * \ref bridge_state_fn_table + * * \ref finalized_state_fn_table + */ +struct cdr_object_fn_table dial_state_fn_table = { + .name = "Dial", + .process_party_a = base_process_party_a, + .process_party_b = dial_state_process_party_b, + .process_dial_begin = dial_state_process_dial_begin, + .process_dial_end = dial_state_process_dial_end, + .process_bridge_enter = dial_state_process_bridge_enter, + .process_bridge_leave = base_process_bridge_leave, +}; + +static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); +static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer); +static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel); + +/*! + * \brief The virtual table for the Dialed Pending state. + * + * A \ref cdr_object that has successfully finished a dial operation, but we + * don't know what they're going to do yet. It's theoretically possible to dial + * a party and then have that party not be bridged with the caller; likewise, + * an origination can complete and the channel go off and execute dialplan. The + * pending state acts as a bridge between either: + * * Entering a bridge + * * Getting a new CDR for new dialplan execution + * * Switching from being originated to executing dialplan + * + * A \ref cdr_object from this state can go in any of the following states: + * * \ref single_state_fn_table + * * \ref dialed_pending_state_fn_table + * * \ref bridge_state_fn_table + * * \ref finalized_state_fn_table + */ +struct cdr_object_fn_table dialed_pending_state_fn_table = { + .name = "DialedPending", + .process_party_a = dialed_pending_state_process_party_a, + .process_dial_begin = dialed_pending_state_process_dial_begin, + .process_bridge_enter = dialed_pending_state_process_bridge_enter, + .process_bridge_leave = base_process_bridge_leave, +}; + +static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); +static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel); + +/*! + * \brief The virtual table for the Bridged state + * + * A \ref cdr_object enters this state when it receives notification that the + * channel has entered a bridge. + * + * A \ref cdr_object from this state can go to: + * * \ref finalized_state_fn_table + * * \ref pending_state_fn_table + */ +struct cdr_object_fn_table bridge_state_fn_table = { + .name = "Bridged", + .process_party_a = base_process_party_a, + .process_party_b = bridge_state_process_party_b, + .process_bridge_leave = bridge_state_process_bridge_leave, +}; + +static void pending_state_init_function(struct cdr_object *cdr); +static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); +static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer); +static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel); + +/*! + * \brief The virtual table for the Pending state + * + * At certain times, we don't know where to go with the CDR. A good example is + * when a channel leaves a bridge - we don't know if the channel is about to + * be hung up; if it is about to go execute dialplan; dial someone; go into + * another bridge, etc. At these times, the CDR goes into pending and observes + * the messages that come in next to infer where the next logical place to go + * is. + * + * In this state, a CDR can go anywhere! + */ +struct cdr_object_fn_table bridged_pending_state_fn_table = { + .name = "Pending", + .init_function = pending_state_init_function, + .process_party_a = pending_state_process_party_a, + .process_dial_begin = pending_state_process_dial_begin, + .process_dial_end = base_process_dial_end, + .process_bridge_enter = pending_state_process_bridge_enter, + .process_bridge_leave = base_process_bridge_leave, +}; + +static void finalized_state_init_function(struct cdr_object *cdr); +static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot); + +/*! + * \brief The virtual table for the finalized state. + * + * Once in the finalized state, the CDR is done. No modifications can be made + * to the CDR. + */ +struct cdr_object_fn_table finalized_state_fn_table = { + .name = "Finalized", + .init_function = finalized_state_init_function, + .process_party_a = finalized_state_process_party_a, +}; + +/*! \brief A wrapper object around a snapshot. + * Fields that are mutable by the CDR engine are replicated here. + */ +struct cdr_object_snapshot { + struct ast_channel_snapshot *snapshot; /*!< The channel snapshot */ + char userfield[AST_MAX_USER_FIELD]; /*!< Userfield for the channel */ + unsigned int flags; /*!< Specific flags for this party */ + struct varshead variables; /*!< CDR variables for the channel */ +}; + +/*! \brief An in-memory representation of an active CDR */ +struct cdr_object { + struct cdr_object_snapshot party_a; /*!< The Party A information */ + struct cdr_object_snapshot party_b; /*!< The Party B information */ + struct cdr_object_fn_table *fn_table; /*!< The current virtual table */ + + enum ast_cdr_disposition disposition; /*!< The disposition of the CDR */ + struct timeval start; /*!< When this CDR was created */ + struct timeval answer; /*!< Either when the channel was answered, or when the path between channels was established */ + struct timeval end; /*!< When this CDR was finalized */ + unsigned int sequence; /*!< A monotonically increasing number for each CDR */ + struct ast_flags flags; /*!< Flags on the CDR */ + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(linkedid); /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */ + AST_STRING_FIELD(name); /*!< Channel name of party A. Cached here as the party A address may change */ + AST_STRING_FIELD(bridge); /*!< The bridge the party A happens to be in. */ + AST_STRING_FIELD(appl); /*!< The last accepted application party A was in */ + AST_STRING_FIELD(data); /*!< The data for the last accepted application party A was in */ + ); + struct cdr_object *next; /*!< The next CDR object in the chain */ + struct cdr_object *last; /*!< The last CDR object in the chain */ +}; + +/*! + * \brief Copy variables from one list to another + * \param to_list destination + * \param from_list source + * \retval The number of copied variables + */ +static int copy_variables(struct varshead *to_list, struct varshead *from_list) +{ + struct ast_var_t *variables, *newvariable = NULL; + const char *var, *val; + int x = 0; + + AST_LIST_TRAVERSE(from_list, variables, entries) { + if (variables && + (var = ast_var_name(variables)) && (val = ast_var_value(variables)) && + !ast_strlen_zero(var) && !ast_strlen_zero(val)) { + newvariable = ast_var_assign(var, val); + AST_LIST_INSERT_HEAD(to_list, newvariable, entries); + x++; + } + } + + return x; +} + +/*! + * \brief Delete all variables from a variable list + * \param headp The head pointer to the variable list to delete + */ +static void free_variables(struct varshead *headp) +{ + struct ast_var_t *vardata; + + while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) { + ast_var_delete(vardata); + } +} + +/*! + * \brief Copy a snapshot and its details + * \param dst The destination + * \param src The source + */ +static void cdr_object_snapshot_copy(struct cdr_object_snapshot *dst, struct cdr_object_snapshot *src) +{ + if (dst->snapshot) { + ao2_t_ref(dst->snapshot, -1, "release old snapshot during copy"); + } + dst->snapshot = src->snapshot; + ao2_t_ref(dst->snapshot, +1, "bump new snapshot during copy"); + strcpy(dst->userfield, src->userfield); + dst->flags = src->flags; + copy_variables(&dst->variables, &src->variables); +} + +/*! + * \brief Transition a \ref cdr_object to a new state + * \param cdr The \ref cdr_object to transition + * \param fn_table The \ref cdr_object_fn_table state to go to + */ +static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + + CDR_DEBUG(mod_cfg, "%p - Transitioning CDR for %s from state %s to %s\n", + cdr, cdr->party_a.snapshot->name, + cdr->fn_table ? cdr->fn_table->name : "NONE", fn_table->name); + cdr->fn_table = fn_table; + if (cdr->fn_table->init_function) { + cdr->fn_table->init_function(cdr); + } +} +/*! \internal + * \brief Hash function for containers of CDRs indexing by Party A name */ +static int cdr_object_channel_hash_fn(const void *obj, const int flags) +{ + const struct cdr_object *cdr = obj; + const char *name = (flags & OBJ_KEY) ? obj : cdr->name; + return ast_str_case_hash(name); +} + +/*! \internal + * \brief Comparison function for containers of CDRs indexing by Party A name + */ +static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags) +{ + struct cdr_object *left = obj; + struct cdr_object *right = arg; + const char *match = (flags & OBJ_KEY) ? arg : right->name; + return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP); +} + +/*! \internal + * \brief Hash function for containers of CDRs indexing by bridge ID + */ +static int cdr_object_bridge_hash_fn(const void *obj, const int flags) +{ + const struct cdr_object *cdr = obj; + const char *id = (flags & OBJ_KEY) ? obj : cdr->bridge; + return ast_str_case_hash(id); +} + +/*! \internal + * \brief Comparison function for containers of CDRs indexing by bridge. Note + * that we expect there to be collisions, as a single bridge may have multiple + * CDRs active at one point in time + */ +static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags) +{ + struct cdr_object *left = obj; + struct cdr_object *right = arg; + struct cdr_object *it_cdr; + const char *match = (flags & OBJ_KEY) ? arg : right->bridge; + for (it_cdr = left; it_cdr; it_cdr = it_cdr->next) { + if (!strcasecmp(it_cdr->bridge, match)) { + return CMP_MATCH; + } + } + return 0; +} + +/*! + * \brief \ref cdr_object Destructor + */ +static void cdr_object_dtor(void *obj) +{ + struct cdr_object *cdr = obj; + struct ast_var_t *it_var; + + if (!cdr) { + return; + } + + ao2_cleanup(cdr->party_a.snapshot); + ao2_cleanup(cdr->party_b.snapshot); + while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a.variables, entries))) { + ast_var_delete(it_var); + } + while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_b.variables, entries))) { + ast_var_delete(it_var); + } + ast_string_field_free_memory(cdr); + + if (cdr->next) { + ao2_cleanup(cdr->next); + } +} + +/*! + * \brief \ref cdr_object constructor + * \param chan The \ref ast_channel_snapshot that is the CDR's Party A + * + * This implicitly sets the state of the newly created CDR to the Single state + * (\ref single_state_fn_table) + */ +static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + struct cdr_object *cdr; + + ast_assert(chan != NULL); + + cdr = ao2_alloc(sizeof(*cdr), cdr_object_dtor); + if (!cdr) { + return NULL; + } + cdr->last = cdr; + if (ast_string_field_init(cdr, 64)) { + return NULL; + } + ast_string_field_set(cdr, name, chan->name); + ast_string_field_set(cdr, linkedid, chan->linkedid); + cdr->disposition = AST_CDR_NULL; + cdr->sequence = ast_atomic_fetchadd_int(&global_cdr_sequence, +1); + + cdr->party_a.snapshot = chan; + ao2_t_ref(cdr->party_a.snapshot, +1, "bump snapshot during CDR creation"); + + CDR_DEBUG(mod_cfg, "%p - Created CDR for channel %s\n", cdr, chan->name); + + cdr_object_transition_state(cdr, &single_state_fn_table); + + return cdr; +} + +/*! + * \brief Create a new \ref cdr_object and append it to an existing chain + * \param cdr The \ref cdr_object to append to + */ +static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr) +{ + struct cdr_object *new_cdr; + struct cdr_object *it_cdr; + struct cdr_object *cdr_last; + + cdr_last = cdr->last; + new_cdr = cdr_object_alloc(cdr_last->party_a.snapshot); + if (!new_cdr) { + return NULL; + } + new_cdr->disposition = AST_CDR_NULL; + + /* Copy over the linkedid, as it may have changed */ + ast_string_field_set(new_cdr, linkedid, cdr_last->linkedid); + ast_string_field_set(new_cdr, appl, cdr_last->appl); + ast_string_field_set(new_cdr, data, cdr_last->data); + + /* Copy over other Party A information */ + cdr_object_snapshot_copy(&new_cdr->party_a, &cdr_last->party_a); + + /* Append the CDR to the end of the list */ + for (it_cdr = cdr; it_cdr->next; it_cdr = it_cdr->next) { + it_cdr->last = new_cdr; + } + it_cdr->last = new_cdr; + it_cdr->next = new_cdr; + + return new_cdr; +} + +/*! + * \brief Return whether or not a \ref ast_channel_snapshot is for a channel + * that was created as the result of a dial operation + * + * \retval 0 the channel was not created as the result of a dial + * \retval 1 the channel was created as the result of a dial + */ +static int snapshot_is_dialed(struct ast_channel_snapshot *snapshot) +{ + return (ast_test_flag(&snapshot->flags, AST_FLAG_OUTGOING) + && !(ast_test_flag(&snapshot->flags, AST_FLAG_ORIGINATED))); +} + +/*! + * \brief Given two CDR snapshots, figure out who should be Party A for the + * resulting CDR + * \param left One of the snapshots + * \param right The other snapshot + * \retval The snapshot that won + */ +static struct cdr_object_snapshot *cdr_object_pick_party_a(struct cdr_object_snapshot *left, struct cdr_object_snapshot *right) +{ + /* Check whether or not the party is dialed. A dialed party is never the + * Party A with a party that was not dialed. + */ + if (!snapshot_is_dialed(left->snapshot) && snapshot_is_dialed(right->snapshot)) { + return left; + } else if (snapshot_is_dialed(left->snapshot) && !snapshot_is_dialed(right->snapshot)) { + return right; + } + + /* Try the Party A flag */ + if (ast_test_flag(left, AST_CDR_FLAG_PARTY_A) && !ast_test_flag(right, AST_CDR_FLAG_PARTY_A)) { + return left; + } else if (!ast_test_flag(right, AST_CDR_FLAG_PARTY_A) && ast_test_flag(right, AST_CDR_FLAG_PARTY_A)) { + return right; + } + + /* Neither party is dialed and neither has the Party A flag - defer to + * creation time */ + if (left->snapshot->creationtime.tv_sec < right->snapshot->creationtime.tv_sec) { + return left; + } else if (left->snapshot->creationtime.tv_sec > right->snapshot->creationtime.tv_sec) { + return right; + } else if (left->snapshot->creationtime.tv_usec > right->snapshot->creationtime.tv_usec) { + return right; + } else { + /* Okay, fine, take the left one */ + return left; + } +} + +/*! + * Compute the duration for a \ref cdr_object + */ +static long cdr_object_get_duration(struct cdr_object *cdr) +{ + if (ast_tvzero(cdr->end)) { + return (long)(ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000); + } else { + return (long)(ast_tvdiff_ms(cdr->end, cdr->start) / 1000); + } +} + +/*! + * \brief Compute the billsec for a \ref cdr_object + */ +static long cdr_object_get_billsec(struct cdr_object *cdr) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + long int ms; + + if (ast_tvzero(cdr->answer)) { + return 0; + } + ms = ast_tvdiff_ms(cdr->end, cdr->answer); + if (ast_test_flag(&mod_cfg->general->settings, CDR_INITIATED_SECONDS) + && (ms % 1000 >= 500)) { + ms = (ms / 1000) + 1; + } else { + ms = ms / 1000; + } + + return ms; +} + +/*! + * \brief Create a chain of \ref ast_cdr objects from a chain of \ref cdr_object + * suitable for consumption by the registered CDR backends + * \param cdr The \ref cdr_object to convert to a public record + * \retval A chain of \ref ast_cdr objects on success + * \retval NULL on failure + */ +static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr) +{ + struct ast_cdr *pub_cdr = NULL, *cdr_prev; + struct ast_var_t *it_var, *it_copy_var; + struct ast_channel_snapshot *party_a; + struct ast_channel_snapshot *party_b; + + while (cdr) { + struct ast_cdr *cdr_copy; + + /* Don't create records for CDRs where the party A was a dialed channel */ + if (snapshot_is_dialed(cdr->party_a.snapshot)) { + cdr = cdr->next; + continue; + } + + cdr_copy = ast_calloc(1, sizeof(*cdr_copy)); + if (!cdr_copy) { + ast_free(pub_cdr); + return NULL; + } + + party_a = cdr->party_a.snapshot; + party_b = cdr->party_b.snapshot; + + /* Party A */ + ast_assert(party_a != NULL); + ast_copy_string(cdr_copy->accountcode, party_a->accountcode, sizeof(cdr_copy->accountcode)); + cdr_copy->amaflags = party_a->amaflags; + ast_copy_string(cdr_copy->channel, party_a->name, sizeof(cdr_copy->channel)); + ast_callerid_merge(cdr_copy->clid, sizeof(cdr_copy->clid), party_a->caller_name, party_a->caller_number, ""); + ast_copy_string(cdr_copy->src, party_a->caller_number, sizeof(cdr_copy->src)); + ast_copy_string(cdr_copy->uniqueid, party_a->uniqueid, sizeof(cdr_copy->uniqueid)); + ast_copy_string(cdr_copy->lastapp, cdr->appl, sizeof(cdr_copy->lastapp)); + ast_copy_string(cdr_copy->lastdata, cdr->data, sizeof(cdr_copy->lastdata)); + ast_copy_string(cdr_copy->dst, party_a->exten, sizeof(cdr_copy->dst)); + ast_copy_string(cdr_copy->dcontext, party_a->context, sizeof(cdr_copy->dcontext)); + + /* Party B */ + if (party_b) { + ast_copy_string(cdr_copy->dstchannel, party_b->name, sizeof(cdr_copy->dstchannel)); + ast_copy_string(cdr_copy->peeraccount, party_b->accountcode, sizeof(cdr_copy->peeraccount)); + if (!ast_strlen_zero(cdr->party_b.userfield)) { + snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", cdr->party_a.userfield, cdr->party_b.userfield); + } + } + if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(cdr->party_a.userfield)) { + ast_copy_string(cdr_copy->userfield, cdr->party_a.userfield, sizeof(cdr_copy->userfield)); + } + + /* Timestamps/durations */ + cdr_copy->start = cdr->start; + cdr_copy->answer = cdr->answer; + cdr_copy->end = cdr->end; + cdr_copy->billsec = cdr_object_get_billsec(cdr); + cdr_copy->duration = cdr_object_get_duration(cdr); + + /* Flags and IDs */ + ast_copy_flags(cdr_copy, &cdr->flags, AST_FLAGS_ALL); + ast_copy_string(cdr_copy->linkedid, cdr->linkedid, sizeof(cdr_copy->linkedid)); + cdr_copy->disposition = cdr->disposition; + cdr_copy->sequence = cdr->sequence; + + /* Variables */ + copy_variables(&cdr_copy->varshead, &cdr->party_a.variables); + AST_LIST_TRAVERSE(&cdr->party_b.variables, it_var, entries) { + int found = 0; + AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) { + if (!strcmp(ast_var_name(it_var), ast_var_name(it_copy_var))) { + found = 1; + break; + } + } + if (!found) { + AST_LIST_INSERT_TAIL(&cdr_copy->varshead, ast_var_assign(ast_var_name(it_var), + ast_var_value(it_var)), entries); + } + } + + if (!pub_cdr) { + pub_cdr = cdr_copy; + cdr_prev = pub_cdr; + } else { + cdr_prev->next = cdr_copy; + cdr_prev = cdr_copy; + } + cdr = cdr->next; + } + + return pub_cdr; +} + +/*! + * \brief Dispatch a CDR. + * \param cdr The \ref cdr_object to dispatch + * + * This will create a \ref ast_cdr object and publish it to the various backends + */ +static void cdr_object_dispatch(struct cdr_object *cdr) +{ + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + struct ast_cdr *pub_cdr; + + CDR_DEBUG(mod_cfg, "%p - Dispatching CDR for Party A %s, Party B %s\n", cdr, + cdr->party_a.snapshot->name, + cdr->party_b.snapshot ? cdr->party_b.snapshot->name : "<none>"); + pub_cdr = cdr_object_create_public_records(cdr); + cdr_detach(pub_cdr); +} + +/*! + * \brief Set the disposition on a \ref cdr_object based on a hangupcause code + * \param cdr The \ref cdr_object + * \param hangupcause The Asterisk hangup cause code + */ +static void cdr_object_set_disposition(struct cdr_object *cdr, int hangupcause) +{ + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + + /* Change the disposition based on the hang up cause */ + switch (hangupcause) { + case AST_CAUSE_BUSY: + cdr->disposition = AST_CDR_BUSY; + break; + case AST_CAUSE_CONGESTION: + if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) { + cdr->disposition = AST_CDR_FAILED; + } else { + cdr->disposition = AST_CDR_CONGESTION; + } + break; + case AST_CAUSE_NO_ROUTE_DESTINATION: + case AST_CAUSE_UNREGISTERED: + cdr->disposition = AST_CDR_FAILED; + break; + case AST_CAUSE_NORMAL_CLEARING: + case AST_CAUSE_NO_ANSWER: + cdr->disposition = AST_CDR_NOANSWER; + break; + default: + break; + } +} + +/*! + * \brief Finalize a CDR. + * + * This function is safe to call multiple times. Note that you can call this + * explicitly before going to the finalized state if there's a chance the CDR + * will be re-activated, in which case the \ref cdr_object's end time should be + * cleared. This function is implicitly called when a CDR transitions to the + * finalized state and right before it is dispatched + * + * \param cdr_object The CDR to finalize + */ +static void cdr_object_finalize(struct cdr_object *cdr) +{ + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + + if (!ast_tvzero(cdr->end)) { + return; + } + cdr->end = ast_tvnow(); + + if (cdr->disposition == AST_CDR_NULL) { + if (!ast_tvzero(cdr->answer)) { + cdr->disposition = AST_CDR_ANSWERED; + } else if (cdr->party_a.snapshot->hangupcause) { + cdr_object_set_disposition(cdr, cdr->party_a.snapshot->hangupcause); + } else if (cdr->party_b.snapshot && cdr->party_b.snapshot->hangupcause) { + cdr_object_set_disposition(cdr, cdr->party_b.snapshot->hangupcause); + } else { + cdr->disposition = AST_CDR_FAILED; + } + } + + ast_debug(1, "Finalized CDR for %s - start %ld.%ld answer %ld.%ld end %ld.%ld dispo %s\n", + cdr->party_a.snapshot->name, + cdr->start.tv_sec, + cdr->start.tv_usec, + cdr->answer.tv_sec, + cdr->answer.tv_usec, + cdr->end.tv_sec, + cdr->end.tv_usec, + ast_cdr_disp2str(cdr->disposition)); +} + +/*! + * \brief Check to see if a CDR needs to move to the finalized state because + * its Party A hungup. + */ +static void cdr_object_check_party_a_hangup(struct cdr_object *cdr) +{ + if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE) + && cdr->fn_table != &finalized_state_fn_table) { + cdr_object_transition_state(cdr, &finalized_state_fn_table); + } +} + +/*! + * \brief Check to see if a CDR needs to be answered based on its Party A. + * Note that this is safe to call as much as you want - we won't answer twice + */ +static void cdr_object_check_party_a_answer(struct cdr_object *cdr) { + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + + if (cdr->party_a.snapshot->state == AST_STATE_UP && ast_tvzero(cdr->answer)) { + cdr->answer = ast_tvnow(); + CDR_DEBUG(mod_cfg, "%p - Set answered time to %ld.%ld\n", cdr, + cdr->answer.tv_sec, + cdr->answer.tv_usec); + } +} + +/*! + * \internal + * \brief Set a variable on a CDR object + * + * \param headp The header pointer to the variable to set + * \param name The name of the variable + * \param value The value of the variable + * + * CDRs that are in a hungup state cannot have their variables set. + */ +static void set_variable(struct varshead *headp, const char *name, const char *value) +{ + struct ast_var_t *newvariable; + + AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) { + if (!strcasecmp(ast_var_name(newvariable), name)) { + AST_LIST_REMOVE_CURRENT(entries); + ast_var_delete(newvariable); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + + if (value) { + newvariable = ast_var_assign(name, value); + AST_LIST_INSERT_HEAD(headp, newvariable, entries); + } +} + +/* \brief Set Caller ID information on a CDR */ +static void cdr_object_update_cid(struct cdr_object_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) +{ + if (!old_snapshot->snapshot) { + set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid); + set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr); + set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr); + return; + } + if (!strcmp(old_snapshot->snapshot->caller_dnid, new_snapshot->caller_dnid)) { + set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid); + } + if (!strcmp(old_snapshot->snapshot->caller_subaddr, new_snapshot->caller_subaddr)) { + set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr); + } + if (!strcmp(old_snapshot->snapshot->dialed_subaddr, new_snapshot->dialed_subaddr)) { + set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr); + } +} + +/*! + * \brief Swap an old \ref cdr_object_snapshot's \ref ast_channel_snapshot for + * a new \ref ast_channel_snapshot + * \param old_snapshot The old \ref cdr_object_snapshot + * \param new_snapshot The new \ref ast_channel_snapshot for old_snapshot + */ +static void cdr_object_swap_snapshot(struct cdr_object_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + cdr_object_update_cid(old_snapshot, new_snapshot); + if (old_snapshot->snapshot) { + ao2_t_ref(old_snapshot->snapshot, -1, "Drop ref for swap"); + } + ao2_t_ref(new_snapshot, +1, "Bump ref for swap"); + old_snapshot->snapshot = new_snapshot; +} + +/* BASE METHOD IMPLEMENTATIONS */ + +static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + + ast_assert(strcmp(snapshot->name, cdr->party_a.snapshot->name) == 0); + cdr_object_swap_snapshot(&cdr->party_a, snapshot); + + /* When Party A is originated to an application and the application exits, the stack + * will attempt to clear the application and restore the dummy originate application + * of "AppDialX". Prevent that, and any other application changes we might not want + * here. + */ + if (!ast_strlen_zero(snapshot->appl) && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))) { + ast_string_field_set(cdr, appl, snapshot->appl); + ast_string_field_set(cdr, data, snapshot->data); + } + + ast_string_field_set(cdr, linkedid, snapshot->linkedid); + cdr_object_check_party_a_answer(cdr); + cdr_object_check_party_a_hangup(cdr); + + return 0; +} + +static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) +{ + /* In general, most things shouldn't get a bridge leave */ + ast_assert(0); + return 1; +} + +static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status) +{ + /* In general, most things shouldn't get a dial end. */ + ast_assert(0); + return 0; +} + +/* SINGLE STATE */ + +static void single_state_init_function(struct cdr_object *cdr) { + cdr->start = ast_tvnow(); + cdr_object_check_party_a_answer(cdr); +} + +static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + /* This should never happen! */ + ast_assert(cdr->party_b.snapshot == NULL); + ast_assert(0); + return; +} + +static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + + if (caller && !strcmp(cdr->party_a.snapshot->name, caller->name)) { + cdr_object_swap_snapshot(&cdr->party_a, caller); + CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr, + cdr->party_a.snapshot->name); + cdr_object_swap_snapshot(&cdr->party_b, peer); + CDR_DEBUG(mod_cfg, "%p - Updated Party B %s snapshot\n", cdr, + cdr->party_b.snapshot->name); + } else if (!strcmp(cdr->party_a.snapshot->name, peer->name)) { + /* We're the entity being dialed, i.e., outbound origination */ + cdr_object_swap_snapshot(&cdr->party_a, peer); + CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr, + cdr->party_a.snapshot->name); + } + + cdr_object_transition_state(cdr, &dial_state_fn_table); + return 0; +} + +/*! + * \brief Handle a comparison between our \ref cdr_object and a \ref cdr_object + * already in the bridge while in the Single state. The goal of this is to find + * a Party B for our CDR. + * + * \param cdr Our \ref cdr_object in the Single state + * \param cand_cdr The \ref cdr_object already in the Bridge state + * + * \retval 0 The cand_cdr had a Party A or Party B that we could use as our + * Party B + * \retval 1 No party in the cand_cdr could be used as our Party B + */ +static int single_state_bridge_enter_comparison(struct cdr_object *cdr, + struct cdr_object *cand_cdr) +{ + struct cdr_object_snapshot *party_a; + + /* Try the candidate CDR's Party A first */ + party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a); + if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) { + cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a); + if (!cand_cdr->party_b.snapshot) { + /* We just stole them - finalize their CDR. Note that this won't + * transition their state, it just sets the end time and the + * disposition - if we need to re-activate them later, we can. + */ + cdr_object_finalize(cand_cdr); + } + return 0; + } + + /* Try their Party B */ + if (!cand_cdr->party_b.snapshot) { + return 1; + } + party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b); + if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) { + cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b); + return 0; + } + + return 1; +} + +static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) +{ + struct ao2_iterator *it_cdrs; + struct cdr_object *cand_cdr_master; + char *bridge_id = ast_strdupa(bridge->uniqueid); + int success = 1; + + ast_string_field_set(cdr, bridge, bridge->uniqueid); + + /* Get parties in the bridge */ + it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY, + cdr_object_bridge_cmp_fn, bridge_id); + if (!it_cdrs) { + /* No one in the bridge yet! */ + cdr_object_transition_state(cdr, &bridge_state_fn_table); + return 0; + } + + while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) { + struct cdr_object *cand_cdr; + + ao2_lock(cand_cdr_master); + for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) { + /* Skip any records that are not in a bridge or in this bridge. + * I'm not sure how that would happen, but it pays to be careful. */ + if (cand_cdr->fn_table != &bridge_state_fn_table || + strcmp(cdr->bridge, cand_cdr->bridge)) { + continue; + } + + if (single_state_bridge_enter_comparison(cdr, cand_cdr)) { + continue; + } + /* We successfully got a party B - break out */ + success = 0; + break; + } + ao2_unlock(cand_cdr_master); + ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference"); + } + ao2_iterator_destroy(it_cdrs); + + /* We always transition state, even if we didn't get a peer */ + cdr_object_transition_state(cdr, &bridge_state_fn_table); + + /* Success implies that we have a Party B */ + return success; +} + +/* DIAL STATE */ + +static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + ast_assert(snapshot != NULL); + + if (!cdr->party_b.snapshot || strcmp(cdr->party_b.snapshot->name, snapshot->name)) { + return; + } + cdr_object_swap_snapshot(&cdr->party_b, snapshot); + + /* If party B hangs up, finalize this CDR */ + if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) { + cdr_object_transition_state(cdr, &finalized_state_fn_table); + } +} + +static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer) +{ + /* Don't process a begin dial here. A party A already in the dial state will + * who receives a dial begin for something else will be handled by the + * message router callback and will add a new CDR for the party A */ + return 1; +} + +/*! \internal \brief Convert a dial status to a CDR disposition */ +static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_status) +{ + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + + if (!strcmp(dial_status, "ANSWER")) { + return AST_CDR_ANSWERED; + } else if (!strcmp(dial_status, "BUSY")) { + return AST_CDR_BUSY; + } else if (!strcmp(dial_status, "CANCEL") || !strcmp(dial_status, "NOANSWER")) { + return AST_CDR_NOANSWER; + } else if (!strcmp(dial_status, "CONGESTION")) { + if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) { + return AST_CDR_FAILED; + } else { + return AST_CDR_CONGESTION; + } + } else if (!strcmp(dial_status, "FAILED")) { + return AST_CDR_FAILED; + } + return AST_CDR_FAILED; +} + +static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status) +{ + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + struct ast_channel_snapshot *party_a; + + if (caller) { + party_a = caller; + } else { + party_a = peer; + } + ast_assert(!strcmp(cdr->party_a.snapshot->name, party_a->name)); + cdr_object_swap_snapshot(&cdr->party_a, party_a); + + if (cdr->party_b.snapshot) { + if (strcmp(cdr->party_b.snapshot->name, peer->name)) { + /* Not the status for this CDR - defer back to the message router */ + return 1; + } + cdr_object_swap_snapshot(&cdr->party_b, peer); + } + + /* Set the disposition based on the dial string. */ + cdr->disposition = dial_status_to_disposition(dial_status); + if (cdr->disposition == AST_CDR_ANSWERED) { + /* Switch to dial pending to wait and see what the caller does */ + cdr_object_transition_state(cdr, &dialed_pending_state_fn_table); + } else { + cdr_object_transition_state(cdr, &finalized_state_fn_table); + } + + return 0; +} + +static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) +{ + struct ao2_iterator *it_cdrs; + char *bridge_id = ast_strdupa(bridge->uniqueid); + struct cdr_object *cand_cdr_master; + int success = 1; + + ast_string_field_set(cdr, bridge, bridge->uniqueid); + + /* Get parties in the bridge */ + it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY, + cdr_object_bridge_cmp_fn, bridge_id); + if (!it_cdrs) { + /* No one in the bridge yet! */ + cdr_object_transition_state(cdr, &bridge_state_fn_table); + return 0; + } + + while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) { + struct cdr_object *cand_cdr; + + ao2_lock(cand_cdr_master); + for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) { + /* Skip any records that are not in a bridge or in this bridge. + * I'm not sure how that would happen, but it pays to be careful. */ + if (cand_cdr->fn_table != &bridge_state_fn_table || + strcmp(cdr->bridge, cand_cdr->bridge)) { + continue; + } + + /* Skip any records that aren't our Party B */ + if (strcmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) { + continue; + } + + cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a); + /* If they have a Party B, they joined up with someone else as their + * Party A. Don't finalize them as they're active. Otherwise, we + * have stolen them so they need to be finalized. + */ + if (!cand_cdr->party_b.snapshot) { + cdr_object_finalize(cand_cdr); + } + success = 0; + break; + } + ao2_unlock(cand_cdr_master); + ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference"); + } + ao2_iterator_destroy(it_cdrs); + + /* We always transition state, even if we didn't get a peer */ + cdr_object_transition_state(cdr, &bridge_state_fn_table); + + /* Success implies that we have a Party B */ + return success; +} + +/* DIALED PENDING STATE */ + +static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + /* If we get a CEP change, we're executing dialplan. If we have a Party B + * that means we need a new CDR; otherwise, switch us over to single. + */ + if (strcmp(snapshot->context, cdr->party_a.snapshot->context) + || strcmp(snapshot->exten, cdr->party_a.snapshot->exten) + || snapshot->priority != cdr->party_a.snapshot->priority + || strcmp(snapshot->appl, cdr->party_a.snapshot->appl)) { + if (cdr->party_b.snapshot) { + cdr_object_transition_state(cdr, &finalized_state_fn_table); + cdr->fn_table->process_party_a(cdr, snapshot); + return 1; + } else { + cdr_object_transition_state(cdr, &single_state_fn_table); + cdr->fn_table->process_party_a(cdr, snapshot); + return 0; + } + } + base_process_party_a(cdr, snapshot); + return 0; +} + +static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) +{ + cdr_object_transition_state(cdr, &dial_state_fn_table); + return cdr->fn_table->process_bridge_enter(cdr, bridge, channel); +} + +static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer) +{ + struct cdr_object *new_cdr; + + cdr_object_transition_state(cdr, &finalized_state_fn_table); + new_cdr = cdr_object_create_and_append(cdr); + cdr_object_transition_state(cdr, &single_state_fn_table); + return new_cdr->fn_table->process_dial_begin(cdr, caller, peer); +} + +/* BRIDGE STATE */ + +static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + if (!cdr->party_b.snapshot || strcmp(cdr->party_b.snapshot->name, snapshot->name)) { + return; + } + cdr_object_swap_snapshot(&cdr->party_b, snapshot); + + /* If party B hangs up, finalize this CDR */ + if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) { + cdr_object_transition_state(cdr, &finalized_state_fn_table); + } +} + +static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) +{ + if (strcmp(cdr->bridge, bridge->uniqueid)) { + return 1; + } + if (strcmp(cdr->party_a.snapshot->name, channel->name) + && cdr->party_b.snapshot + && strcmp(cdr->party_b.snapshot->name, channel->name)) { + return 1; + } + cdr_object_transition_state(cdr, &finalized_state_fn_table); + + return 0; +} + +/* PENDING STATE */ + +static void pending_state_init_function(struct cdr_object *cdr) +{ + ast_cdr_set_property(cdr->name, AST_CDR_FLAG_DISABLE); +} + +static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + if (ast_test_flag(&snapshot->flags, AST_FLAG_ZOMBIE)) { + return 0; + } + + /* Ignore if we don't get a CEP change */ + if (!strcmp(snapshot->context, cdr->party_a.snapshot->context) + && !strcmp(snapshot->exten, cdr->party_a.snapshot->exten) + && snapshot->priority == cdr->party_a.snapshot->priority) { + return 0; + } + + cdr_object_transition_state(cdr, &single_state_fn_table); + ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE); + cdr->fn_table->process_party_a(cdr, snapshot); + return 0; +} + +static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer) +{ + cdr_object_transition_state(cdr, &single_state_fn_table); + ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE); + return cdr->fn_table->process_dial_begin(cdr, caller, peer); +} + +static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel) +{ + cdr_object_transition_state(cdr, &single_state_fn_table); + ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE); + return cdr->fn_table->process_bridge_enter(cdr, bridge, channel); +} + +/* FINALIZED STATE */ + +static void finalized_state_init_function(struct cdr_object *cdr) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + + if (!ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) { + return; + } + + cdr_object_finalize(cdr); +} + +static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot) +{ + if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)) { + cdr_object_finalize(cdr); + } + + /* Indicate that, if possible, we should get a new CDR */ + return 1; +} + +/* TOPIC ROUTER CALLBACKS */ + +/*! + * \brief Handler for Stasis-Core dial messages + * \param data Passed on + * \param sub The stasis subscription for this message callback + * \param topic The topic this message was published for + * \param message The message + */ +static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + RAII_VAR(struct cdr_object *, cdr_caller, NULL, ao2_cleanup); + RAII_VAR(struct cdr_object *, cdr_peer, NULL, ao2_cleanup); + struct cdr_object *cdr; + struct ast_multi_channel_blob *payload = stasis_message_data(message); + struct ast_channel_snapshot *caller; + struct ast_channel_snapshot *peer; + struct cdr_object_snapshot *party_a; + struct cdr_object_snapshot *party_b; + struct cdr_object *it_cdr; + struct ast_json *dial_status_blob; + const char *dial_status = NULL; + int res = 1; + + CDR_DEBUG(mod_cfg, "Dial message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec); + ast_assert(payload != NULL); + + caller = ast_multi_channel_blob_get_channel(payload, "caller"); + peer = ast_multi_channel_blob_get_channel(payload, "peer"); + if (!peer && !caller) { + return; + } + dial_status_blob = ast_json_object_get(ast_multi_channel_blob_get_json(payload), "dialstatus"); + if (dial_status_blob) { + dial_status = ast_json_string_get(dial_status_blob); + } + + /* Figure out who is running this show */ + if (caller) { + cdr_caller = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY); + } + if (peer) { + cdr_peer = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY); + } + if (cdr_caller && cdr_peer) { + party_a = cdr_object_pick_party_a(&cdr_caller->party_a, &cdr_peer->party_a); + if (!strcmp(party_a->snapshot->name, cdr_caller->party_a.snapshot->name)) { + cdr = cdr_caller; + party_b = &cdr_peer->party_a; + } else { + cdr = cdr_peer; + party_b = &cdr_caller->party_a; + } + } else if (cdr_caller) { + cdr = cdr_caller; + party_a = &cdr_caller->party_a; + party_b = NULL; + } else if (cdr_peer) { + cdr = cdr_peer; + party_a = NULL; + party_b = &cdr_peer->party_a; + } else { + return; + } + + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (ast_strlen_zero(dial_status)) { + if (!it_cdr->fn_table->process_dial_begin) { + continue; + } + CDR_DEBUG(mod_cfg, "%p - Processing Dial Begin message for channel %s, peer %s\n", + cdr, + party_a ? party_a->snapshot->name : "(none)", + party_b ? party_b->snapshot->name : "(none)"); + res &= it_cdr->fn_table->process_dial_begin(it_cdr, + party_a ? party_a->snapshot : NULL, + party_b ? party_b->snapshot : NULL); + } else { + if (!it_cdr->fn_table->process_dial_end) { + continue; + } + CDR_DEBUG(mod_cfg, "%p - Processing Dial End message for channel %s, peer %s\n", + cdr, + party_a ? party_a->snapshot->name : "(none)", + party_b ? party_b->snapshot->name : "(none)"); + it_cdr->fn_table->process_dial_end(it_cdr, + party_a ? party_a->snapshot : NULL, + party_b ? party_b->snapshot : NULL, + dial_status); + } + } + + /* If no CDR handled a dial begin message, make a new one */ + if (res && ast_strlen_zero(dial_status)) { + struct cdr_object *new_cdr; + + new_cdr = cdr_object_create_and_append(cdr); + if (!new_cdr) { + return; + } + new_cdr->fn_table->process_dial_begin(new_cdr, + party_a ? party_a->snapshot : NULL, + party_b ? party_b->snapshot : NULL); + } + ao2_unlock(cdr); +} + +static int cdr_object_finalize_party_b(void *obj, void *arg, int flags) +{ + struct cdr_object *cdr = obj; + struct ast_channel_snapshot *party_b = arg; + struct cdr_object *it_cdr; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->party_b.snapshot && !strcmp(it_cdr->party_b.snapshot->name, party_b->name)) { + /* Don't transition to the finalized state - let the Party A do + * that when its ready + */ + cdr_object_finalize(it_cdr); + } + } + return 0; +} + +static int cdr_object_update_party_b(void *obj, void *arg, int flags) +{ + struct cdr_object *cdr = obj; + struct ast_channel_snapshot *party_b = arg; + struct cdr_object *it_cdr; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (!it_cdr->fn_table->process_party_b) { + continue; + } + if (it_cdr->party_b.snapshot && !strcmp(it_cdr->party_b.snapshot->name, party_b->name)) { + it_cdr->fn_table->process_party_b(it_cdr, party_b); + } + } + return 0; +} + +/*! \internal \brief Filter channel snapshots by technology */ +static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot) +{ + if (!strncmp(snapshot->name, "CBAnn", 5) || + !strncmp(snapshot->name, "CBRec", 5)) { + return 1; + } + return 0; +} + +/*! \internal \brief Filter a channel cache update */ +static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + int ret = 0; + + /* Drop cache updates from certain channel technologies */ + if (old_snapshot) { + ret |= filter_channel_snapshot(old_snapshot); + } + if (new_snapshot) { + ret |= filter_channel_snapshot(new_snapshot); + } + + return ret; +} + +/*! \brief Determine if we need to add a new CDR based on snapshots */ +static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + + if (!new_snapshot) { + return 0; + } + + if (ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE)) { + return 0; + } + + /* Auto-fall through will increment the priority but have no application */ + if (ast_strlen_zero(new_snapshot->appl)) { + return 0; + } + + if (old_snapshot && !strcmp(old_snapshot->context, new_snapshot->context) + && !strcmp(old_snapshot->exten, new_snapshot->exten) + && old_snapshot->priority == new_snapshot->priority + && !(strcmp(old_snapshot->appl, new_snapshot->appl))) { + return 0; + } + + return 1; +} + +/*! + * \brief Handler for Stasis-Core channel cache update messages + * \param data Passed on + * \param sub The stasis subscription for this message callback + * \param topic The topic this message was published for + * \param message The message + */ +static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message) +{ + RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup); + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + struct stasis_cache_update *update = stasis_message_data(message); + struct ast_channel_snapshot *old_snapshot; + struct ast_channel_snapshot *new_snapshot; + const char *name; + struct cdr_object *it_cdr; + + ast_assert(update != NULL); + if (ast_channel_snapshot_type() != update->type) { + return; + } + + old_snapshot = stasis_message_data(update->old_snapshot); + new_snapshot = stasis_message_data(update->new_snapshot); + name = new_snapshot ? new_snapshot->name : old_snapshot->name; + + if (filter_channel_cache_message(old_snapshot, new_snapshot)) { + return; + } + + CDR_DEBUG(mod_cfg, "Channel Update message for %s: %u.%08u\n", + name, + (unsigned int)stasis_message_timestamp(message)->tv_sec, + (unsigned int)stasis_message_timestamp(message)->tv_usec); + + if (new_snapshot && !old_snapshot) { + cdr = cdr_object_alloc(new_snapshot); + if (!cdr) { + return; + } + ao2_link(active_cdrs_by_channel, cdr); + } + + /* Handle Party A */ + if (!cdr) { + cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY); + } + if (!cdr) { + ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name); + } else { + ao2_lock(cdr); + if (new_snapshot) { + int all_reject = 1; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (!it_cdr->fn_table->process_party_a) { + continue; + } + CDR_DEBUG(mod_cfg, "%p - Processing new channel snapshot %s\n", it_cdr, new_snapshot->name); + all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot); + } + if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) { + /* We're not hung up and we have a new snapshot - we need a new CDR */ + struct cdr_object *new_cdr; + new_cdr = cdr_object_create_and_append(cdr); + new_cdr->fn_table->process_party_a(new_cdr, new_snapshot); + } + } else { + CDR_DEBUG(mod_cfg, "%p - Beginning finalize/dispatch for %s\n", cdr, old_snapshot->name); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + cdr_object_finalize(it_cdr); + } + cdr_object_dispatch(cdr); + ao2_unlink(active_cdrs_by_channel, cdr); + } + ao2_unlock(cdr); + } + + /* Handle Party B */ + if (new_snapshot) { + ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_update_party_b, + new_snapshot); + } else { + ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_finalize_party_b, + old_snapshot); + } + +} + +struct bridge_leave_data { + struct ast_bridge_snapshot *bridge; + struct ast_channel_snapshot *channel; }; -static AST_RWLIST_HEAD_STATIC(be_list, ast_cdr_beitem); +/*! \brief Callback used to notify CDRs of a Party B leaving the bridge */ +static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, int flags) +{ + struct cdr_object *cdr = obj; + struct bridge_leave_data *leave_data = arg; + struct cdr_object *it_cdr; -struct ast_cdr_batch_item { - struct ast_cdr *cdr; - struct ast_cdr_batch_item *next; + if (strcmp(cdr->bridge, leave_data->bridge->uniqueid)) { + return 0; + } + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table != &bridge_state_fn_table) { + continue; + } + if (!it_cdr->party_b.snapshot) { + continue; + } + if (strcmp(it_cdr->party_b.snapshot->name, leave_data->channel->name)) { + continue; + } + if (!it_cdr->fn_table->process_bridge_leave(it_cdr, leave_data->bridge, leave_data->channel)) { + /* Update the end times for this CDR. We don't want to actually + * finalize it, as the Party A will eventually need to leave, which + * will switch the records to pending bridged. + */ + cdr_object_finalize(it_cdr); + } + } + return 0; +} + +/*! \brief Filter bridge messages based on bridge technology */ +static int filter_bridge_messages(struct ast_bridge_snapshot *bridge) +{ + /* Ignore holding bridge technology messages. We treat this simply as an application + * that a channel enters into. + */ + if (!strcmp(bridge->technology, "holding_bridge")) { + return 1; + } + return 0; +} + +/*! + * \brief Handler for when a channel leaves a bridge + * \param bridge The \ref ast_bridge_snapshot representing the bridge + * \param channel The \ref ast_channel_snapshot representing the channel + */ +static void handle_bridge_leave_message(void *data, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *message) +{ + struct ast_bridge_blob *update = stasis_message_data(message); + struct ast_bridge_snapshot *bridge = update->bridge; + struct ast_channel_snapshot *channel = update->channel; + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY), + ao2_cleanup); + struct cdr_object *it_cdr; + struct cdr_object *pending_cdr; + struct bridge_leave_data leave_data = { + .bridge = bridge, + .channel = channel, + }; + int left_bridge = 0; + + if (filter_bridge_messages(bridge)) { + return; + } + + CDR_DEBUG(mod_cfg, "Bridge Leave message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec); + + if (!cdr) { + ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name); + return; + } + + /* Party A */ + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (!it_cdr->fn_table->process_bridge_leave) { + continue; + } + CDR_DEBUG(mod_cfg, "%p - Processing Bridge Leave for %s\n", + it_cdr, channel->name); + if (!it_cdr->fn_table->process_bridge_leave(it_cdr, bridge, channel)) { + ast_string_field_set(it_cdr, bridge, ""); + left_bridge = 1; + } + } + if (!left_bridge) { + ao2_unlock(cdr); + return; + } + + ao2_unlink(active_cdrs_by_bridge, cdr); + + /* Create a new pending record. If the channel decides to do something else, + * the pending record will handle it - otherwise, if gets dropped. + */ + pending_cdr = cdr_object_create_and_append(cdr); + cdr_object_transition_state(pending_cdr, &bridged_pending_state_fn_table); + ao2_unlock(cdr); + + /* Party B */ + ao2_callback(active_cdrs_by_bridge, OBJ_NODATA, + cdr_object_party_b_left_bridge_cb, + &leave_data); +} + +struct bridge_candidate { + struct cdr_object *cdr; /*!< The actual CDR this candidate belongs to, either as A or B */ + struct cdr_object_snapshot candidate; /*!< The candidate for a new pairing */ }; -static struct ast_cdr_batch { - int size; - struct ast_cdr_batch_item *head; - struct ast_cdr_batch_item *tail; -} *batch = NULL; +/*! \internal + * \brief Comparison function for \ref bridge_candidate objects + */ +static int bridge_candidate_cmp_fn(void *obj, void *arg, int flags) +{ + struct bridge_candidate *left = obj; + struct bridge_candidate *right = arg; + const char *match = (flags & OBJ_KEY) ? arg : right->candidate.snapshot->name; + return strcasecmp(left->candidate.snapshot->name, match) ? 0 : (CMP_MATCH | CMP_STOP); +} + +/*! \internal + * \brief Hash function for \ref bridge_candidate objects + */ +static int bridge_candidate_hash_fn(const void *obj, const int flags) +{ + const struct bridge_candidate *bc = obj; + const char *id = (flags & OBJ_KEY) ? obj : bc->candidate.snapshot->name; + return ast_str_case_hash(id); +} + +/*! \brief \ref bridge_candidate Destructor */ +static void bridge_candidate_dtor(void *obj) +{ + struct bridge_candidate *bcand = obj; + ao2_cleanup(bcand->cdr); + ao2_cleanup(bcand->candidate.snapshot); + free_variables(&bcand->candidate.variables); +} +/*! + * \brief \ref bridge_candidate Constructor + * \param cdr The \ref cdr_object that is a candidate for being compared to in + * a bridge operation + * \param candidate The \ref cdr_object_snapshot candidate snapshot in the CDR + * that should be used during the operaton + */ +static struct bridge_candidate *bridge_candidate_alloc(struct cdr_object *cdr, struct cdr_object_snapshot *candidate) +{ + struct bridge_candidate *bcand; -static int cdr_sequence = 0; + bcand = ao2_alloc(sizeof(*bcand), bridge_candidate_dtor); + if (!bcand) { + return NULL; + } + bcand->cdr = cdr; + ao2_ref(bcand->cdr, +1); + bcand->candidate.flags = candidate->flags; + strcpy(bcand->candidate.userfield, candidate->userfield); + bcand->candidate.snapshot = candidate->snapshot; + ao2_ref(bcand->candidate.snapshot, +1); + copy_variables(&bcand->candidate.variables, &candidate->variables); -static int cdr_seq_inc(struct ast_cdr *cdr); + return bcand; +} -static struct ast_sched_context *sched; -static int cdr_sched = -1; -static pthread_t cdr_thread = AST_PTHREADT_NULL; +/*! + * \internal \brief Build and add bridge candidates based on a CDR + * \param bridge_id The ID of the bridge we need candidates for + * \param candidates The container of \ref bridge_candidate objects + * \param cdr The \ref cdr_object that is our candidate + * \param party_a Non-zero if we should look at the Party A channel; 0 if Party B + */ +static void add_candidate_for_bridge(const char *bridge_id, + struct ao2_container *candidates, + struct cdr_object *cdr, + int party_a) +{ + struct cdr_object *it_cdr; -static int enabled; -static const int ENABLED_DEFAULT = 1; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + struct cdr_object_snapshot *party_snapshot; + RAII_VAR(struct bridge_candidate *, bcand, NULL, ao2_cleanup); -static int batchmode; -static const int BATCHMODE_DEFAULT = 0; + party_snapshot = party_a ? &it_cdr->party_a : &it_cdr->party_b; -static int unanswered; -static const int UNANSWERED_DEFAULT = 0; + if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge_id, it_cdr->bridge)) { + continue; + } -static int congestion; -static const int CONGESTION_DEFAULT = 0; + if (!party_snapshot->snapshot) { + continue; + } -static int batchsize; -static const int BATCH_SIZE_DEFAULT = 100; + /* Don't add a party twice */ + bcand = ao2_find(candidates, party_snapshot->snapshot->name, OBJ_KEY); + if (bcand) { + continue; + } -static int batchtime; -static const int BATCH_TIME_DEFAULT = 300; + bcand = bridge_candidate_alloc(it_cdr, party_snapshot); + if (bcand) { + ao2_link(candidates, bcand); + } + } +} -static int batchscheduleronly; -static const int BATCH_SCHEDULER_ONLY_DEFAULT = 0; +/*! + * \brief Create new \ref bridge_candidate objects for each party currently + * in a bridge + * \param bridge The \param ast_bridge_snapshot for the bridge we're processing + * + * Note that we use two passes here instead of one so that we only create a + * candidate for a party B if they are never a party A in the bridge. Otherwise, + * we don't care about them. + */ +static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snapshot *bridge) +{ + struct ao2_container *candidates = ao2_container_alloc(51, bridge_candidate_hash_fn, bridge_candidate_cmp_fn); + char *bridge_id = ast_strdupa(bridge->uniqueid); + struct ao2_iterator *it_cdrs; + struct cdr_object *cand_cdr_master; -static int batchsafeshutdown; -static const int BATCH_SAFE_SHUTDOWN_DEFAULT = 1; + if (!candidates) { + return NULL; + } -AST_MUTEX_DEFINE_STATIC(cdr_sched_lock); + /* For each CDR that has a record in the bridge, get their Party A and + * make them a candidate. Note that we do this in two passes as opposed to one so + * that we give preference CDRs where the channel is Party A */ + it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY, + cdr_object_bridge_cmp_fn, bridge_id); + if (!it_cdrs) { + /* No one in the bridge yet! */ + ao2_cleanup(candidates); + return NULL; + } + while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) { + SCOPED_AO2LOCK(lock, cand_cdr_master); + add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 1); + } + ao2_iterator_destroy(it_cdrs); -AST_MUTEX_DEFINE_STATIC(cdr_batch_lock); + /* For each CDR that has a record in the bridge, get their Party B and + * make them a candidate. */ + it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY, + cdr_object_bridge_cmp_fn, bridge_id); + if (!it_cdrs) { + /* Now it's just an error. */ + ao2_cleanup(candidates); + return NULL; + } + while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) { + SCOPED_AO2LOCK(lock, cand_cdr_master); + add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 0); + } + ao2_iterator_destroy(it_cdrs); -/* these are used to wake up the CDR thread when there's work to do */ -AST_MUTEX_DEFINE_STATIC(cdr_pending_lock); -static ast_cond_t cdr_pending_cond; + return candidates; +} + +/*! + * \internal \brief Create a new CDR, append it to an existing CDR, and update its snapshots + * \note The new CDR will be automatically transitioned to the bridge state + */ +static void bridge_candidate_add_to_cdr(struct cdr_object *cdr, + const char *bridge_id, + struct cdr_object_snapshot *party_b) +{ + struct cdr_object *new_cdr; + + new_cdr = cdr_object_create_and_append(cdr); + cdr_object_snapshot_copy(&new_cdr->party_b, party_b); + cdr_object_check_party_a_answer(new_cdr); + ast_string_field_set(new_cdr, bridge, cdr->bridge); + cdr_object_transition_state(new_cdr, &bridge_state_fn_table); +} + +/*! + * \brief Process a single \ref bridge_candidate. Note that this is called as + * part of an \ref ao2_callback on an \ref ao2_container of \ref bridge_candidate + * objects previously created by \ref create_candidates_for_bridge. + * + * \param obj The \ref bridge_candidate being processed + * \param arg The \ref cdr_object that is being compared against the candidates + * + * The purpose of this function is to create the necessary CDR entries as a + * result of \ref cdr_object having entered the same bridge as the CDR + * represented by \ref bridge_candidate. + */ +static int bridge_candidate_process(void *obj, void *arg, int flags) +{ + struct bridge_candidate *bcand = obj; + struct cdr_object *cdr = arg; + struct cdr_object_snapshot *party_a; + + /* If the candidate is us or someone we've taken on, pass on by */ + if (!strcmp(cdr->party_a.snapshot->name, bcand->candidate.snapshot->name) + || (cdr->party_b.snapshot && !(strcmp(cdr->party_b.snapshot->name, bcand->candidate.snapshot->name)))) { + return 0; + } + + party_a = cdr_object_pick_party_a(&cdr->party_a, &bcand->candidate); + /* We're party A - make a new CDR, append it to us, and set the candidate as + * Party B */ + if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) { + bridge_candidate_add_to_cdr(cdr, cdr->bridge, &bcand->candidate); + return 0; + } + + /* We're Party B. Check if the candidate is the CDR's Party A. If so, find out if we + * can add ourselves directly as the Party B, or if we need a new CDR. */ + if (!strcmp(bcand->cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)) { + if (bcand->cdr->party_b.snapshot + && strcmp(bcand->cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) { + bridge_candidate_add_to_cdr(bcand->cdr, cdr->bridge, &cdr->party_a); + } else { + cdr_object_snapshot_copy(&bcand->cdr->party_b, &cdr->party_a); + /* It's possible that this joined at one point and was never chosen + * as party A. Clear their end time, as it would be set in such a + * case. + */ + memset(&bcand->cdr->end, 0, sizeof(bcand->cdr->end)); + } + } else { + /* We are Party B to a candidate CDR's Party B. Since a candidate + * CDR will only have a Party B represented here if that channel + * was never a Party A in the bridge, we have to go looking for + * that channel's primary CDR record. + */ + struct cdr_object *b_party = ao2_find(active_cdrs_by_channel, bcand->candidate.snapshot->name, OBJ_KEY); + if (!b_party) { + /* Holy cow - no CDR? */ + b_party = cdr_object_alloc(bcand->candidate.snapshot); + cdr_object_snapshot_copy(&b_party->party_a, &bcand->candidate); + cdr_object_snapshot_copy(&b_party->party_b, &cdr->party_a); + cdr_object_check_party_a_answer(b_party); + ast_string_field_set(b_party, bridge, cdr->bridge); + cdr_object_transition_state(b_party, &bridge_state_fn_table); + ao2_link(active_cdrs_by_channel, b_party); + } else { + bridge_candidate_add_to_cdr(b_party, cdr->bridge, &cdr->party_a); + } + ao2_link(active_cdrs_by_bridge, b_party); + ao2_ref(b_party, -1); + } + + return 0; +} -int check_cdr_enabled(void) +/*! + * \brief Handle creating bridge pairings for the \ref cdr_object that just + * entered a bridge + * \param cdr The \ref cdr_object that just entered the bridge + * \param bridge The \ref ast_bridge_snapshot representing the bridge it just entered + */ +static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge) { - return enabled; + RAII_VAR(struct ao2_container *, candidates, + create_candidates_for_bridge(bridge), + ao2_cleanup); + + if (!candidates) { + return; + } + + ao2_callback(candidates, OBJ_NODATA, + bridge_candidate_process, + cdr); + + return; } /*! - * \brief Register a CDR driver. Each registered CDR driver generates a CDR - * \retval 0 on success. - * \retval -1 on error + * \brief Handler for Stasis-Core bridge enter messages + * \param data Passed on + * \param sub The stasis subscription for this message callback + * \param topic The topic this message was published for + * \param message The message - hopefully a bridge one! */ +static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub, + struct stasis_topic *topic, struct stasis_message *message) +{ + struct ast_bridge_blob *update = stasis_message_data(message); + struct ast_bridge_snapshot *bridge = update->bridge; + struct ast_channel_snapshot *channel = update->channel; + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY), + ao2_cleanup); + RAII_VAR(struct module_config *, mod_cfg, + ao2_global_obj_ref(module_configs), ao2_cleanup); + int res = 1; + struct cdr_object *it_cdr; + struct cdr_object *handled_cdr = NULL; + + if (filter_bridge_messages(bridge)) { + return; + } + + CDR_DEBUG(mod_cfg, "Bridge Enter message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec); + + if (!cdr) { + ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name); + return; + } + + ao2_lock(cdr); + + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table->process_party_a) { + CDR_DEBUG(mod_cfg, "%p - Updating Party A %s snapshot\n", it_cdr, + channel->name); + it_cdr->fn_table->process_party_a(it_cdr, channel); + } + + /* Notify all states that they have entered a bridge */ + if (it_cdr->fn_table->process_bridge_enter) { + CDR_DEBUG(mod_cfg, "%p - Processing bridge enter for %s\n", it_cdr, + channel->name); + res &= it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel); + if (!res && !handled_cdr) { + handled_cdr = it_cdr; + } + } + } + + if (res) { + /* We didn't win on any - end this CDR. If someone else comes in later + * that is Party B to this CDR, it can re-activate this CDR. + */ + cdr_object_finalize(cdr); + } + + /* Create the new matchings, but only for either: + * * The first CDR in the chain that handled it. This avoids issues with + * forked CDRs. + * * If no one handled it, the last CDR in the chain. This would occur if + * a CDR joined a bridge and it wasn't Party A for anyone. We still need + * to make pairings with everyone in the bridge. + */ + if (!handled_cdr) { + handled_cdr = cdr->last; + } + handle_bridge_pairings(handled_cdr, bridge); + + ao2_link(active_cdrs_by_bridge, cdr); + ao2_unlock(cdr); +} + +struct ast_cdr_config *ast_cdr_get_config(void) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + ao2_ref(mod_cfg->general, +1); + return mod_cfg->general; +} + +void ast_cdr_set_config(struct ast_cdr_config *config) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + ao2_cleanup(mod_cfg->general); + mod_cfg->general = config; + ao2_ref(mod_cfg->general, +1); +} + +int ast_cdr_is_enabled(void) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED); +} + int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be) { - struct ast_cdr_beitem *i = NULL; + struct cdr_beitem *i = NULL; if (!name) return -1; @@ -175,10 +2469,9 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be) return 0; } -/*! unregister a CDR driver */ void ast_cdr_unregister(const char *name) { - struct ast_cdr_beitem *i = NULL; + struct cdr_beitem *i = NULL; AST_RWLIST_WRLOCK(&be_list); AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) { @@ -187,82 +2480,51 @@ void ast_cdr_unregister(const char *name) break; } } - AST_RWLIST_TRAVERSE_SAFE_END; - AST_RWLIST_UNLOCK(&be_list); - - if (i) { - ast_verb(2, "Unregistered '%s' CDR backend\n", name); - ast_free(i); - } -} - -int ast_cdr_isset_unanswered(void) -{ - return unanswered; -} - -int ast_cdr_isset_congestion(void) -{ - return congestion; -} - -struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr) -{ - struct ast_cdr *newcdr = ast_cdr_dup(cdr); - if (!newcdr) - return NULL; - - cdr_seq_inc(newcdr); - return newcdr; -} - -struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr) -{ - struct ast_cdr *newcdr = ast_cdr_dup(cdr); - if (!newcdr) - return NULL; + AST_RWLIST_TRAVERSE_SAFE_END; + AST_RWLIST_UNLOCK(&be_list); - cdr_seq_inc(cdr); - return newcdr; + if (i) { + ast_verb(2, "Unregistered '%s' CDR backend\n", name); + ast_free(i); + } } -/*! Duplicate a CDR record - \returns Pointer to new CDR record -*/ struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr) { struct ast_cdr *newcdr; - if (!cdr) /* don't die if we get a null cdr pointer */ + if (!cdr) { return NULL; + } newcdr = ast_cdr_alloc(); - if (!newcdr) + if (!newcdr) { return NULL; + } memcpy(newcdr, cdr, sizeof(*newcdr)); - /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */ memset(&newcdr->varshead, 0, sizeof(newcdr->varshead)); - ast_cdr_copy_vars(newcdr, cdr); + copy_variables(&newcdr->varshead, &cdr->varshead); newcdr->next = NULL; return newcdr; } -static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur) +static const char *cdr_format_var_internal(struct ast_cdr *cdr, const char *name) { - if (ast_strlen_zero(name)) + struct ast_var_t *variables; + struct varshead *headp = &cdr->varshead; + + if (ast_strlen_zero(name)) { return NULL; + } - for (; cdr; cdr = recur ? cdr->next : NULL) { - struct ast_var_t *variables; - struct varshead *headp = &cdr->varshead; - AST_LIST_TRAVERSE(headp, variables, entries) { - if (!strcasecmp(name, ast_var_name(variables))) - return ast_var_value(variables); + AST_LIST_TRAVERSE(headp, variables, entries) { + if (!strcasecmp(name, ast_var_name(variables))) { + return ast_var_value(variables); } } - return NULL; + return '\0'; } static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize) @@ -279,45 +2541,43 @@ static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufs } } -/*! CDR channel variable retrieval */ -void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw) +void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw) { const char *fmt = "%Y-%m-%d %T"; const char *varbuf; - if (!cdr) /* don't die if the cdr is null */ + if (!cdr) { return; + } *ret = NULL; - /* special vars (the ones from the struct ast_cdr when requested by name) - I'd almost say we should convert all the stringed vals to vars */ - if (!strcasecmp(name, "clid")) + if (!strcasecmp(name, "clid")) { ast_copy_string(workspace, cdr->clid, workspacelen); - else if (!strcasecmp(name, "src")) + } else if (!strcasecmp(name, "src")) { ast_copy_string(workspace, cdr->src, workspacelen); - else if (!strcasecmp(name, "dst")) + } else if (!strcasecmp(name, "dst")) { ast_copy_string(workspace, cdr->dst, workspacelen); - else if (!strcasecmp(name, "dcontext")) + } else if (!strcasecmp(name, "dcontext")) { ast_copy_string(workspace, cdr->dcontext, workspacelen); - else if (!strcasecmp(name, "channel")) + } else if (!strcasecmp(name, "channel")) { ast_copy_string(workspace, cdr->channel, workspacelen); - else if (!strcasecmp(name, "dstchannel")) + } else if (!strcasecmp(name, "dstchannel")) { ast_copy_string(workspace, cdr->dstchannel, workspacelen); - else if (!strcasecmp(name, "lastapp")) + } else if (!strcasecmp(name, "lastapp")) { ast_copy_string(workspace, cdr->lastapp, workspacelen); - else if (!strcasecmp(name, "lastdata")) + } else if (!strcasecmp(name, "lastdata")) { ast_copy_string(workspace, cdr->lastdata, workspacelen); - else if (!strcasecmp(name, "start")) + } else if (!strcasecmp(name, "start")) { cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen); - else if (!strcasecmp(name, "answer")) + } else if (!strcasecmp(name, "answer")) { cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen); - else if (!strcasecmp(name, "end")) + } else if (!strcasecmp(name, "end")) { cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen); - else if (!strcasecmp(name, "duration")) { + } else if (!strcasecmp(name, "duration")) { snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000); } else if (!strcasecmp(name, "billsec")) { - snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000); + snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000); } else if (!strcasecmp(name, "disposition")) { if (raw) { snprintf(workspace, workspacelen, "%ld", cdr->disposition); @@ -328,683 +2588,288 @@ void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *wor if (raw) { snprintf(workspace, workspacelen, "%ld", cdr->amaflags); } else { - ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen); + ast_copy_string(workspace, ast_channel_amaflags2string(cdr->amaflags), workspacelen); } - } else if (!strcasecmp(name, "accountcode")) + } else if (!strcasecmp(name, "accountcode")) { ast_copy_string(workspace, cdr->accountcode, workspacelen); - else if (!strcasecmp(name, "peeraccount")) + } else if (!strcasecmp(name, "peeraccount")) { ast_copy_string(workspace, cdr->peeraccount, workspacelen); - else if (!strcasecmp(name, "uniqueid")) + } else if (!strcasecmp(name, "uniqueid")) { ast_copy_string(workspace, cdr->uniqueid, workspacelen); - else if (!strcasecmp(name, "linkedid")) + } else if (!strcasecmp(name, "linkedid")) { ast_copy_string(workspace, cdr->linkedid, workspacelen); - else if (!strcasecmp(name, "userfield")) + } else if (!strcasecmp(name, "userfield")) { ast_copy_string(workspace, cdr->userfield, workspacelen); - else if (!strcasecmp(name, "sequence")) + } else if (!strcasecmp(name, "sequence")) { snprintf(workspace, workspacelen, "%d", cdr->sequence); - else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur))) + } else if ((varbuf = cdr_format_var_internal(cdr, name))) { ast_copy_string(workspace, varbuf, workspacelen); - else + } else { workspace[0] = '\0'; + } - if (!ast_strlen_zero(workspace)) + if (!ast_strlen_zero(workspace)) { *ret = workspace; + } +} + +/* + * \internal + * \brief Callback that finds all CDRs that reference a particular channel + */ +static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags) +{ + struct cdr_object *cdr = obj; + const char *name = arg; + if (!(flags & OBJ_KEY)) { + return 0; + } + if (!strcasecmp(cdr->party_a.snapshot->name, name) || + (cdr->party_b.snapshot && !strcasecmp(cdr->party_b.snapshot->name, name))) { + return CMP_MATCH; + } + return 0; } -/* readonly cdr variables */ +/* Read Only CDR variables */ static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel", "lastapp", "lastdata", "start", "answer", "end", "duration", "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid", "userfield", "sequence", NULL }; -/*! Set a CDR channel variable - \note You can't set the CDR variables that belong to the actual CDR record, like "billsec". -*/ -int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur) + +int ast_cdr_setvar(const char *channel_name, const char *name, const char *value) { - struct ast_var_t *newvariable; - struct varshead *headp; + struct cdr_object *cdr; + struct cdr_object *it_cdr; + struct ao2_iterator *it_cdrs; + char *arg = ast_strdupa(channel_name); int x; for (x = 0; cdr_readonly_vars[x]; x++) { if (!strcasecmp(name, cdr_readonly_vars[x])) { - ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name); + ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!\n", name); return -1; } } - if (!cdr) { - ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n"); + it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE | OBJ_KEY, cdr_object_select_all_by_channel_cb, arg); + if (!it_cdrs) { + ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name); return -1; } - for (; cdr; cdr = recur ? cdr->next : NULL) { - if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - continue; - headp = &cdr->varshead; - AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) { - if (!strcasecmp(ast_var_name(newvariable), name)) { - /* there is already such a variable, delete it */ - AST_LIST_REMOVE_CURRENT(entries); - ast_var_delete(newvariable); - break; - } - } - AST_LIST_TRAVERSE_SAFE_END; - - if (value) { - newvariable = ast_var_assign(name, value); - AST_LIST_INSERT_HEAD(headp, newvariable, entries); - } - } - - return 0; -} - -int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr) -{ - struct ast_var_t *variables, *newvariable = NULL; - struct varshead *headpa, *headpb; - const char *var, *val; - int x = 0; - - if (!to_cdr || !from_cdr) /* don't die if one of the pointers is null */ - return 0; - - headpa = &from_cdr->varshead; - headpb = &to_cdr->varshead; - - AST_LIST_TRAVERSE(headpa,variables,entries) { - if (variables && - (var = ast_var_name(variables)) && (val = ast_var_value(variables)) && - !ast_strlen_zero(var) && !ast_strlen_zero(val)) { - newvariable = ast_var_assign(var, val); - AST_LIST_INSERT_HEAD(headpb, newvariable, entries); - x++; - } - } - - return x; -} - -int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur) -{ - struct ast_var_t *variables; - const char *var; - char *tmp; - char workspace[256]; - int total = 0, x = 0, i; - - ast_str_reset(*buf); - - for (; cdr; cdr = recur ? cdr->next : NULL) { - if (++x > 1) - ast_str_append(buf, 0, "\n"); - - AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) { - if (!(var = ast_var_name(variables))) { + while ((cdr = ao2_iterator_next(it_cdrs))) { + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + struct varshead *headp = NULL; + if (it_cdr->fn_table == &finalized_state_fn_table) { continue; } - - if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variables), ""), sep) < 0) { - ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); - break; + if (!strcmp(channel_name, it_cdr->party_a.snapshot->name)) { + headp = &it_cdr->party_a.variables; + } else if (it_cdr->party_b.snapshot && !strcmp(channel_name, it_cdr->party_b.snapshot->name)) { + headp = &it_cdr->party_b.variables; } - - total++; - } - - for (i = 0; cdr_readonly_vars[i]; i++) { - workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */ - ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0); - if (!tmp) - continue; - - if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep) < 0) { - ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); - break; - } else - total++; - } - } - - return total; -} - - -void ast_cdr_free_vars(struct ast_cdr *cdr, int recur) -{ - - /* clear variables */ - for (; cdr; cdr = recur ? cdr->next : NULL) { - struct ast_var_t *vardata; - struct varshead *headp = &cdr->varshead; - while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) - ast_var_delete(vardata); - } -} - -/*! \brief print a warning if cdr already posted */ -static void check_post(struct ast_cdr *cdr) -{ - if (!cdr) - return; - if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) - ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "<unknown>")); -} - -void ast_cdr_free(struct ast_cdr *cdr) -{ - - while (cdr) { - struct ast_cdr *next = cdr->next; - - ast_cdr_free_vars(cdr, 0); - ast_free(cdr); - cdr = next; - } -} - -/*! \brief the same as a cdr_free call, only with no checks; just get rid of it */ -void ast_cdr_discard(struct ast_cdr *cdr) -{ - while (cdr) { - struct ast_cdr *next = cdr->next; - - ast_cdr_free_vars(cdr, 0); - ast_free(cdr); - cdr = next; - } -} - -struct ast_cdr *ast_cdr_alloc(void) -{ - struct ast_cdr *x; - x = ast_calloc(1, sizeof(*x)); - if (!x) - ast_log(LOG_ERROR,"Allocation Failure for a CDR!\n"); - return x; -} - -static void cdr_merge_vars(struct ast_cdr *to, struct ast_cdr *from) -{ - struct ast_var_t *variablesfrom,*variablesto; - struct varshead *headpfrom = &to->varshead; - struct varshead *headpto = &from->varshead; - AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom, variablesfrom, entries) { - /* for every var in from, stick it in to */ - const char *fromvarname, *fromvarval; - const char *tovarname = NULL, *tovarval = NULL; - fromvarname = ast_var_name(variablesfrom); - fromvarval = ast_var_value(variablesfrom); - tovarname = 0; - - /* now, quick see if that var is in the 'to' cdr already */ - AST_LIST_TRAVERSE(headpto, variablesto, entries) { - - /* now, quick see if that var is in the 'to' cdr already */ - if ( strcasecmp(fromvarname, ast_var_name(variablesto)) == 0 ) { - tovarname = ast_var_name(variablesto); - tovarval = ast_var_value(variablesto); - break; + if (headp) { + set_variable(headp, name, value); } } - if (tovarname && strcasecmp(fromvarval,tovarval) != 0) { /* this message here to see how irritating the userbase finds it */ - ast_log(LOG_NOTICE, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname, fromvarval, tovarval); - continue; - } else if (tovarname && strcasecmp(fromvarval,tovarval) == 0) /* if they are the same, the job is done */ - continue; - - /* rip this var out of the from cdr, and stick it in the to cdr */ - AST_LIST_MOVE_CURRENT(headpto, entries); + ao2_unlock(cdr); + ao2_ref(cdr, -1); } - AST_LIST_TRAVERSE_SAFE_END; + ao2_iterator_destroy(it_cdrs); + + return 0; } -void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from) +/*! + * \brief Format a variable on a \ref cdr_object + */ +static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char *value, size_t length) { - struct ast_cdr *zcdr; - struct ast_cdr *lto = NULL; - struct ast_cdr *lfrom = NULL; - int discard_from = 0; - - if (!to || !from) - return; + struct ast_var_t *variable; - /* don't merge into locked CDR's -- it's bad business */ - if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) { - zcdr = to; /* safety valve? */ - while (to->next) { - lto = to; - to = to->next; - } - - if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) { - ast_log(LOG_WARNING, "Merging into locked CDR... no choice.\n"); - to = zcdr; /* safety-- if all there are is locked CDR's, then.... ?? */ - lto = NULL; + AST_LIST_TRAVERSE(&cdr->party_a.variables, variable, entries) { + if (!strcasecmp(name, ast_var_name(variable))) { + ast_copy_string(value, ast_var_value(variable), length); + return; } } - if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) { - struct ast_cdr *llfrom = NULL; - discard_from = 1; - if (lto) { - /* insert the from stuff after lto */ - lto->next = from; - lfrom = from; - while (lfrom && lfrom->next) { - if (!lfrom->next->next) - llfrom = lfrom; - lfrom = lfrom->next; - } - /* rip off the last entry and put a copy of the to at the end */ - if (llfrom) { - llfrom->next = to; - } - from = lfrom; - } else { - /* save copy of the current *to cdr */ - struct ast_cdr tcdr; - memcpy(&tcdr, to, sizeof(tcdr)); - /* copy in the locked from cdr */ - memcpy(to, from, sizeof(*to)); - lfrom = from; - while (lfrom && lfrom->next) { - if (!lfrom->next->next) - llfrom = lfrom; - lfrom = lfrom->next; - } - from->next = NULL; - /* rip off the last entry and put a copy of the to at the end */ - if (llfrom == from) { - to = to->next = ast_cdr_dup(&tcdr); - } else if (llfrom) { - to = llfrom->next = ast_cdr_dup(&tcdr); - } - from = lfrom; - } - } + *value = '\0'; +} - if (!ast_tvzero(from->start)) { - if (!ast_tvzero(to->start)) { - if (ast_tvcmp(to->start, from->start) > 0 ) { - to->start = from->start; /* use the earliest time */ - from->start = ast_tv(0,0); /* we actively "steal" these values */ - } - /* else nothing to do */ - } else { - to->start = from->start; - from->start = ast_tv(0,0); /* we actively "steal" these values */ - } - } - if (!ast_tvzero(from->answer)) { - if (!ast_tvzero(to->answer)) { - if (ast_tvcmp(to->answer, from->answer) > 0 ) { - to->answer = from->answer; /* use the earliest time */ - from->answer = ast_tv(0,0); /* we actively "steal" these values */ - } - /* we got the earliest answer time, so we'll settle for that? */ +/*! + * \brief Format one of the standard properties on a \ref cdr_object + */ +static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char *value, size_t length) +{ + struct ast_channel_snapshot *party_a = cdr_obj->party_a.snapshot; + struct ast_channel_snapshot *party_b = cdr_obj->party_b.snapshot; + + if (!strcasecmp(name, "clid")) { + ast_callerid_merge(value, length, party_a->caller_name, party_a->caller_number, ""); + } else if (!strcasecmp(name, "src")) { + ast_copy_string(value, party_a->caller_number, length); + } else if (!strcasecmp(name, "dst")) { + ast_copy_string(value, party_a->exten, length); + } else if (!strcasecmp(name, "dcontext")) { + ast_copy_string(value, party_a->context, length); + } else if (!strcasecmp(name, "channel")) { + ast_copy_string(value, party_a->name, length); + } else if (!strcasecmp(name, "dstchannel")) { + if (party_b) { + ast_copy_string(value, party_b->name, length); } else { - to->answer = from->answer; - from->answer = ast_tv(0,0); /* we actively "steal" these values */ + ast_copy_string(value, "", length); } - } - if (!ast_tvzero(from->end)) { - if (!ast_tvzero(to->end)) { - if (ast_tvcmp(to->end, from->end) < 0 ) { - to->end = from->end; /* use the latest time */ - from->end = ast_tv(0,0); /* we actively "steal" these values */ - to->duration = to->end.tv_sec - to->start.tv_sec; /* don't forget to update the duration, billsec, when we set end */ - to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec; - } - /* else, nothing to do */ + } else if (!strcasecmp(name, "lastapp")) { + ast_copy_string(value, party_a->appl, length); + } else if (!strcasecmp(name, "lastdata")) { + ast_copy_string(value, party_a->data, length); + } else if (!strcasecmp(name, "start")) { + cdr_get_tv(cdr_obj->start, NULL, value, length); + } else if (!strcasecmp(name, "answer")) { + cdr_get_tv(cdr_obj->answer, NULL, value, length); + } else if (!strcasecmp(name, "end")) { + cdr_get_tv(cdr_obj->end, NULL, value, length); + } else if (!strcasecmp(name, "duration")) { + snprintf(value, length, "%ld", cdr_object_get_duration(cdr_obj)); + } else if (!strcasecmp(name, "billsec")) { + snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj)); + } else if (!strcasecmp(name, "disposition")) { + snprintf(value, length, "%d", cdr_obj->disposition); + } else if (!strcasecmp(name, "amaflags")) { + snprintf(value, length, "%d", party_a->amaflags); + } else if (!strcasecmp(name, "accountcode")) { + ast_copy_string(value, party_a->accountcode, length); + } else if (!strcasecmp(name, "peeraccount")) { + if (party_b) { + ast_copy_string(value, party_b->accountcode, length); } else { - to->end = from->end; - from->end = ast_tv(0,0); /* we actively "steal" these values */ - to->duration = to->end.tv_sec - to->start.tv_sec; - to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec; - } - } - if (to->disposition < from->disposition) { - to->disposition = from->disposition; - from->disposition = AST_CDR_NOANSWER; - } - if (ast_strlen_zero(to->lastapp) && !ast_strlen_zero(from->lastapp)) { - ast_copy_string(to->lastapp, from->lastapp, sizeof(to->lastapp)); - from->lastapp[0] = 0; /* theft */ - } - if (ast_strlen_zero(to->lastdata) && !ast_strlen_zero(from->lastdata)) { - ast_copy_string(to->lastdata, from->lastdata, sizeof(to->lastdata)); - from->lastdata[0] = 0; /* theft */ - } - if (ast_strlen_zero(to->dcontext) && !ast_strlen_zero(from->dcontext)) { - ast_copy_string(to->dcontext, from->dcontext, sizeof(to->dcontext)); - from->dcontext[0] = 0; /* theft */ - } - if (ast_strlen_zero(to->dstchannel) && !ast_strlen_zero(from->dstchannel)) { - ast_copy_string(to->dstchannel, from->dstchannel, sizeof(to->dstchannel)); - from->dstchannel[0] = 0; /* theft */ - } - if (!ast_strlen_zero(from->channel) && (ast_strlen_zero(to->channel) || !strncasecmp(from->channel, "Agent/", 6))) { - ast_copy_string(to->channel, from->channel, sizeof(to->channel)); - from->channel[0] = 0; /* theft */ - } - if (ast_strlen_zero(to->src) && !ast_strlen_zero(from->src)) { - ast_copy_string(to->src, from->src, sizeof(to->src)); - from->src[0] = 0; /* theft */ - } - if (ast_strlen_zero(to->clid) && !ast_strlen_zero(from->clid)) { - ast_copy_string(to->clid, from->clid, sizeof(to->clid)); - from->clid[0] = 0; /* theft */ - } - if (ast_strlen_zero(to->dst) && !ast_strlen_zero(from->dst)) { - ast_copy_string(to->dst, from->dst, sizeof(to->dst)); - from->dst[0] = 0; /* theft */ - } - if (!to->amaflags) - to->amaflags = AST_CDR_DOCUMENTATION; - if (!from->amaflags) - from->amaflags = AST_CDR_DOCUMENTATION; /* make sure both amaflags are set to something (DOC is default) */ - if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (to->amaflags == AST_CDR_DOCUMENTATION && from->amaflags != AST_CDR_DOCUMENTATION)) { - to->amaflags = from->amaflags; - } - if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->accountcode) && !ast_strlen_zero(from->accountcode))) { - ast_copy_string(to->accountcode, from->accountcode, sizeof(to->accountcode)); - } - if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->peeraccount) && !ast_strlen_zero(from->peeraccount))) { - ast_copy_string(to->peeraccount, from->peeraccount, sizeof(to->peeraccount)); - } - if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->userfield) && !ast_strlen_zero(from->userfield))) { - ast_copy_string(to->userfield, from->userfield, sizeof(to->userfield)); - } - /* flags, varsead, ? */ - cdr_merge_vars(from, to); - - if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS)) - ast_set_flag(to, AST_CDR_FLAG_KEEP_VARS); - if (ast_test_flag(from, AST_CDR_FLAG_POSTED)) - ast_set_flag(to, AST_CDR_FLAG_POSTED); - if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) - ast_set_flag(to, AST_CDR_FLAG_LOCKED); - if (ast_test_flag(from, AST_CDR_FLAG_CHILD)) - ast_set_flag(to, AST_CDR_FLAG_CHILD); - if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED)) - ast_set_flag(to, AST_CDR_FLAG_POST_DISABLED); - - /* last, but not least, we need to merge any forked CDRs to the 'to' cdr */ - while (from->next) { - /* just rip 'em off the 'from' and insert them on the 'to' */ - zcdr = from->next; - from->next = zcdr->next; - zcdr->next = NULL; - /* zcdr is now ripped from the current list; */ - ast_cdr_append(to, zcdr); - } - if (discard_from) - ast_cdr_discard(from); -} - -void ast_cdr_start(struct ast_cdr *cdr) -{ - for (; cdr; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - check_post(cdr); - cdr->start = ast_tvnow(); - } - } -} - -void ast_cdr_answer(struct ast_cdr *cdr) -{ - - for (; cdr; cdr = cdr->next) { - if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED)) - continue; - if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - continue; - check_post(cdr); - if (cdr->disposition < AST_CDR_ANSWERED) - cdr->disposition = AST_CDR_ANSWERED; - if (ast_tvzero(cdr->answer)) - cdr->answer = ast_tvnow(); - } -} - -void ast_cdr_busy(struct ast_cdr *cdr) -{ - - for (; cdr; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - check_post(cdr); - cdr->disposition = AST_CDR_BUSY; - } - } -} - -void ast_cdr_failed(struct ast_cdr *cdr) -{ - for (; cdr; cdr = cdr->next) { - check_post(cdr); - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - check_post(cdr); - if (cdr->disposition < AST_CDR_FAILED) - cdr->disposition = AST_CDR_FAILED; + ast_copy_string(value, "", length); } + } else if (!strcasecmp(name, "uniqueid")) { + ast_copy_string(value, party_a->uniqueid, length); + } else if (!strcasecmp(name, "linkedid")) { + ast_copy_string(value, cdr_obj->linkedid, length); + } else if (!strcasecmp(name, "userfield")) { + ast_copy_string(value, cdr_obj->party_a.userfield, length); + } else if (!strcasecmp(name, "sequence")) { + snprintf(value, length, "%d", cdr_obj->sequence); + } else { + return 1; } -} -void ast_cdr_noanswer(struct ast_cdr *cdr) -{ - while (cdr) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - check_post(cdr); - cdr->disposition = AST_CDR_NOANSWER; - } - cdr = cdr->next; - } + return 0; } -void ast_cdr_congestion(struct ast_cdr *cdr) +int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length) { - char *chan; + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct cdr_object *cdr_obj; - /* if congestion log is disabled, pass the buck to ast_cdr_failed */ - if (!congestion) { - ast_cdr_failed(cdr); + if (!cdr) { + ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name); + return 1; } - while (cdr && congestion) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>"; - - if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) { - ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan); - } - - if (cdr->disposition < AST_CDR_CONGESTION) { - cdr->disposition = AST_CDR_CONGESTION; - } - } - cdr = cdr->next; + if (ast_strlen_zero(name)) { + return 1; } -} -/* everywhere ast_cdr_disposition is called, it will call ast_cdr_failed() - if ast_cdr_disposition returns a non-zero value */ + ao2_lock(cdr); -int ast_cdr_disposition(struct ast_cdr *cdr, int cause) -{ - int res = 0; + cdr_obj = cdr->last; - for (; cdr; cdr = cdr->next) { - switch (cause) { /* handle all the non failure, busy cases, return 0 not to set disposition, - return -1 to set disposition to FAILED */ - case AST_CAUSE_BUSY: - ast_cdr_busy(cdr); - break; - case AST_CAUSE_NO_ANSWER: - ast_cdr_noanswer(cdr); - break; - case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION: - ast_cdr_congestion(cdr); - break; - case AST_CAUSE_NORMAL: - break; - default: - res = -1; - } + if (cdr_object_format_property(cdr_obj, name, value, length)) { + /* Property failed; attempt variable */ + cdr_object_format_var_internal(cdr_obj, name, value, length); } - return res; -} + ao2_unlock(cdr); -void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann) -{ - for (; cdr; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - check_post(cdr); - ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel)); - } - } + return 0; } -void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data) +int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep) { + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct cdr_object *it_cdr; + struct ast_var_t *variable; + const char *var; + RAII_VAR(char *, workspace, ast_malloc(256), ast_free); + int total = 0, x = 0, i; - for (; cdr; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - check_post(cdr); - ast_copy_string(cdr->lastapp, S_OR(app, ""), sizeof(cdr->lastapp)); - ast_copy_string(cdr->lastdata, S_OR(data, ""), sizeof(cdr->lastdata)); - } + if (!workspace) { + return 1; } -} - -void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t) -{ - for (; cdr; cdr = cdr->next) { - if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED)) - continue; - if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - continue; - check_post(cdr); - cdr->answer = t; + if (!cdr) { + ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name); + return 1; } -} -void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition) -{ + ast_str_reset(*buf); - for (; cdr; cdr = cdr->next) { - if (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - continue; - check_post(cdr); - cdr->disposition = disposition; - } -} + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (++x > 1) + ast_str_append(buf, 0, "\n"); -/* set cid info for one record */ -static void set_one_cid(struct ast_cdr *cdr, struct ast_channel *c) -{ - const char *num; + AST_LIST_TRAVERSE(&it_cdr->party_a.variables, variable, entries) { + if (!(var = ast_var_name(variable))) { + continue; + } - if (!cdr) { - return; - } + if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variable), ""), sep) < 0) { + ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); + break; + } - /* Grab source from ANI or normal Caller*ID */ - num = S_COR(ast_channel_caller(c)->ani.number.valid, ast_channel_caller(c)->ani.number.str, - S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL)); - ast_callerid_merge(cdr->clid, sizeof(cdr->clid), - S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, NULL), num, ""); - ast_copy_string(cdr->src, S_OR(num, ""), sizeof(cdr->src)); - ast_cdr_setvar(cdr, "dnid", S_OR(ast_channel_dialed(c)->number.str, ""), 0); + total++; + } - if (ast_channel_caller(c)->id.subaddress.valid) { - ast_cdr_setvar(cdr, "callingsubaddr", S_OR(ast_channel_caller(c)->id.subaddress.str, ""), 0); - } - if (ast_channel_dialed(c)->subaddress.valid) { - ast_cdr_setvar(cdr, "calledsubaddr", S_OR(ast_channel_dialed(c)->subaddress.str, ""), 0); - } -} + for (i = 0; cdr_readonly_vars[i]; i++) { + /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */ + workspace[0] = 0; + cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace)); -int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c) -{ - for (; cdr; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - set_one_cid(cdr, c); + if (!ast_strlen_zero(workspace) + && ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, workspace, sep) < 0) { + ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n"); + break; + } + total++; + } } - return 0; -} -static int cdr_seq_inc(struct ast_cdr *cdr) -{ - return (cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1)); + return total; } -int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c) +void ast_cdr_free(struct ast_cdr *cdr) { - for ( ; cdr ; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - ast_copy_string(cdr->channel, ast_channel_name(c), sizeof(cdr->channel)); - set_one_cid(cdr, c); - cdr_seq_inc(cdr); + while (cdr) { + struct ast_cdr *next = cdr->next; - cdr->disposition = (ast_channel_state(c) == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NOANSWER; - cdr->amaflags = ast_channel_amaflags(c) ? ast_channel_amaflags(c) : ast_default_amaflags; - ast_copy_string(cdr->accountcode, ast_channel_accountcode(c), sizeof(cdr->accountcode)); - ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(c), sizeof(cdr->peeraccount)); - /* Destination information */ - ast_copy_string(cdr->dst, S_OR(ast_channel_macroexten(c),ast_channel_exten(c)), sizeof(cdr->dst)); - ast_copy_string(cdr->dcontext, S_OR(ast_channel_macrocontext(c),ast_channel_context(c)), sizeof(cdr->dcontext)); - /* Unique call identifier */ - ast_copy_string(cdr->uniqueid, ast_channel_uniqueid(c), sizeof(cdr->uniqueid)); - /* Linked call identifier */ - ast_copy_string(cdr->linkedid, ast_channel_linkedid(c), sizeof(cdr->linkedid)); - } + free_variables(&cdr->varshead); + ast_free(cdr); + cdr = next; } - return 0; } -/* Three routines were "fixed" via 10668, and later shown that - users were depending on this behavior. ast_cdr_end, - ast_cdr_setvar and ast_cdr_answer are the three routines. - While most of the other routines would not touch - LOCKED cdr's, these three routines were designed to - operate on locked CDR's as a matter of course. - I now appreciate how this plays with the ForkCDR app, - which forms these cdr chains in the first place. - cdr_end is pretty key: all cdrs created are closed - together. They only vary by start time. Arithmetically, - users can calculate the subintervals they wish to track. */ - -void ast_cdr_end(struct ast_cdr *cdr) +struct ast_cdr *ast_cdr_alloc(void) { - for ( ; cdr ; cdr = cdr->next) { - if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - continue; - check_post(cdr); - if (ast_tvzero(cdr->end)) - cdr->end = ast_tvnow(); - if (ast_tvzero(cdr->start)) { - ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", S_OR(cdr->channel, "<unknown>")); - cdr->disposition = AST_CDR_FAILED; - } else - cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec; - if (ast_tvzero(cdr->answer)) { - if (cdr->disposition == AST_CDR_ANSWERED) { - ast_log(LOG_WARNING, "CDR on channel '%s' has no answer time but is 'ANSWERED'\n", S_OR(cdr->channel, "<unknown>")); - cdr->disposition = AST_CDR_FAILED; - } - } else { - cdr->billsec = cdr->end.tv_sec - cdr->answer.tv_sec; - if (ast_test_flag(&ast_options, AST_OPT_FLAG_INITIATED_SECONDS)) - cdr->billsec += cdr->end.tv_usec > cdr->answer.tv_usec ? 1 : 0; - } - } + struct ast_cdr *x; + + x = ast_calloc(1, sizeof(*x)); + return x; } -char *ast_cdr_disp2str(int disposition) +const char *ast_cdr_disp2str(int disposition) { switch (disposition) { case AST_CDR_NULL: @@ -1023,272 +2888,247 @@ char *ast_cdr_disp2str(int disposition) return "UNKNOWN"; } -/*! Converts AMA flag to printable string - * - * \param flag, flags - */ -char *ast_cdr_flags2str(int flag) -{ - switch (flag) { - case AST_CDR_OMIT: - return "OMIT"; - case AST_CDR_BILLING: - return "BILLING"; - case AST_CDR_DOCUMENTATION: - return "DOCUMENTATION"; - } - return "Unknown"; -} +struct party_b_userfield_update { + const char *channel_name; + const char *userfield; +}; -int ast_cdr_setaccount(struct ast_channel *chan, const char *account) +/*! \brief Callback used to update the userfield on Party B on all CDRs */ +static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flags) { - struct ast_cdr *cdr = ast_channel_cdr(chan); - const char *old_acct = ""; - - if (!ast_strlen_zero(ast_channel_accountcode(chan))) { - old_acct = ast_strdupa(ast_channel_accountcode(chan)); - } - - ast_channel_accountcode_set(chan, account); - for ( ; cdr ; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - ast_copy_string(cdr->accountcode, ast_channel_accountcode(chan), sizeof(cdr->accountcode)); + struct cdr_object *cdr = obj; + struct party_b_userfield_update *info = arg; + struct cdr_object *it_cdr; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table == &finalized_state_fn_table) { + continue; + } + if (it_cdr->party_b.snapshot + && !strcmp(it_cdr->party_b.snapshot->name, info->channel_name)) { + strcpy(it_cdr->party_b.userfield, info->userfield); } } - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a CDR's AccountCode is changed.</synopsis> - </managerEventInstance> - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "NewAccountCode", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "AccountCode: %s\r\n" - "OldAccountCode: %s\r\n", - ast_channel_name(chan), ast_channel_uniqueid(chan), ast_channel_accountcode(chan), old_acct); - return 0; } -int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account) +void ast_cdr_setuserfield(const char *channel_name, const char *userfield) { - struct ast_cdr *cdr = ast_channel_cdr(chan); - const char *old_acct = ""; - - if (!ast_strlen_zero(ast_channel_peeraccount(chan))) { - old_acct = ast_strdupa(ast_channel_peeraccount(chan)); - } + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct party_b_userfield_update party_b_info = { + .channel_name = channel_name, + .userfield = userfield, + }; + struct cdr_object *it_cdr; - ast_channel_peeraccount_set(chan, account); - for ( ; cdr ; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(chan), sizeof(cdr->peeraccount)); + /* Handle Party A */ + if (cdr) { + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table == &finalized_state_fn_table) { + continue; + } + strcpy(it_cdr->party_a.userfield, userfield); } + ao2_unlock(cdr); } - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a CDR's PeerAccount is changed.</synopsis> - </managerEventInstance> - ***/ - ast_manager_event(chan, EVENT_FLAG_CALL, "NewPeerAccount", - "Channel: %s\r\n" - "Uniqueid: %s\r\n" - "PeerAccount: %s\r\n" - "OldPeerAccount: %s\r\n", - ast_channel_name(chan), ast_channel_uniqueid(chan), ast_channel_peeraccount(chan), old_acct); - return 0; + /* Handle Party B */ + ao2_callback(active_cdrs_by_channel, OBJ_NODATA, + cdr_object_update_party_b_userfield_cb, + &party_b_info); + } -int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag) +static void post_cdr(struct ast_cdr *cdr) { - struct ast_cdr *cdr; - int newflag = ast_cdr_amaflags2int(flag); - if (newflag) { - for (cdr = ast_channel_cdr(chan); cdr; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - cdr->amaflags = newflag; - } + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + struct cdr_beitem *i; + + for (; cdr ; cdr = cdr->next) { + /* For people, who don't want to see unanswered single-channel events */ + if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) && + cdr->disposition < AST_CDR_ANSWERED && + (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) { + continue; } - } - return 0; + if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) { + continue; + } + AST_RWLIST_RDLOCK(&be_list); + AST_RWLIST_TRAVERSE(&be_list, i, list) { + i->be(cdr); + } + AST_RWLIST_UNLOCK(&be_list); + } } -int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield) +int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option) { - struct ast_cdr *cdr = ast_channel_cdr(chan); + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct cdr_object *it_cdr; + + if (!cdr) { + return -1; + } - for ( ; cdr ; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield)); + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table == &finalized_state_fn_table) { + continue; + } + ast_set_flag(&it_cdr->flags, option); } + ao2_unlock(cdr); return 0; } -int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield) +int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option) { - struct ast_cdr *cdr = ast_channel_cdr(chan); + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct cdr_object *it_cdr; - for ( ; cdr ; cdr = cdr->next) { - int len = strlen(cdr->userfield); + if (!cdr) { + return -1; + } - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) - ast_copy_string(cdr->userfield + len, userfield, sizeof(cdr->userfield) - len); + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table == &finalized_state_fn_table) { + continue; + } + ast_clear_flag(&it_cdr->flags, option); } + ao2_unlock(cdr); return 0; } -int ast_cdr_update(struct ast_channel *c) +int ast_cdr_reset(const char *channel_name, struct ast_flags *options) { - struct ast_cdr *cdr = ast_channel_cdr(c); - - for ( ; cdr ; cdr = cdr->next) { - if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - set_one_cid(cdr, c); + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct ast_var_t *vardata; + struct cdr_object *it_cdr; - /* Copy account code et-al */ - ast_copy_string(cdr->accountcode, ast_channel_accountcode(c), sizeof(cdr->accountcode)); - ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(c), sizeof(cdr->peeraccount)); - ast_copy_string(cdr->linkedid, ast_channel_linkedid(c), sizeof(cdr->linkedid)); + if (!cdr) { + return -1; + } - /* Destination information */ /* XXX privilege macro* ? */ - ast_copy_string(cdr->dst, S_OR(ast_channel_macroexten(c), ast_channel_exten(c)), sizeof(cdr->dst)); - ast_copy_string(cdr->dcontext, S_OR(ast_channel_macrocontext(c), ast_channel_context(c)), sizeof(cdr->dcontext)); + ao2_lock(cdr); + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + /* clear variables */ + if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) { + while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) { + ast_var_delete(vardata); + } + if (cdr->party_b.snapshot) { + while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_b.variables, entries))) { + ast_var_delete(vardata); + } + } } + + /* Reset to initial state */ + memset(&it_cdr->start, 0, sizeof(it_cdr->start)); + memset(&it_cdr->end, 0, sizeof(it_cdr->end)); + memset(&it_cdr->answer, 0, sizeof(it_cdr->answer)); + it_cdr->start = ast_tvnow(); + cdr_object_check_party_a_answer(it_cdr); } + ao2_unlock(cdr); return 0; } -int ast_cdr_amaflags2int(const char *flag) +int ast_cdr_fork(const char *channel_name, struct ast_flags *options) { - if (!strcasecmp(flag, "default")) - return 0; - if (!strcasecmp(flag, "omit")) - return AST_CDR_OMIT; - if (!strcasecmp(flag, "billing")) - return AST_CDR_BILLING; - if (!strcasecmp(flag, "documentation")) - return AST_CDR_DOCUMENTATION; - return -1; -} + RAII_VAR(struct cdr_object *, cdr, + ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY), + ao2_cleanup); + struct cdr_object *new_cdr; + struct cdr_object *it_cdr; + struct cdr_object *cdr_obj; -static void post_cdr(struct ast_cdr *cdr) -{ - struct ast_cdr_beitem *i; + if (!cdr) { + return -1; + } - for ( ; cdr ; cdr = cdr->next) { - if (!unanswered && cdr->disposition < AST_CDR_ANSWERED && (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) { - /* For people, who don't want to see unanswered single-channel events */ - ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED); - continue; + { + SCOPED_AO2LOCK(lock, cdr); + cdr_obj = cdr->last; + if (cdr_obj->fn_table == &finalized_state_fn_table) { + /* If the last CDR in the chain is finalized, don't allow a fork - + * things are already dying at this point + */ + ast_log(AST_LOG_ERROR, "FARK\n"); + return -1; } - /* don't post CDRs that are for dialed channels unless those - * channels were originated from asterisk (pbx_spool, manager, - * cli) */ - if (ast_test_flag(cdr, AST_CDR_FLAG_DIALED) && !ast_test_flag(cdr, AST_CDR_FLAG_ORIGINATED)) { - ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED); - continue; + /* Copy over the basic CDR information. The Party A information is + * copied over automatically as part of the append + */ + ast_debug(1, "Forking CDR for channel %s\n", cdr->party_a.snapshot->name); + new_cdr = cdr_object_create_and_append(cdr); + if (!new_cdr) { + return -1; } - - check_post(cdr); - ast_set_flag(cdr, AST_CDR_FLAG_POSTED); - if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) - continue; - AST_RWLIST_RDLOCK(&be_list); - AST_RWLIST_TRAVERSE(&be_list, i, list) { - i->be(cdr); + new_cdr->fn_table = cdr_obj->fn_table; + ast_string_field_set(new_cdr, bridge, cdr->bridge); + new_cdr->flags = cdr->flags; + + /* If there's a Party B, copy it over as well */ + if (cdr_obj->party_b.snapshot) { + new_cdr->party_b.snapshot = cdr_obj->party_b.snapshot; + ao2_ref(new_cdr->party_b.snapshot, +1); + strcpy(new_cdr->party_b.userfield, cdr_obj->party_b.userfield); + new_cdr->party_b.flags = cdr_obj->party_b.flags; + if (ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) { + copy_variables(&new_cdr->party_b.variables, &cdr_obj->party_b.variables); + } } - AST_RWLIST_UNLOCK(&be_list); - } -} + new_cdr->start = cdr_obj->start; + new_cdr->answer = cdr_obj->answer; -void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags) -{ - struct ast_cdr *duplicate; - struct ast_flags flags = { 0 }; + /* Modify the times based on the flags passed in */ + if (ast_test_flag(options, AST_CDR_FLAG_SET_ANSWER) + && new_cdr->party_a.snapshot->state == AST_STATE_UP) { + new_cdr->answer = ast_tvnow(); + } + if (ast_test_flag(options, AST_CDR_FLAG_RESET)) { + new_cdr->answer = ast_tvnow(); + new_cdr->start = ast_tvnow(); + } - if (_flags) - ast_copy_flags(&flags, _flags, AST_FLAGS_ALL); + /* Create and append, by default, copies over the variables */ + if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) { + free_variables(&new_cdr->party_a.variables); + } - for ( ; cdr ; cdr = cdr->next) { - /* Detach if post is requested */ - if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) { - if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) { - ast_cdr_end(cdr); - if ((duplicate = ast_cdr_dup_unique_swap(cdr))) { - ast_cdr_detach(duplicate); + /* Finalize any current CDRs */ + if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) { + for (it_cdr = cdr; it_cdr != new_cdr; it_cdr = it_cdr->next) { + if (it_cdr->fn_table == &finalized_state_fn_table) { + continue; } - ast_set_flag(cdr, AST_CDR_FLAG_POSTED); - } - - /* enable CDR only */ - if (ast_test_flag(&flags, AST_CDR_FLAG_POST_ENABLE)) { - ast_clear_flag(cdr, AST_CDR_FLAG_POST_DISABLED); - continue; - } - - /* clear variables */ - if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) { - ast_cdr_free_vars(cdr, 0); + /* Force finalization on the CDR. This will bypass any checks for + * end before 'h' extension. + */ + cdr_object_finalize(it_cdr); + cdr_object_transition_state(it_cdr, &finalized_state_fn_table); } - - /* Reset to initial state */ - ast_clear_flag(cdr, AST_FLAGS_ALL); - memset(&cdr->start, 0, sizeof(cdr->start)); - memset(&cdr->end, 0, sizeof(cdr->end)); - memset(&cdr->answer, 0, sizeof(cdr->answer)); - cdr->billsec = 0; - cdr->duration = 0; - ast_cdr_start(cdr); - cdr->disposition = AST_CDR_NOANSWER; } } -} - -void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *_flags) -{ - struct ast_flags flags = { 0 }; - - if (_flags) - ast_copy_flags(&flags, _flags, AST_FLAGS_ALL); - - /* Reset to initial state */ - if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) { /* But do NOT lose the NoCDR() setting */ - ast_clear_flag(cdr, AST_FLAGS_ALL); - ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED); - } else { - ast_clear_flag(cdr, AST_FLAGS_ALL); - } - - memset(&cdr->start, 0, sizeof(cdr->start)); - memset(&cdr->end, 0, sizeof(cdr->end)); - memset(&cdr->answer, 0, sizeof(cdr->answer)); - cdr->billsec = 0; - cdr->duration = 0; - ast_cdr_start(cdr); - cdr->disposition = AST_CDR_NULL; -} - -struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr) -{ - struct ast_cdr *ret; - - if (cdr) { - ret = cdr; - - while (cdr->next) - cdr = cdr->next; - cdr->next = newcdr; - } else { - ret = newcdr; - } - return ret; + return 0; } /*! \note Don't call without cdr_batch_lock */ @@ -1313,8 +3153,8 @@ static int init_batch(void) static void *do_batch_backend_process(void *data) { - struct ast_cdr_batch_item *processeditem; - struct ast_cdr_batch_item *batchitem = data; + struct cdr_batch_item *processeditem; + struct cdr_batch_item *batchitem = data; /* Push each CDR into storage mechanism(s) and free all the memory */ while (batchitem) { @@ -1328,14 +3168,16 @@ static void *do_batch_backend_process(void *data) return NULL; } -void ast_cdr_submit_batch(int do_shutdown) +static void cdr_submit_batch(int do_shutdown) { - struct ast_cdr_batch_item *oldbatchitems = NULL; + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + struct cdr_batch_item *oldbatchitems = NULL; pthread_t batch_post_thread = AST_PTHREADT_NULL; /* if there's no batch, or no CDRs in the batch, then there's nothing to do */ - if (!batch || !batch->head) + if (!batch || !batch->head) { return; + } /* move the old CDRs aside, and prepare a new CDR batch */ ast_mutex_lock(&cdr_batch_lock); @@ -1345,7 +3187,7 @@ void ast_cdr_submit_batch(int do_shutdown) /* if configured, spawn a new thread to post these CDRs, also try to save as much as possible if we are shutting down safely */ - if (batchscheduleronly || do_shutdown) { + if (ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) || do_shutdown) { ast_debug(1, "CDR single-threaded batch processing begins now\n"); do_batch_backend_process(oldbatchitems); } else { @@ -1360,10 +3202,12 @@ void ast_cdr_submit_batch(int do_shutdown) static int submit_scheduled_batch(const void *data) { - ast_cdr_submit_batch(0); + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + cdr_submit_batch(0); /* manually reschedule from this point in time */ + ast_mutex_lock(&cdr_sched_lock); - cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL); + cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.size * 1000, submit_scheduled_batch, NULL); ast_mutex_unlock(&cdr_sched_lock); /* returning zero so the scheduler does not automatically reschedule */ return 0; @@ -1386,25 +3230,26 @@ static void submit_unscheduled_batch(void) ast_mutex_unlock(&cdr_pending_lock); } -void ast_cdr_detach(struct ast_cdr *cdr) +static void cdr_detach(struct ast_cdr *cdr) { - struct ast_cdr_batch_item *newtail; + struct cdr_batch_item *newtail; int curr; + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); int submit_batch = 0; - if (!cdr) + if (!cdr) { return; + } /* maybe they disabled CDR stuff completely, so just drop it */ - if (!enabled) { + if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) { ast_debug(1, "Dropping CDR !\n"); - ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED); ast_cdr_free(cdr); return; } /* post stuff immediately if we are not in batch mode, this is legacy behaviour */ - if (!batchmode) { + if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { post_cdr(cdr); ast_cdr_free(cdr); return; @@ -1436,7 +3281,7 @@ void ast_cdr_detach(struct ast_cdr *cdr) curr = batch->size++; /* if we have enough stuff to post, then do it */ - if (curr >= (batchsize - 1)) { + if (curr >= (mod_cfg->general->batch_settings.size - 1)) { submit_batch = 1; } ast_mutex_unlock(&cdr_batch_lock); @@ -1473,11 +3318,40 @@ static void *do_cdr(void *data) return NULL; } +static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + + switch (cmd) { + case CLI_INIT: + e->command = "cdr set debug [on|off]"; + e->usage = "Enable or disable extra debugging in the CDR Engine"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (!strcmp(a->argv[3], "on") && !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) { + ast_set_flag(&mod_cfg->general->settings, CDR_DEBUG); + ast_cli(a->fd, "CDR debugging enabled\n"); + } else if (!strcmp(a->argv[3], "off") && ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) { + ast_clear_flag(&mod_cfg->general->settings, CDR_DEBUG); + ast_cli(a->fd, "CDR debugging disabled\n"); + } + + return CLI_SUCCESS; +} + static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct ast_cdr_beitem *beitem=NULL; - int cnt=0; - long nextbatchtime=0; + struct cdr_beitem *beitem = NULL; + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + int cnt = 0; + long nextbatchtime = 0; switch (cmd) { case CLI_INIT: @@ -1496,23 +3370,23 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_ ast_cli(a->fd, "\n"); ast_cli(a->fd, "Call Detail Record (CDR) settings\n"); ast_cli(a->fd, "----------------------------------\n"); - ast_cli(a->fd, " Logging: %s\n", enabled ? "Enabled" : "Disabled"); - ast_cli(a->fd, " Mode: %s\n", batchmode ? "Batch" : "Simple"); - if (enabled) { - ast_cli(a->fd, " Log unanswered calls: %s\n", unanswered ? "Yes" : "No"); - ast_cli(a->fd, " Log congestion: %s\n\n", congestion ? "Yes" : "No"); - if (batchmode) { + ast_cli(a->fd, " Logging: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) ? "Enabled" : "Disabled"); + ast_cli(a->fd, " Mode: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE) ? "Batch" : "Simple"); + if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) { + ast_cli(a->fd, " Log unanswered calls: %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) ? "Yes" : "No"); + ast_cli(a->fd, " Log congestion: %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION) ? "Yes" : "No"); + if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { ast_cli(a->fd, "* Batch Mode Settings\n"); ast_cli(a->fd, " -------------------\n"); if (batch) cnt = batch->size; if (cdr_sched > -1) nextbatchtime = ast_sched_when(sched, cdr_sched); - ast_cli(a->fd, " Safe shutdown: %s\n", batchsafeshutdown ? "Enabled" : "Disabled"); - ast_cli(a->fd, " Threading model: %s\n", batchscheduleronly ? "Scheduler only" : "Scheduler plus separate threads"); + ast_cli(a->fd, " Safe shutdown: %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN) ? "Enabled" : "Disabled"); + ast_cli(a->fd, " Threading model: %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) ? "Scheduler only" : "Scheduler plus separate threads"); ast_cli(a->fd, " Current batch size: %d record%s\n", cnt, ESS(cnt)); - ast_cli(a->fd, " Maximum batch size: %d record%s\n", batchsize, ESS(batchsize)); - ast_cli(a->fd, " Maximum batch time: %d second%s\n", batchtime, ESS(batchtime)); + ast_cli(a->fd, " Maximum batch size: %d record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size)); + ast_cli(a->fd, " Maximum batch time: %d second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time)); ast_cli(a->fd, " Next batch processing time: %ld second%s\n\n", nextbatchtime, ESS(nextbatchtime)); } ast_cli(a->fd, "* Registered Backends\n"); @@ -1555,148 +3429,163 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_ static struct ast_cli_entry cli_submit = AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data"); static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CDR status"); +static struct ast_cli_entry cli_debug = AST_CLI_DEFINE(handle_cli_debug, "Enable debugging"); -static void do_reload(int reload) + +/*! + * \brief This dispatches *all* \ref cdr_objects. It should only be used during + * shutdown, so that we get billing records for everything that we can. + */ +static int cdr_object_dispatch_all_cb(void *obj, void *arg, int flags) { - struct ast_config *config; - struct ast_variable *v; - int cfg_size; - int cfg_time; - int was_enabled; - int was_batchmode; - struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + struct cdr_object *cdr = obj; + struct cdr_object *it_cdr; - if ((config = ast_config_load2("cdr.conf", "cdr", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) { - return; + for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) { + cdr_object_transition_state(it_cdr, &finalized_state_fn_table); } + cdr_object_dispatch(cdr); - ast_mutex_lock(&cdr_batch_lock); - - was_enabled = enabled; - was_batchmode = batchmode; - - batchsize = BATCH_SIZE_DEFAULT; - batchtime = BATCH_TIME_DEFAULT; - batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT; - batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT; - enabled = ENABLED_DEFAULT; - batchmode = BATCHMODE_DEFAULT; - unanswered = UNANSWERED_DEFAULT; - congestion = CONGESTION_DEFAULT; + return 0; +} - if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) { - ast_mutex_unlock(&cdr_batch_lock); +static void finalize_batch_mode(void) +{ + if (cdr_thread == AST_PTHREADT_NULL) { return; } + /* wake up the thread so it will exit */ + pthread_cancel(cdr_thread); + pthread_kill(cdr_thread, SIGURG); + pthread_join(cdr_thread, NULL); + cdr_thread = AST_PTHREADT_NULL; + ast_cond_destroy(&cdr_pending_cond); + ast_cli_unregister(&cli_submit); + ast_cdr_engine_term(); +} - /* don't run the next scheduled CDR posting while reloading */ - ast_mutex_lock(&cdr_sched_lock); - AST_SCHED_DEL(sched, cdr_sched); - ast_mutex_unlock(&cdr_sched_lock); +static int process_config(int reload) +{ + RAII_VAR(struct module_config *, mod_cfg, module_config_alloc(), ao2_cleanup); - for (v = ast_variable_browse(config, "general"); v; v = v->next) { - if (!strcasecmp(v->name, "enable")) { - enabled = ast_true(v->value); - } else if (!strcasecmp(v->name, "unanswered")) { - unanswered = ast_true(v->value); - } else if (!strcasecmp(v->name, "congestion")) { - congestion = ast_true(v->value); - } else if (!strcasecmp(v->name, "batch")) { - batchmode = ast_true(v->value); - } else if (!strcasecmp(v->name, "scheduleronly")) { - batchscheduleronly = ast_true(v->value); - } else if (!strcasecmp(v->name, "safeshutdown")) { - batchsafeshutdown = ast_true(v->value); - } else if (!strcasecmp(v->name, "size")) { - if (sscanf(v->value, "%30d", &cfg_size) < 1) { - ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value); - } else if (cfg_size < 0) { - ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size); - } else { - batchsize = cfg_size; - } - } else if (!strcasecmp(v->name, "time")) { - if (sscanf(v->value, "%30d", &cfg_time) < 1) { - ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value); - } else if (cfg_time < 0) { - ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time); - } else { - batchtime = cfg_time; - } - } else if (!strcasecmp(v->name, "endbeforehexten")) { - ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN); - } else if (!strcasecmp(v->name, "initiatedseconds")) { - ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INITIATED_SECONDS); + if (!reload) { + if (aco_info_init(&cfg_info)) { + return 1; } - } - - if (enabled && !batchmode) { - ast_log(LOG_NOTICE, "CDR simple logging enabled.\n"); - } else if (enabled && batchmode) { - ast_mutex_lock(&cdr_sched_lock); - cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL); - ast_mutex_unlock(&cdr_sched_lock); - ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime); - } else { - ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n"); - } - /* if this reload enabled the CDR batch mode, create the background thread - if it does not exist */ - if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) { - ast_cond_init(&cdr_pending_cond, NULL); - if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) { - ast_log(LOG_ERROR, "Unable to start CDR thread.\n"); - ast_mutex_lock(&cdr_sched_lock); - AST_SCHED_DEL(sched, cdr_sched); - ast_mutex_unlock(&cdr_sched_lock); - } else { - ast_cli_register(&cli_submit); - ast_register_atexit(ast_cdr_engine_term); - } - /* if this reload disabled the CDR and/or batch mode and there is a background thread, - kill it */ - } else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) { - /* wake up the thread so it will exit */ - pthread_cancel(cdr_thread); - pthread_kill(cdr_thread, SIGURG); - pthread_join(cdr_thread, NULL); - cdr_thread = AST_PTHREADT_NULL; - ast_cond_destroy(&cdr_pending_cond); - ast_cli_unregister(&cli_submit); - ast_unregister_atexit(ast_cdr_engine_term); - /* if leaving batch mode, then post the CDRs in the batch, - and don't reschedule, since we are stopping CDR logging */ - if (!batchmode && was_batchmode) { - ast_cdr_engine_term(); + aco_option_register(&cfg_info, "enable", ACO_EXACT, general_options, DEFAULT_ENABLED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_ENABLED); + aco_option_register(&cfg_info, "debug", ACO_EXACT, general_options, 0, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_DEBUG); + aco_option_register(&cfg_info, "unanswered", ACO_EXACT, general_options, DEFAULT_UNANSWERED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_UNANSWERED); + aco_option_register(&cfg_info, "congestion", ACO_EXACT, general_options, 0, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_CONGESTION); + aco_option_register(&cfg_info, "batch", ACO_EXACT, general_options, DEFAULT_BATCHMODE, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_BATCHMODE); + aco_option_register(&cfg_info, "endbeforehexten", ACO_EXACT, general_options, DEFAULT_END_BEFORE_H_EXTEN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_END_BEFORE_H_EXTEN); + aco_option_register(&cfg_info, "initiatedseconds", ACO_EXACT, general_options, DEFAULT_INITIATED_SECONDS, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_INITIATED_SECONDS); + aco_option_register(&cfg_info, "scheduleronly", ACO_EXACT, general_options, DEFAULT_BATCH_SCHEDULER_ONLY, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SCHEDULER_ONLY); + aco_option_register(&cfg_info, "safeshutdown", ACO_EXACT, general_options, DEFAULT_BATCH_SAFE_SHUTDOWN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SAFE_SHUTDOWN); + aco_option_register(&cfg_info, "size", ACO_EXACT, general_options, DEFAULT_BATCH_SIZE, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.size), 0, MAX_BATCH_SIZE); + aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 0, MAX_BATCH_TIME); + } + + if (aco_process_config(&cfg_info, reload)) { + if (!mod_cfg) { + return 1; + } + /* If we couldn't process the configuration and this wasn't a reload, + * create a default config + */ + if (!reload && !(aco_set_defaults(&general_option, "general", mod_cfg->general))) { + ast_log(LOG_NOTICE, "Failed to process CDR configuration; using defaults\n"); + ao2_global_obj_replace(module_configs, mod_cfg); + return 0; } + return 1; } - ast_mutex_unlock(&cdr_batch_lock); - ast_config_destroy(config); + if (reload) { + manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: CDR\r\nMessage: CDR subsystem reload requested\r\n"); + } + return 0; } static void cdr_engine_shutdown(void) { - if (cdr_thread != AST_PTHREADT_NULL) { - /* wake up the thread so it will exit */ - pthread_cancel(cdr_thread); - pthread_kill(cdr_thread, SIGURG); - pthread_join(cdr_thread, NULL); - cdr_thread = AST_PTHREADT_NULL; - ast_cond_destroy(&cdr_pending_cond); - } - ast_cli_unregister(&cli_submit); - + ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb, + NULL); + finalize_batch_mode(); + aco_info_destroy(&cfg_info); ast_cli_unregister(&cli_status); + ast_cli_unregister(&cli_debug); ast_sched_context_destroy(sched); sched = NULL; ast_free(batch); batch = NULL; + + ao2_ref(active_cdrs_by_channel, -1); + ao2_ref(active_cdrs_by_bridge, -1); +} + +static void cdr_enable_batch_mode(struct ast_cdr_config *config) +{ + SCOPED_LOCK(batch, &cdr_batch_lock, ast_mutex_lock, ast_mutex_unlock); + + /* Only create the thread level portions once */ + if (cdr_thread == AST_PTHREADT_NULL) { + ast_cond_init(&cdr_pending_cond, NULL); + if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start CDR thread.\n"); + return; + } + ast_cli_register(&cli_submit); + } + + /* Kill the currently scheduled item */ + AST_SCHED_DEL(sched, cdr_sched); + cdr_sched = ast_sched_add(sched, config->batch_settings.time * 1000, submit_scheduled_batch, NULL); + ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", + config->batch_settings.size, config->batch_settings.time); } int ast_cdr_engine_init(void) { + RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup); + + if (process_config(0)) { + return -1; + } + + /* The prime here should be the same as the channel container */ + active_cdrs_by_channel = ao2_container_alloc(51, cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn); + if (!active_cdrs_by_channel) { + return -1; + } + + active_cdrs_by_bridge = ao2_container_alloc(51, cdr_object_bridge_hash_fn, cdr_object_bridge_cmp_fn); + if (!active_cdrs_by_bridge) { + return -1; + } + + cdr_topic = stasis_topic_create("cdr_engine"); + if (!cdr_topic) { + return -1; + } + + channel_subscription = stasis_forward_all(stasis_caching_get_topic(ast_channel_topic_all_cached()), cdr_topic); + if (!channel_subscription) { + return -1; + } + bridge_subscription = stasis_forward_all(stasis_caching_get_topic(ast_bridge_topic_all_cached()), cdr_topic); + if (!bridge_subscription) { + return -1; + } + stasis_router = stasis_message_router_create(cdr_topic); + if (!stasis_router) { + return -1; + } + stasis_message_router_add(stasis_router, stasis_cache_update_type(), handle_channel_cache_message, NULL); + stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL); + stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL); + stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL); + sched = ast_sched_context_create(); if (!sched) { ast_log(LOG_ERROR, "Unable to create schedule context.\n"); @@ -1704,69 +3593,70 @@ int ast_cdr_engine_init(void) } ast_cli_register(&cli_status); - do_reload(0); + ast_cli_register(&cli_debug); ast_register_atexit(cdr_engine_shutdown); + mod_cfg = ao2_global_obj_ref(module_configs); + + if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) { + if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { + cdr_enable_batch_mode(mod_cfg->general); + } else { + ast_log(LOG_NOTICE, "CDR simple logging enabled.\n"); + } + } else { + ast_log(LOG_NOTICE, "CDR logging disabled.\n"); + } + return 0; } -/* \note This actually gets called a couple of times at shutdown. Once, before we start - hanging up channels, and then again, after the channel hangup timeout expires */ void ast_cdr_engine_term(void) { - ast_cdr_submit_batch(batchsafeshutdown); -} + RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); -int ast_cdr_engine_reload(void) -{ - do_reload(1); - return 0; + /* Since this is called explicitly during process shutdown, we might not have ever + * been initialized. If so, the config object will be NULL. + */ + if (!mod_cfg) { + return; + } + if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { + return; + } + cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN)); } -int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur) +int ast_cdr_engine_reload(void) { - struct ast_cdr *tmpcdr; - struct ast_data *level; - struct ast_var_t *variables; - const char *var, *val; - int x = 1, i; - char workspace[256]; - char *tmp; + RAII_VAR(struct module_config *, old_mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup); + RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup); - if (!cdr) { + if (process_config(1)) { return -1; } - for (tmpcdr = cdr; tmpcdr; tmpcdr = (recur ? tmpcdr->next : NULL)) { - level = ast_data_add_node(tree, "level"); - if (!level) { - continue; - } - - ast_data_add_int(level, "level_number", x); + mod_cfg = ao2_global_obj_ref(module_configs); - AST_LIST_TRAVERSE(&tmpcdr->varshead, variables, entries) { - if (variables && (var = ast_var_name(variables)) && - (val = ast_var_value(variables)) && !ast_strlen_zero(var) - && !ast_strlen_zero(val)) { - ast_data_add_str(level, var, val); - } else { - break; - } + if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) || + !(ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE))) { + /* If batch mode used to be enabled, finalize the batch */ + if (ast_test_flag(&old_mod_cfg->general->settings, CDR_BATCHMODE)) { + finalize_batch_mode(); } + } - for (i = 0; cdr_readonly_vars[i]; i++) { - workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */ - ast_cdr_getvar(tmpcdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0); - if (!tmp) { - continue; - } - ast_data_add_str(level, cdr_readonly_vars[i], tmp); + if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) { + if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) { + ast_log(LOG_NOTICE, "CDR simple logging enabled.\n"); + } else { + cdr_enable_batch_mode(mod_cfg->general); } - - x++; + } else { + ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n"); } return 0; } + diff --git a/main/cel.c b/main/cel.c index ff74ed1efeb0f75fa0e3058cdb843d1266f1f420..8110b116eb66df2dcd019a19ff4fac30ccff0dc6 100644 --- a/main/cel.c +++ b/main/cel.c @@ -433,16 +433,6 @@ static int dialstatus_cmp(void *obj, void *arg, int flags) return !strcmp(blob1_id, blob2_id) ? CMP_MATCH | CMP_STOP : 0; } -/*! - * \brief Map of ast_cel_ama_flags to strings - */ -static const char * const cel_ama_flags[AST_CEL_AMA_FLAG_TOTAL] = { - [AST_CEL_AMA_FLAG_NONE] = "NONE", - [AST_CEL_AMA_FLAG_OMIT] = "OMIT", - [AST_CEL_AMA_FLAG_BILLING] = "BILLING", - [AST_CEL_AMA_FLAG_DOCUMENTATION] = "DOCUMENTATION", -}; - unsigned int ast_cel_check_enabled(void) { RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup); @@ -625,16 +615,6 @@ const char *ast_cel_get_type_name(enum ast_cel_event_type type) return S_OR(cel_event_types[type], "Unknown"); } -const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag) -{ - if (flag < 0 || flag >= ARRAY_LEN(cel_ama_flags)) { - ast_log(LOG_WARNING, "Invalid AMA flag: %d\n", flag); - return "Unknown"; - } - - return S_OR(cel_ama_flags[flag], "Unknown"); -} - static int cel_track_app(const char *const_app) { RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup); diff --git a/main/channel.c b/main/channel.c index 86a8f4994599b34881ae564b7d743506f5bcf415..89ab9ca36dddc8c87f5f8f0133b05bcb8881a75c 100644 --- a/main/channel.c +++ b/main/channel.c @@ -104,7 +104,6 @@ struct ast_epoll_data { /*! \brief Prevent new channel allocation if shutting down. */ static int shutting_down; -static int uniqueint; static int chancount; unsigned long global_fin, global_fout; @@ -116,6 +115,8 @@ AST_THREADSTORAGE(state2str_threadbuf); * 100ms */ #define AST_DEFAULT_EMULATE_DTMF_DURATION 100 +#define DEFAULT_AMA_FLAGS AST_AMA_DOCUMENTATION + /*! Minimum amount of time between the end of the last digit and the beginning * of a new one - 45ms */ #define AST_MIN_DTMF_GAP 45 @@ -984,7 +985,7 @@ static void ast_dummy_channel_destructor(void *obj); static struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 0))) __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char *cid_name, const char *acctcode, const char *exten, const char *context, - const char *linkedid, const int amaflag, const char *file, int line, + const char *linkedid, enum ama_flags amaflag, const char *file, int line, const char *function, const char *name_fmt, va_list ap) { struct ast_channel *tmp; @@ -1001,7 +1002,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char return NULL; } - if (!(tmp = ast_channel_internal_alloc(ast_channel_destructor))) { + if (!(tmp = ast_channel_internal_alloc(ast_channel_destructor, linkedid))) { /* Channel structure allocation failure. */ return NULL; } @@ -1078,20 +1079,6 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char now = ast_tvnow(); ast_channel_creationtime_set(tmp, &now); - if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) { - ast_channel_uniqueid_build(tmp, "%li.%d", (long) time(NULL), - ast_atomic_fetchadd_int(&uniqueint, 1)); - } else { - ast_channel_uniqueid_build(tmp, "%s-%li.%d", ast_config_AST_SYSTEM_NAME, - (long) time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1)); - } - - if (!ast_strlen_zero(linkedid)) { - ast_channel_linkedid_set(tmp, linkedid); - } else { - ast_channel_linkedid_set(tmp, ast_channel_uniqueid(tmp)); - } - ast_channel_internal_setup_topics(tmp); if (!ast_strlen_zero(name_fmt)) { @@ -1123,25 +1110,20 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char /* Reminder for the future: under what conditions do we NOT want to track cdrs on channels? */ /* These 4 variables need to be set up for the cdr_init() to work right */ - if (amaflag) { + if (amaflag != AST_AMA_NONE) { ast_channel_amaflags_set(tmp, amaflag); } else { - ast_channel_amaflags_set(tmp, ast_default_amaflags); + ast_channel_amaflags_set(tmp, DEFAULT_AMA_FLAGS); } - if (!ast_strlen_zero(acctcode)) + if (!ast_strlen_zero(acctcode)) { ast_channel_accountcode_set(tmp, acctcode); - else - ast_channel_accountcode_set(tmp, ast_default_accountcode); + } ast_channel_context_set(tmp, S_OR(context, "default")); ast_channel_exten_set(tmp, S_OR(exten, "s")); ast_channel_priority_set(tmp, 1); - ast_channel_cdr_set(tmp, ast_cdr_alloc()); - ast_cdr_init(ast_channel_cdr(tmp), tmp); - ast_cdr_start(ast_channel_cdr(tmp)); - ast_atomic_fetchadd_int(&chancount, +1); headp = ast_channel_varshead(tmp); @@ -1165,7 +1147,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char * a lot of data into this func to do it here! */ if (ast_get_channel_tech(tech) || (tech2 && ast_get_channel_tech(tech2))) { - ast_publish_channel_state(tmp); + ast_channel_publish_snapshot(tmp); } ast_channel_internal_finalize(tmp); @@ -1176,7 +1158,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *cid_num, const char *cid_name, const char *acctcode, const char *exten, const char *context, - const char *linkedid, const int amaflag, + const char *linkedid, const enum ama_flags amaflag, const char *file, int line, const char *function, const char *name_fmt, ...) { @@ -1202,7 +1184,7 @@ struct ast_channel *ast_dummy_channel_alloc(void) struct ast_channel *tmp; struct varshead *headp; - if (!(tmp = ast_channel_internal_alloc(ast_dummy_channel_destructor))) { + if (!(tmp = ast_channel_internal_alloc(ast_dummy_channel_destructor, NULL))) { /* Dummy channel structure allocation failure. */ return NULL; } @@ -2470,7 +2452,7 @@ static void ast_channel_destructor(void *obj) ast_jb_destroy(chan); if (ast_channel_cdr(chan)) { - ast_cdr_discard(ast_channel_cdr(chan)); + ast_cdr_free(ast_channel_cdr(chan)); ast_channel_cdr_set(chan, NULL); } @@ -2530,7 +2512,7 @@ static void ast_dummy_channel_destructor(void *obj) ast_var_delete(vardata); if (ast_channel_cdr(chan)) { - ast_cdr_discard(ast_channel_cdr(chan)); + ast_cdr_free(ast_channel_cdr(chan)); ast_channel_cdr_set(chan, NULL); } @@ -2884,26 +2866,17 @@ int ast_hangup(struct ast_channel *chan) ast_cc_offer(chan); - ast_publish_channel_state(chan); - - if (ast_channel_cdr(chan) && !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_BRIDGED) && - !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED) && - (ast_channel_cdr(chan)->disposition != AST_CDR_NULL || ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED))) { - ast_channel_lock(chan); - ast_cdr_end(ast_channel_cdr(chan)); - ast_cdr_detach(ast_channel_cdr(chan)); - ast_channel_cdr_set(chan, NULL); - ast_channel_unlock(chan); - } + ast_channel_publish_snapshot(chan); ast_channel_unref(chan); return 0; } -int ast_raw_answer(struct ast_channel *chan, int cdr_answer) +int ast_raw_answer(struct ast_channel *chan) { int res = 0; + struct timeval answertime; ast_channel_lock(chan); @@ -2919,6 +2892,9 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer) return -1; } + answertime = ast_tvnow(); + ast_channel_answertime_set(chan, &answertime); + ast_channel_unlock(chan); switch (ast_channel_state(chan)) { @@ -2929,18 +2905,9 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer) res = ast_channel_tech(chan)->answer(chan); } ast_setstate(chan, AST_STATE_UP); - if (cdr_answer) { - ast_cdr_answer(ast_channel_cdr(chan)); - } ast_channel_unlock(chan); break; case AST_STATE_UP: - /* Calling ast_cdr_answer when it it has previously been called - * is essentially a no-op, so it is safe. - */ - if (cdr_answer) { - ast_cdr_answer(ast_channel_cdr(chan)); - } break; default: break; @@ -2951,13 +2918,13 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer) return res; } -int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer) +int __ast_answer(struct ast_channel *chan, unsigned int delay) { int res = 0; enum ast_channel_state old_state; old_state = ast_channel_state(chan); - if ((res = ast_raw_answer(chan, cdr_answer))) { + if ((res = ast_raw_answer(chan))) { return res; } @@ -3059,7 +3026,27 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer) int ast_answer(struct ast_channel *chan) { - return __ast_answer(chan, 0, 1); + return __ast_answer(chan, 0); +} + +int ast_channel_get_duration(struct ast_channel *chan) +{ + ast_assert(NULL != chan); + + if (ast_tvzero(ast_channel_creationtime(chan))) { + return 0; + } + return (ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(chan)) / 1000); +} + +int ast_channel_get_up_time(struct ast_channel *chan) +{ + ast_assert(NULL != chan); + + if (ast_tvzero(ast_channel_answertime(chan))) { + return 0; + } + return (ast_tvdiff_ms(ast_tvnow(), ast_channel_answertime(chan)) / 1000); } void ast_deactivate_generator(struct ast_channel *chan) @@ -4472,6 +4459,33 @@ void ast_channel_hangupcause_hash_set(struct ast_channel *chan, const struct ast } } +enum ama_flags ast_channel_string2amaflag(const char *flag) +{ + if (!strcasecmp(flag, "default")) + return DEFAULT_AMA_FLAGS; + if (!strcasecmp(flag, "omit")) + return AST_AMA_OMIT; + if (!strcasecmp(flag, "billing")) + return AST_AMA_BILLING; + if (!strcasecmp(flag, "documentation")) + return AST_AMA_DOCUMENTATION; + return AST_AMA_NONE; +} + +const char *ast_channel_amaflags2string(enum ama_flags flag) +{ + switch (flag) { + case AST_AMA_OMIT: + return "OMIT"; + case AST_AMA_BILLING: + return "BILLING"; + case AST_AMA_DOCUMENTATION: + return "DOCUMENTATION"; + default: + return "Unknown"; + } +} + int ast_indicate_data(struct ast_channel *chan, int _condition, const void *data, size_t datalen) { @@ -5625,15 +5639,15 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan } if (oh->account) { ast_channel_lock(new_chan); - ast_cdr_setaccount(new_chan, oh->account); + ast_channel_accountcode_set(new_chan, oh->account); ast_channel_unlock(new_chan); } } else if (caller) { /* no outgoing helper so use caller if avaliable */ call_forward_inherit(new_chan, caller, orig); } + ast_set_flag(ast_channel_flags(new_chan), AST_FLAG_ORIGINATED); ast_channel_lock_both(orig, new_chan); - ast_copy_flags(ast_channel_cdr(new_chan), ast_channel_cdr(orig), AST_CDR_FLAG_ORIGINATED); ast_channel_accountcode_set(new_chan, ast_channel_accountcode(orig)); ast_party_connected_line_copy(ast_channel_connected(new_chan), ast_channel_connected(orig)); ast_party_redirecting_copy(ast_channel_redirecting(new_chan), ast_channel_redirecting(orig)); @@ -5699,7 +5713,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c } if (oh->account) { ast_channel_lock(chan); - ast_cdr_setaccount(chan, oh->account); + ast_channel_accountcode_set(chan, oh->account); ast_channel_unlock(chan); } } @@ -5714,7 +5728,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c */ ast_set_callerid(chan, cid_num, cid_name, cid_num); - ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_ORIGINATED); + ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED); ast_party_connected_line_set_init(&connected, ast_channel_connected(chan)); if (cid_num) { connected.id.number.valid = 1; @@ -5764,25 +5778,21 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c break; case AST_CONTROL_BUSY: - ast_cdr_busy(ast_channel_cdr(chan)); *outstate = f->subclass.integer; timeout = 0; break; case AST_CONTROL_INCOMPLETE: - ast_cdr_failed(ast_channel_cdr(chan)); *outstate = AST_CONTROL_CONGESTION; timeout = 0; break; case AST_CONTROL_CONGESTION: - ast_cdr_failed(ast_channel_cdr(chan)); *outstate = f->subclass.integer; timeout = 0; break; case AST_CONTROL_ANSWER: - ast_cdr_answer(ast_channel_cdr(chan)); *outstate = f->subclass.integer; timeout = 0; /* trick to force exit from the while() */ break; @@ -5833,28 +5843,10 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c *outstate = AST_CONTROL_ANSWER; if (res <= 0) { - struct ast_cdr *chancdr; ast_channel_lock(chan); if (AST_CONTROL_RINGING == last_subclass) { ast_channel_hangupcause_set(chan, AST_CAUSE_NO_ANSWER); } - if (!ast_channel_cdr(chan) && (chancdr = ast_cdr_alloc())) { - ast_channel_cdr_set(chan, chancdr); - ast_cdr_init(ast_channel_cdr(chan), chan); - } - if (ast_channel_cdr(chan)) { - char tmp[256]; - - snprintf(tmp, sizeof(tmp), "%s/%s", type, addr); - ast_cdr_setapp(ast_channel_cdr(chan), "Dial", tmp); - ast_cdr_update(chan); - ast_cdr_start(ast_channel_cdr(chan)); - ast_cdr_end(ast_channel_cdr(chan)); - /* If the cause wasn't handled properly */ - if (ast_cdr_disposition(ast_channel_cdr(chan), ast_channel_hangupcause(chan))) { - ast_cdr_failed(ast_channel_cdr(chan)); - } - } ast_channel_unlock(chan); ast_hangup(chan); chan = NULL; @@ -6024,9 +6016,6 @@ int ast_call(struct ast_channel *chan, const char *addr, int timeout) /* Stop if we're a zombie or need a soft hangup */ ast_channel_lock(chan); if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan)) { - if (ast_channel_cdr(chan)) { - ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED); - } if (ast_channel_tech(chan)->call) res = ast_channel_tech(chan)->call(chan, addr, timeout); ast_set_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING); @@ -6517,32 +6506,20 @@ static void clone_variables(struct ast_channel *original, struct ast_channel *cl } } - - -/* return the oldest of two linkedids. linkedid is derived from - uniqueid which is formed like this: [systemname-]ctime.seq - - The systemname, and the dash are optional, followed by the epoch - time followed by an integer sequence. Note that this is not a - decimal number, since 1.2 is less than 1.11 in uniqueid land. - - To compare two uniqueids, we parse out the integer values of the - time and the sequence numbers and compare them, with time trumping - sequence. -*/ -static const char *oldest_linkedid(const char *a, const char *b) +const char *ast_channel_oldest_linkedid(const char *a, const char *b) { const char *satime, *saseq; const char *sbtime, *sbseq; const char *dash; - unsigned int atime, aseq, btime, bseq; - if (ast_strlen_zero(a)) + if (ast_strlen_zero(a)) { return b; + } - if (ast_strlen_zero(b)) + if (ast_strlen_zero(b)) { return a; + } satime = a; sbtime = b; @@ -6558,8 +6535,9 @@ static const char *oldest_linkedid(const char *a, const char *b) /* the sequence comes after the '.' */ saseq = strchr(satime, '.'); sbseq = strchr(sbtime, '.'); - if (!saseq || !sbseq) + if (!saseq || !sbseq) { return NULL; + } saseq++; sbseq++; @@ -6578,122 +6556,6 @@ static const char *oldest_linkedid(const char *a, const char *b) } } -/*! Set the channel's linkedid to the given string, and also check to - * see if the channel's old linkedid is now being retired */ -static void ast_channel_change_linkedid(struct ast_channel *chan, const char *linkedid) -{ - ast_assert(linkedid != NULL); - /* if the linkedid for this channel is being changed from something, check... */ - if (ast_channel_linkedid(chan) && !strcmp(ast_channel_linkedid(chan), linkedid)) { - return; - } - - ast_channel_linkedid_set(chan, linkedid); - ast_cel_linkedid_ref(linkedid); -} - -/*! \brief Propagate the oldest linkedid between associated channels */ -void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer) -{ - const char* linkedid=NULL; - struct ast_channel *bridged; - -/* - * BUGBUG this needs to be updated to not use ast_channel_internal_bridged_channel(). - * BUGBUG this needs to be updated to not use ast_bridged_channel(). - * - * We may be better off making this a function of the bridging - * framework. Essentially, as each channel joins a bridge, the - * oldest linkedid should be propagated between all pairs of - * channels. This should be handled by bridging (unless you're - * in an infinite wait bridge...) just like the BRIDGEPEER - * channel variable. - * - * This is currently called in two places: - * - * (1) In channel masquerade. To some extent this shouldn't - * really be done any longer - we don't really want a channel to - * have its linkedid change, even if it replaces a channel that - * had an older linkedid. The two channels aren't really - * 'related', they're simply swapping with each other. - * - * (2) In features.c as two channels are bridged. - */ - linkedid = oldest_linkedid(ast_channel_linkedid(chan), ast_channel_linkedid(peer)); - linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(chan)); - linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(peer)); - if (ast_channel_internal_bridged_channel(chan)) { - bridged = ast_bridged_channel(chan); - if (bridged && bridged != peer) { - linkedid = oldest_linkedid(linkedid, ast_channel_linkedid(bridged)); - linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(bridged)); - } - } - if (ast_channel_internal_bridged_channel(peer)) { - bridged = ast_bridged_channel(peer); - if (bridged && bridged != chan) { - linkedid = oldest_linkedid(linkedid, ast_channel_linkedid(bridged)); - linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(bridged)); - } - } - - /* just in case setting a stringfield to itself causes problems */ - linkedid = ast_strdupa(linkedid); - - ast_channel_change_linkedid(chan, linkedid); - ast_channel_change_linkedid(peer, linkedid); - if (ast_channel_internal_bridged_channel(chan)) { - bridged = ast_bridged_channel(chan); - if (bridged && bridged != peer) { - ast_channel_change_linkedid(bridged, linkedid); - } - } - if (ast_channel_internal_bridged_channel(peer)) { - bridged = ast_bridged_channel(peer); - if (bridged && bridged != chan) { - ast_channel_change_linkedid(bridged, linkedid); - } - } -} - -#if 0 //BUGBUG setting up peeraccount needs to be removed. -/* copy accountcode and peeraccount across during a link */ -static void ast_set_owners_and_peers(struct ast_channel *chan1, - struct ast_channel *chan2) -{ - if (!ast_strlen_zero(ast_channel_accountcode(chan1)) && ast_strlen_zero(ast_channel_peeraccount(chan2))) { - ast_debug(1, "setting peeraccount to %s for %s from data on channel %s\n", - ast_channel_accountcode(chan1), ast_channel_name(chan2), ast_channel_name(chan1)); - ast_channel_peeraccount_set(chan2, ast_channel_accountcode(chan1)); - } - if (!ast_strlen_zero(ast_channel_accountcode(chan2)) && ast_strlen_zero(ast_channel_peeraccount(chan1))) { - ast_debug(1, "setting peeraccount to %s for %s from data on channel %s\n", - ast_channel_accountcode(chan2), ast_channel_name(chan1), ast_channel_name(chan2)); - ast_channel_peeraccount_set(chan1, ast_channel_accountcode(chan2)); - } - if (!ast_strlen_zero(ast_channel_peeraccount(chan1)) && ast_strlen_zero(ast_channel_accountcode(chan2))) { - ast_debug(1, "setting accountcode to %s for %s from data on channel %s\n", - ast_channel_peeraccount(chan1), ast_channel_name(chan2), ast_channel_name(chan1)); - ast_channel_accountcode_set(chan2, ast_channel_peeraccount(chan1)); - } - if (!ast_strlen_zero(ast_channel_peeraccount(chan2)) && ast_strlen_zero(ast_channel_accountcode(chan1))) { - ast_debug(1, "setting accountcode to %s for %s from data on channel %s\n", - ast_channel_peeraccount(chan2), ast_channel_name(chan1), ast_channel_name(chan2)); - ast_channel_accountcode_set(chan1, ast_channel_peeraccount(chan2)); - } - if (0 != strcmp(ast_channel_accountcode(chan1), ast_channel_peeraccount(chan2))) { - ast_debug(1, "changing peeraccount from %s to %s on %s to match channel %s\n", - ast_channel_peeraccount(chan2), ast_channel_peeraccount(chan1), ast_channel_name(chan2), ast_channel_name(chan1)); - ast_channel_peeraccount_set(chan2, ast_channel_accountcode(chan1)); - } - if (0 != strcmp(ast_channel_accountcode(chan2), ast_channel_peeraccount(chan1))) { - ast_debug(1, "changing peeraccount from %s to %s on %s to match channel %s\n", - ast_channel_peeraccount(chan1), ast_channel_peeraccount(chan2), ast_channel_name(chan1), ast_channel_name(chan2)); - ast_channel_peeraccount_set(chan1, ast_channel_accountcode(chan2)); - } -} -#endif //BUGBUG - /*! * \internal * \brief Transfer COLP between target and transferee channels. @@ -6775,7 +6637,6 @@ void ast_do_masquerade(struct ast_channel *original) } exchange; struct ast_channel *clonechan, *chans[2]; struct ast_channel *bridged; - struct ast_cdr *cdr; struct ast_datastore *xfer_ds; struct xfer_masquerade_ds *xfer_colp; struct ast_format rformat; @@ -6943,9 +6804,6 @@ void ast_do_masquerade(struct ast_channel *original) snprintf(tmp_name, sizeof(tmp_name), "%s<ZOMBIE>", ast_channel_name(clonechan)); /* quick, hide the brains! */ __ast_change_name_nolink(clonechan, tmp_name); - /* share linked id's */ - ast_channel_set_linkgroup(original, clonechan); - /* Swap the technologies */ t = ast_channel_tech(original); ast_channel_tech_set(original, ast_channel_tech(clonechan)); @@ -6955,11 +6813,6 @@ void ast_do_masquerade(struct ast_channel *original) ast_channel_tech_pvt_set(original, ast_channel_tech_pvt(clonechan)); ast_channel_tech_pvt_set(clonechan, t_pvt); - /* Swap the cdrs */ - cdr = ast_channel_cdr(original); - ast_channel_cdr_set(original, ast_channel_cdr(clonechan)); - ast_channel_cdr_set(clonechan, cdr); - /* Swap the alertpipes */ ast_channel_internal_alertpipe_swap(original, clonechan); @@ -7098,7 +6951,7 @@ void ast_do_masquerade(struct ast_channel *original) ast_channel_redirecting_set(original, ast_channel_redirecting(clonechan)); ast_channel_redirecting_set(clonechan, &exchange.redirecting); - ast_publish_channel_state(original); + ast_channel_publish_snapshot(original); /* Restore original timing file descriptor */ ast_channel_set_fd(original, AST_TIMING_FD, ast_channel_timingfd(original)); @@ -7257,11 +7110,8 @@ void ast_set_callerid(struct ast_channel *chan, const char *cid_num, const char ast_free(ast_channel_caller(chan)->ani.number.str); ast_channel_caller(chan)->ani.number.str = ast_strdup(cid_ani); } - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } - ast_publish_channel_state(chan); + ast_channel_publish_snapshot(chan); ast_channel_unlock(chan); } @@ -7287,10 +7137,7 @@ void ast_channel_set_caller_event(struct ast_channel *chan, const struct ast_par ast_channel_lock(chan); ast_party_caller_set(ast_channel_caller(chan), caller, update); - ast_publish_channel_state(chan); - if (ast_channel_cdr(chan)) { - ast_cdr_setcid(ast_channel_cdr(chan), chan); - } + ast_channel_publish_snapshot(chan); ast_channel_unlock(chan); } diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c index 1e21cced0bf305c85b671aa672b1b89b20cbbed7..afa4c9f3d3eaf7f26a119d9465214ea75306bfc1 100644 --- a/main/channel_internal_api.c +++ b/main/channel_internal_api.c @@ -38,6 +38,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include <unistd.h> #include <fcntl.h> +#include "asterisk/paths.h" #include "asterisk/channel.h" #include "asterisk/channel_internal.h" #include "asterisk/data.h" @@ -143,6 +144,7 @@ struct ast_channel { struct ast_namedgroups *named_callgroups; /*!< Named call group for call pickups */ struct ast_namedgroups *named_pickupgroups; /*!< Named pickup group - which call groups can be picked up? */ struct timeval creationtime; /*!< The time of channel creation */ + struct timeval answertime; /*!< The time the channel was answered */ struct ast_readq_list readq; struct ast_jb jb; /*!< The jitterbuffer state */ struct timeval dtmf_tv; /*!< The time that an in process digit began, or the last digit ended */ @@ -206,6 +208,9 @@ struct ast_channel { struct stasis_subscription *endpoint_forward; /*!< Subscription for event forwarding to endpoint's topic */ }; +/*! \brief The monotonically increasing integer counter for channel uniqueids */ +static int uniqueint; + /* AST_DATA definitions, which will probably have to be re-thought since the channel will be opaque */ #if 0 /* XXX AstData: ast_callerid no longer exists. (Equivalent code not readily apparent.) */ @@ -333,7 +338,7 @@ int ast_channel_data_add_structure(struct ast_data *tree, if (!enum_node) { return -1; } - ast_data_add_str(enum_node, "text", ast_cdr_flags2str(ast_channel_amaflags(chan))); + ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(ast_channel_amaflags(chan))); ast_data_add_int(enum_node, "value", ast_channel_amaflags(chan)); /* transfercapability */ @@ -400,8 +405,6 @@ int ast_channel_data_add_structure(struct ast_data *tree, return -1; } - ast_cdr_data_add_structure(data_cdr, ast_channel_cdr(chan), 1); - return 0; } @@ -413,9 +416,11 @@ int ast_channel_data_cmp_structure(const struct ast_data_search *tree, /* ACCESSORS */ -#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish) \ +#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish, assert_on_null) \ void ast_channel_##field##_set(struct ast_channel *chan, const char *value) \ { \ + if ((assert_on_null)) ast_assert(!ast_strlen_zero(value)); \ + if (!strcmp(value, chan->field)) return; \ ast_string_field_set(chan, field, value); \ if (publish) ast_channel_publish_snapshot(chan); \ } \ @@ -431,20 +436,20 @@ void ast_channel_##field##_build(struct ast_channel *chan, const char *fmt, ...) va_start(ap, fmt); \ ast_channel_##field##_build_va(chan, fmt, ap); \ va_end(ap); \ - if (publish) ast_channel_publish_snapshot(chan); \ } -DEFINE_STRINGFIELD_SETTERS_FOR(name, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(language, 1); -DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0); -DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(name, 0, 1); +DEFINE_STRINGFIELD_SETTERS_FOR(language, 1, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 1, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 1, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0, 1); +DEFINE_STRINGFIELD_SETTERS_FOR(linkedid, 1, 1); +DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0, 0); +DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0, 0); #define DEFINE_STRINGFIELD_GETTER_FOR(field) const char *ast_channel_##field(const struct ast_channel *chan) \ { \ @@ -464,12 +469,6 @@ DEFINE_STRINGFIELD_GETTER_FOR(parkinglot); DEFINE_STRINGFIELD_GETTER_FOR(hangupsource); DEFINE_STRINGFIELD_GETTER_FOR(dialcontext); -void ast_channel_linkedid_set(struct ast_channel *chan, const char *value) -{ - ast_assert(!ast_strlen_zero(value)); - ast_string_field_set(chan, linkedid, value); -} - const char *ast_channel_appl(const struct ast_channel *chan) { return chan->appl; @@ -555,14 +554,20 @@ void ast_channel_sending_dtmf_tv_set(struct ast_channel *chan, struct timeval va chan->sending_dtmf_tv = value; } -int ast_channel_amaflags(const struct ast_channel *chan) +enum ama_flags ast_channel_amaflags(const struct ast_channel *chan) { return chan->amaflags; } -void ast_channel_amaflags_set(struct ast_channel *chan, int value) + +void ast_channel_amaflags_set(struct ast_channel *chan, enum ama_flags value) { + if (chan->amaflags == value) { + return; + } chan->amaflags = value; + ast_channel_publish_snapshot(chan); } + #ifdef HAVE_EPOLL int ast_channel_epfd(const struct ast_channel *chan) { @@ -1043,6 +1048,16 @@ void ast_channel_creationtime_set(struct ast_channel *chan, struct timeval *valu chan->creationtime = *value; } +struct timeval ast_channel_answertime(struct ast_channel *chan) +{ + return chan->answertime; +} + +void ast_channel_answertime_set(struct ast_channel *chan, struct timeval *value) +{ + chan->answertime = *value; +} + /* Evil softhangup accessors */ int ast_channel_softhangup_internal_flag(struct ast_channel *chan) { @@ -1350,7 +1365,7 @@ static int pvt_cause_cmp_fn(void *obj, void *vstr, int flags) #define DIALED_CAUSES_BUCKETS 37 -struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *file, int line, const char *function) +struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *linkedid, const char *file, int line, const char *function) { struct ast_channel *tmp; #if defined(REF_DEBUG) @@ -1368,7 +1383,21 @@ struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), } if (!(tmp->dialed_causes = ao2_container_alloc(DIALED_CAUSES_BUCKETS, pvt_cause_hash_fn, pvt_cause_cmp_fn))) { - return ast_channel_unref(tmp); + return ast_channel_unref(tmp); + } + + if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) { + ast_channel_uniqueid_build(tmp, "%li.%d", (long)time(NULL), + ast_atomic_fetchadd_int(&uniqueint, 1)); + } else { + ast_channel_uniqueid_build(tmp, "%s-%li.%d", ast_config_AST_SYSTEM_NAME, + (long)time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1)); + } + + if (!ast_strlen_zero(linkedid)) { + ast_string_field_set(tmp, linkedid, linkedid); + } else { + ast_string_field_set(tmp, linkedid, tmp->uniqueid); } return tmp; diff --git a/main/cli.c b/main/cli.c index 683ae9c3ea31b6de454e1df0bef9a1ccd5e1d048..3431d1de827072a53bf433f717fe2c0b8acb1e7f 100644 --- a/main/cli.c +++ b/main/cli.c @@ -932,8 +932,8 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar bridge = ast_channel_get_bridge(c); if (!count) { - if ((concise || verbose) && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) { - int duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000); + if ((concise || verbose) && !ast_tvzero(ast_channel_creationtime(c))) { + int duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(c)) / 1000); if (verbose) { int durh = duration / 3600; int durm = (duration % 3600) / 60; @@ -1465,8 +1465,8 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar ast_channel_lock(c); - if (ast_channel_cdr(c)) { - elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec; + if (!ast_tvzero(ast_channel_creationtime(c))) { + elapsed_seconds = now.tv_sec - ast_channel_creationtime(c).tv_sec; hour = elapsed_seconds / 3600; min = (elapsed_seconds % 3600) / 60; sec = elapsed_seconds % 60; @@ -1565,7 +1565,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar ast_str_append(&output, 0, " Variables:\n%s\n", ast_str_buffer(obuf)); } - if (ast_channel_cdr(c) && ast_cdr_serialize_variables(ast_channel_cdr(c), &obuf, '=', '\n', 1)) { + if (ast_cdr_serialize_variables(ast_channel_name(c), &obuf, '=', '\n')) { ast_str_append(&output, 0, " CDR Variables:\n%s\n", ast_str_buffer(obuf)); } diff --git a/main/dial.c b/main/dial.c index 840f681d683efb08db2dd9ea27d3cc6cf1451c1d..ab35373c5fe57a9a4732f57c5149b75abe744d84 100644 --- a/main/dial.c +++ b/main/dial.c @@ -332,7 +332,7 @@ int ast_dial_prerun(struct ast_dial *dial, struct ast_channel *chan, struct ast_ } /*! \brief Helper function that does the beginning dialing per-appended channel */ -static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan) +static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan, int async) { char numsubst[AST_MAX_EXTENSION]; int res = 1; @@ -351,9 +351,10 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann ast_hangup(channel->owner); channel->owner = NULL; } else { - if (chan) + if (chan) { ast_poll_channel_add(chan, channel->owner); - ast_channel_publish_dial(chan, channel->owner, channel->device, NULL); + } + ast_channel_publish_dial(async ? NULL : chan, channel->owner, channel->device, NULL); res = 1; ast_verb(3, "Called %s\n", numsubst); } @@ -362,7 +363,7 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann } /*! \brief Helper function that does the beginning dialing per dial structure */ -static int begin_dial(struct ast_dial *dial, struct ast_channel *chan) +static int begin_dial(struct ast_dial *dial, struct ast_channel *chan, int async) { struct ast_dial_channel *channel = NULL; int success = 0; @@ -370,7 +371,7 @@ static int begin_dial(struct ast_dial *dial, struct ast_channel *chan) /* Iterate through channel list, requesting and calling each one */ AST_LIST_LOCK(&dial->channels); AST_LIST_TRAVERSE(&dial->channels, channel, list) { - success += begin_dial_channel(channel, chan); + success += begin_dial_channel(channel, chan, async); } AST_LIST_UNLOCK(&dial->channels); @@ -409,7 +410,7 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c AST_LIST_UNLOCK(&dial->channels); /* Finally give it a go... send it out into the world */ - begin_dial_channel(channel, chan); + begin_dial_channel(channel, chan, chan ? 0 : 1); /* Drop the original channel */ ast_hangup(original); @@ -819,7 +820,7 @@ enum ast_dial_result ast_dial_run(struct ast_dial *dial, struct ast_channel *cha } /* Dial each requested channel */ - if (!begin_dial(dial, chan)) + if (!begin_dial(dial, chan, async)) return AST_DIAL_RESULT_FAILED; /* If we are running async spawn a thread and send it away... otherwise block here */ diff --git a/main/features.c b/main/features.c index d5b5ce295b3404531feeacfed205de5236d6d4a0..c44520c53e6c598a4f970864e92ebf8c25be2db9 100644 --- a/main/features.c +++ b/main/features.c @@ -3618,7 +3618,7 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, /* Answer if need be */ if (ast_channel_state(chan) != AST_STATE_UP) { - if (ast_raw_answer(chan, 1)) { + if (ast_raw_answer(chan)) { return -1; } } @@ -3628,8 +3628,6 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, ast_channel_log("Pre-bridge CHAN Channel info", chan); ast_channel_log("Pre-bridge PEER Channel info", peer); #endif - /* two channels are being marked as linked here */ - ast_channel_set_linkgroup(chan, peer); /* * If we are bridging a call, stop worrying about forwarding diff --git a/main/manager.c b/main/manager.c index 0e402aeb8693b1a69fb1a144a86854ce8e6fec3a..d4960d6de15b97a70f31b3fba6aa93ee7dbff126 100644 --- a/main/manager.c +++ b/main/manager.c @@ -3770,8 +3770,8 @@ static int action_status(struct mansession *s, const struct message *m) bridge_text[0] = '\0'; } if (ast_channel_pbx(c)) { - if (ast_channel_cdr(c)) { - elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec; + if (!ast_tvzero(ast_channel_creationtime(c))) { + elapsed_seconds = now.tv_sec - ast_channel_creationtime(c).tv_sec; } astman_append(s, "Event: Status\r\n" @@ -5120,7 +5120,7 @@ static int action_coresettings(struct mansession *s, const struct message *m) ast_config_AST_RUN_GROUP, option_maxfiles, AST_CLI_YESNO(ast_realtime_enabled()), - AST_CLI_YESNO(check_cdr_enabled()), + AST_CLI_YESNO(ast_cdr_is_enabled()), AST_CLI_YESNO(check_webmanager_enabled()) ); return 0; @@ -5228,8 +5228,8 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m ast_channel_lock(c); bc = ast_bridged_channel(c); - if (ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) { - duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000); + if (!ast_tvzero(ast_channel_creationtime(c))) { + duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(c)) / 1000); durh = duration / 3600; durm = (duration % 3600) / 60; durs = duration % 60; diff --git a/main/manager_channels.c b/main/manager_channels.c index 277dc873d86d98d1253a7af619e9e31e1b541b9a..5ae35b21d63eaa4af0dabd58997120ac29b69121 100644 --- a/main/manager_channels.c +++ b/main/manager_channels.c @@ -153,6 +153,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </syntax> </managerEventInstance> </managerEvent> + <managerEvent language="en_US" name="NewAccountCode"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a Channel's AccountCode is changed.</synopsis> + <syntax> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + <parameter name="OldAccountCode"> + <para>The channel's previous account code</para> + </parameter> + </syntax> + </managerEventInstance> + </managerEvent> <managerEvent language="en_US" name="DialBegin"> <managerEventInstance class="EVENT_FLAG_CALL"> <synopsis>Raised when a dial action has started.</synopsis> @@ -627,7 +638,8 @@ static struct ast_manager_event_blob *channel_newexten( return NULL; } - if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) { + if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot) + && !strcmp(old_snapshot->appl, new_snapshot->appl)) { return NULL; } @@ -662,10 +674,28 @@ static struct ast_manager_event_blob *channel_new_callerid( ast_describe_caller_presentation(new_snapshot->caller_pres)); } +static struct ast_manager_event_blob *channel_new_accountcode( + struct ast_channel_snapshot *old_snapshot, + struct ast_channel_snapshot *new_snapshot) +{ + if (!old_snapshot || !new_snapshot) { + return NULL; + } + + if (!strcmp(old_snapshot->accountcode, new_snapshot->accountcode)) { + return NULL; + } + + return ast_manager_event_blob_create( + EVENT_FLAG_CALL, "NewAccountCode", + "OldAccountCode: %s\r\n", old_snapshot->accountcode); +} + channel_snapshot_monitor channel_monitors[] = { channel_state_change, channel_newexten, - channel_new_callerid + channel_new_callerid, + channel_new_accountcode }; static void channel_snapshot_update(void *data, struct stasis_subscription *sub, diff --git a/main/pbx.c b/main/pbx.c index 2e8c321696d247ad8bb514c39d0f3a72b813e4da..af988dcf961efa46afcf781f5462309e2ff70666 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -470,36 +470,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <ref type="function">Exception</ref> </see-also> </application> - <application name="ResetCDR" language="en_US"> - <synopsis> - Resets the Call Data Record. - </synopsis> - <syntax> - <parameter name="options"> - <optionlist> - <option name="w"> - <para>Store the current CDR record before resetting it.</para> - </option> - <option name="a"> - <para>Store any stacked records.</para> - </option> - <option name="v"> - <para>Save CDR variables.</para> - </option> - <option name="e"> - <para>Enable CDR only (negate effects of NoCDR).</para> - </option> - </optionlist> - </parameter> - </syntax> - <description> - <para>This application causes the Call Data Record to be reset.</para> - </description> - <see-also> - <ref type="application">ForkCDR</ref> - <ref type="application">NoCDR</ref> - </see-also> - </application> <application name="Ringing" language="en_US"> <synopsis> Indicate ringing tone. @@ -657,9 +627,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </syntax> <description> <para>This application will set the channel's AMA Flags for billing purposes.</para> + <warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning> </description> <see-also> <ref type="function">CDR</ref> + <ref type="function">CHANNEL</ref> </see-also> </application> <application name="Wait" language="en_US"> @@ -1139,7 +1111,6 @@ static int pbx_builtin_background(struct ast_channel *, const char *); static int pbx_builtin_wait(struct ast_channel *, const char *); static int pbx_builtin_waitexten(struct ast_channel *, const char *); static int pbx_builtin_incomplete(struct ast_channel *, const char *); -static int pbx_builtin_resetcdr(struct ast_channel *, const char *); static int pbx_builtin_setamaflags(struct ast_channel *, const char *); static int pbx_builtin_ringing(struct ast_channel *, const char *); static int pbx_builtin_proceeding(struct ast_channel *, const char *); @@ -1326,7 +1297,6 @@ static struct pbx_builtin { { "Proceeding", pbx_builtin_proceeding }, { "Progress", pbx_builtin_progress }, { "RaiseException", pbx_builtin_raise_exception }, - { "ResetCDR", pbx_builtin_resetcdr }, { "Ringing", pbx_builtin_ringing }, { "SayAlpha", pbx_builtin_saycharacters }, { "SayDigits", pbx_builtin_saydigits }, @@ -1565,15 +1535,13 @@ int pbx_exec(struct ast_channel *c, /*!< Channel */ const char *saved_c_appl; const char *saved_c_data; - if (ast_channel_cdr(c) && !ast_check_hangup(c)) - ast_cdr_setapp(ast_channel_cdr(c), app->name, data); - /* save channel values */ saved_c_appl= ast_channel_appl(c); saved_c_data= ast_channel_data(c); ast_channel_appl_set(c, app->name); ast_channel_data_set(c, data); + ast_channel_publish_snapshot(c); if (app->module) u = __ast_module_user_add(app->module, c); @@ -5713,10 +5681,6 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context) ast_channel_lock(chan); - if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) { - ast_cdr_end(ast_channel_cdr(chan)); - } - /* Set h exten location */ if (context != ast_channel_context(chan)) { ast_channel_context_set(chan, context); @@ -5797,10 +5761,6 @@ int ast_pbx_hangup_handler_run(struct ast_channel *chan) return 0; } - if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) { - ast_cdr_end(ast_channel_cdr(chan)); - } - /* * Make sure that the channel is marked as hungup since we are * going to run the hangup handlers on it. @@ -6114,12 +6074,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, set_ext_pri(c, "s", 1); } - ast_channel_lock(c); - if (ast_channel_cdr(c)) { - /* allow CDR variables that have been collected after channel was created to be visible during call */ - ast_cdr_update(c); - } - ast_channel_unlock(c); for (;;) { char dst_exten[256]; /* buffer to accumulate digits */ int pos = 0; /* XXX should check bounds */ @@ -6229,11 +6183,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, } /* Call timed out with no special extension to jump to. */ } - ast_channel_lock(c); - if (ast_channel_cdr(c)) { - ast_cdr_update(c); - } - ast_channel_unlock(c); error = 1; break; } @@ -6339,12 +6288,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c, } } } - ast_channel_lock(c); - if (ast_channel_cdr(c)) { - ast_verb(2, "CDR updated on %s\n",ast_channel_name(c)); - ast_cdr_update(c); - } - ast_channel_unlock(c); } } @@ -9991,13 +9934,16 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co } dialed = ast_dial_get_channel(outgoing->dial, 0); + if (!dialed) { + return -1; + } ast_set_variables(dialed, vars); if (account) { - ast_cdr_setaccount(dialed, account); + ast_channel_accountcode_set(dialed, account); } - ast_set_flag(ast_channel_cdr(dialed), AST_CDR_FLAG_ORIGINATED); + ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED); if (!ast_strlen_zero(cid_num) && !ast_strlen_zero(cid_name)) { struct ast_party_connected_line connected; @@ -10043,12 +9989,13 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co /* Wait for dialing to complete */ if (channel || synchronous) { if (channel) { + ast_channel_ref(*channel); ast_channel_unlock(*channel); } while (!outgoing->dialed) { ast_cond_wait(&outgoing->cond, &outgoing->lock); } - if (channel) { + if (channel && *channel) { ast_channel_lock(*channel); } } @@ -10078,7 +10025,7 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co } if (account) { - ast_cdr_setaccount(failed, account); + ast_channel_accountcode_set(failed, account); } set_ext_pri(failed, "failed", 1); @@ -10387,8 +10334,8 @@ static int pbx_builtin_busy(struct ast_channel *chan, const char *data) /* Don't change state of an UP channel, just indicate busy in audio */ if (ast_channel_state(chan) != AST_STATE_UP) { + ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY); ast_setstate(chan, AST_STATE_BUSY); - ast_cdr_busy(ast_channel_cdr(chan)); } wait_for_hangup(chan, data); return -1; @@ -10403,8 +10350,8 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data) /* Don't change state of an UP channel, just indicate congestion in audio */ if (ast_channel_state(chan) != AST_STATE_UP) { + ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION); ast_setstate(chan, AST_STATE_BUSY); - ast_cdr_congestion(ast_channel_cdr(chan)); } wait_for_hangup(chan, data); return -1; @@ -10416,7 +10363,6 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data) static int pbx_builtin_answer(struct ast_channel *chan, const char *data) { int delay = 0; - int answer_cdr = 1; char *parse; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(delay); @@ -10424,7 +10370,7 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data) ); if (ast_strlen_zero(data)) { - return __ast_answer(chan, 0, 1); + return __ast_answer(chan, 0); } parse = ast_strdupa(data); @@ -10439,10 +10385,12 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data) } if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) { - answer_cdr = 0; + if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) { + ast_log(AST_LOG_WARNING, "Failed to disable CDR on %s\n", ast_channel_name(chan)); + } } - return __ast_answer(chan, delay, answer_cdr); + return __ast_answer(chan, delay); } static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) @@ -10459,7 +10407,7 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) if (ast_check_hangup(chan)) { return -1; } else if (ast_channel_state(chan) != AST_STATE_UP && answer) { - __ast_answer(chan, 0, 1); + __ast_answer(chan, 0); } ast_indicate(chan, AST_CONTROL_INCOMPLETE); @@ -10467,39 +10415,30 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data) return AST_PBX_INCOMPLETE; } -AST_APP_OPTIONS(resetcdr_opts, { - AST_APP_OPTION('w', AST_CDR_FLAG_POSTED), - AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED), - AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS), - AST_APP_OPTION('e', AST_CDR_FLAG_POST_ENABLE), -}); - /*! * \ingroup applications */ -static int pbx_builtin_resetcdr(struct ast_channel *chan, const char *data) +static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) { - char *args; - struct ast_flags flags = { 0 }; + ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n"); - if (!ast_strlen_zero(data)) { - args = ast_strdupa(data); - ast_app_parse_options(resetcdr_opts, &flags, NULL, args); + if (ast_strlen_zero(data)) { + ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n"); + return 0; } - - ast_cdr_reset(ast_channel_cdr(chan), &flags); - - return 0; -} - -/*! - * \ingroup applications - */ -static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data) -{ /* Copy the AMA Flags as specified */ ast_channel_lock(chan); - ast_cdr_setamaflags(chan, data ? data : ""); + if (isdigit(data[0])) { + int amaflags; + if (sscanf(data, "%30d", &amaflags) != 1) { + ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan)); + ast_channel_unlock(chan); + return 0; + } + ast_channel_amaflags_set(chan, amaflags); + } else { + ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data)); + } ast_channel_unlock(chan); return 0; } diff --git a/main/stasis.c b/main/stasis.c index e810dd852d5bfd9b5dea2df8e7c2af7eeff6cd74..406a1bb254b23cd778a9227921237398b14b9386 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 115bb7b67ba7bbafd15ee74de4627d72a8d3c420..5757c869a899db2c0fa42deb6e22ae9f159f94fd 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 2a88b00685ef23bec2fdb354d41c327c3fce6a0e..e76f258243d13365feede97f0a8be080670c9f08 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 2109c9478b1270afeb9ab9960fb017b3022bd85d..fdc4916e1653aa5cd71229576f94879e048697ed 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 fde9b953b74a3b4a8a30230aafcc0a5e9ac34e1c..1007254875eae441ac19113dfd4474393a872976 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 486310dd6bc2b4ca94c5f2cdca3bff1b0cf8908c..a841f3623508368f1e164f9ca32d8dd7e7a928bc 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 e648f941b7f699e8d555ab71b861833f16bca59c..7d5fd83e6a636409ad81355830cded72efb930f8 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 f1da4ec834b6390071ddf8d2748058ad60803c74..b5225010e2de14b34241109e4c530c4c5e2dfe08 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 b7534b93d90af042b00b593447612398a16e3f94..53d4b06e28abb6294940d6f5b0a68464504edcba 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 0000000000000000000000000000000000000000..c9621a450ff3ee2a185238671642f28e9db6b64b --- /dev/null +++ b/tests/test_cdr.c @@ -0,0 +1,2413 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Matt Jordan <mjordan@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief CDR unit tests + * + * \author Matt Jordan <mjordan@digium.com> + * + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <math.h> +#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, "<none>")); + 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");