Update Asterisk's CDRs for the new bridging framework
authorMatthew Jordan <mjordan@digium.com>
Mon, 17 Jun 2013 03:00:38 +0000 (03:00 +0000)
committerMatthew Jordan <mjordan@digium.com>
Mon, 17 Jun 2013 03:00:38 +0000 (03:00 +0000)
This patch is the initial push to update Asterisk's CDR engine for the new
bridging framework. This patch guts the existing CDR engine and builds the new
on top of messages coming across Stasis. As changes in channel state and bridge
state are detected, CDRs are built and dispatched accordingly. This
fundamentally changes CDRs in a few ways.
(1) CDRs are now *very* reflective of the actual state of channels and bridges.
    This means CDRs track well with what an actual channel is doing - which
    is useful in transfer scenarios (which were previously difficult to pin
    down). It does, however, mean that CDRs cannot be 'fooled'. Previous
    behavior in Asterisk allowed for CDR applications, channels, and other
    properties to be spoofed in parts of the code - this no longer works.
(2) CDRs have defined behavior in multi-party scenarios. This behavior will not
    be what everyone wants, but it is a defined behavior and as such, it is
    predictable.
(3) The CDR manipulation functions and applications have been overhauled. Major
    changes have been made to ResetCDR and ForkCDR in particular. Many of the
    options for these two applications no longer made any sense with the new
    framework and the (slightly) more immutable nature of CDRs.

There are a plethora of other changes. For a full description of CDR behavior,
see the CDR specification on the Asterisk wiki.

(closes issue ASTERISK-21196)

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

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@391947 65c4cc65-6c06-0410-ace0-fbb531ad65f3

68 files changed:
CHANGES
UPGRADE.txt
addons/cdr_mysql.c
addons/chan_ooh323.c
apps/app_authenticate.c
apps/app_cdr.c
apps/app_dial.c
apps/app_disa.c
apps/app_dumpchan.c
apps/app_followme.c
apps/app_forkcdr.c
apps/app_osplookup.c
apps/app_queue.c
cdr/cdr_adaptive_odbc.c
cdr/cdr_csv.c
cdr/cdr_custom.c
cdr/cdr_manager.c
cdr/cdr_odbc.c
cdr/cdr_pgsql.c
cdr/cdr_radius.c
cdr/cdr_syslog.c
cdr/cdr_tds.c
cel/cel_manager.c
cel/cel_radius.c
cel/cel_tds.c
channels/chan_agent.c
channels/chan_dahdi.c
channels/chan_h323.c
channels/chan_iax2.c
channels/chan_mgcp.c
channels/chan_sip.c
channels/chan_skinny.c
channels/chan_unistim.c
funcs/func_callerid.c
funcs/func_cdr.c
funcs/func_channel.c
include/asterisk/bridging.h
include/asterisk/cdr.h
include/asterisk/cel.h
include/asterisk/channel.h
include/asterisk/channel_internal.h
include/asterisk/stasis_channels.h
include/asterisk/stasis_internal.h [new file with mode: 0644]
include/asterisk/test.h
include/asterisk/time.h
main/asterisk.c
main/bridging.c
main/bridging_basic.c
main/cdr.c
main/cel.c
main/channel.c
main/channel_internal_api.c
main/cli.c
main/dial.c
main/features.c
main/manager.c
main/manager_channels.c
main/pbx.c
main/stasis.c
main/stasis_cache.c
main/stasis_channels.c
main/test.c
main/utils.c
res/res_agi.c
res/res_config_sqlite.c
res/res_monitor.c
res/res_stasis_answer.c
tests/test_cdr.c [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index f3d420b..b81c2da 100644 (file)
--- a/CHANGES
+++ b/CHANGES
 --- 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
 ------------------
index dfe8081..7a5261b 100644 (file)
 ===
 ===========================================================
 
+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.
index b87d8c6..23e96c5 100644 (file)
@@ -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) {
index a4f62ac..303b068 100644 (file)
@@ -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) {
index fbb4300..a837058 100644 (file)
@@ -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))))
index 3c24471..ba7139c 100644 (file)
@@ -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;
 }
 
index b0caa5c..c75eb2d 100644 (file)
@@ -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]);
index c43370c..fe53772 100644 (file)
@@ -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;
                }
index 722f155..7613832 100644 (file)
@@ -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"
index 6698000..d12de3c 100644 (file)
@@ -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);
 }
 
index 354792f..6231d38 100644 (file)
@@ -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)
index b37a2ae..5ab497f 100644 (file)
@@ -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);
index 4c96583..724ea47 100644 (file)
@@ -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,
index 4bf3602..a590fb3 100644 (file)
@@ -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 */
index 5cfde82..a6f8a4d 100644 (file)
@@ -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;
index 290e534..2a3b1a1 100644 (file)
@@ -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))) {
index a82bcf9..e3ae7a5 100644 (file)
@@ -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;
 }
index 7ea2f04..022d752 100644 (file)
@@ -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);
 
index 906f022..dc73de4 100644 (file)
@@ -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) {
index 92ec8a4..2bf2002 100644 (file)
@@ -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;
 
index 8a7f077..dec4d65 100644 (file)
@@ -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,
index dd75dbb..aef57b5 100644 (file)
@@ -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
                        );
                }
        }
index e1d0dc1..245c780 100644 (file)
@@ -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,
index 0edf57f..9067a04 100644 (file)
@@ -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;
        }
index df2b573..1bb4d51 100644 (file)
@@ -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) {
index d72254e..57f0914 100644 (file)
@@ -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;
                                }
index 109fac0..24337e3 100644 (file)
@@ -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
index 61817db..e26bb5f 100644 (file)
@@ -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 {
index 7c0de99..486af52 100644 (file)
@@ -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);
 
index 9080dba..5a0b7ad 100644 (file)
@@ -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 {
index eb79d23..fbd7f1c 100644 (file)
@@ -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");
index 5e07566..ad51edf 100644 (file)
@@ -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 {
index fbc6add..f512b0f 100644 (file)
@@ -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);
index 30026af..b4649c1 100644 (file)
@@ -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;
index adb4274..0f900fe 100644 (file)
@@ -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");
index 9379244..4327fd2 100644 (file)
@@ -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);
index 9d3f5b3..0bfcc32 100644 (file)
@@ -1032,6 +1032,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
  *
index c2268e3..548ca99 100644 (file)
 #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 */
index 034a96a..914037d 100644 (file)
@@ -36,20 +36,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
  */
 enum ast_cel_event_type {
@@ -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
  *
index 42d50f2..1b36c14 100644 (file)
@@ -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 */
@@ -1028,6 +1034,16 @@ enum channelreloadreason {
 };
 
 /*!
+ * \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
  *       take a channel argument.
@@ -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.
@@ -2197,6 +2211,28 @@ int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen,
 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.
  *
  * \note Use ast_channel_set_caller() and ast_channel_set_caller_event() instead.
@@ -2728,12 +2764,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 */
index 38776c1..a54a1b8 100644 (file)
@@ -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);
index 339c772..ca075ae 100644 (file)
  */
 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 (file)
index 0000000..67ab88f
--- /dev/null
@@ -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_ */
index d890b7a..d29969f 100644 (file)
@@ -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
@@ -247,6 +249,30 @@ 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
  *
  * \param test callback function (required)
@@ -267,6 +293,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
  *
@@ -278,6 +338,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.
  *
  * \param test currently executing test
index dd68db7..f2382df 100644 (file)
@@ -152,6 +152,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
  *
index 99da006..871cd97 100644 (file)
@@ -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);
        }
index 7300504..93e4ec2 100644 (file)
@@ -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;
index 43862a0..09f2ca5 100644 (file)
@@ -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);
 }
 
index a056067..9f710fe 100644 (file)
@@ -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
+            &