diff --git a/CHANGES b/CHANGES
index f3d420bc8f6c72499a62bc69b842555829863bc5..b81c2da287b2ee51f6e652454bd77bbfd7f4f013 100644
--- a/CHANGES
+++ b/CHANGES
@@ -11,6 +11,94 @@
 --- Functionality changes from Asterisk 11 to Asterisk 12 --------------------
 ------------------------------------------------------------------------------
 
+Applications
+------------------
+
+AgentMonitorOutgoing
+------------------
+ * The 'c' option has been removed. It is not possible to modify the name of a
+   channel involved in a CDR.
+
+ForkCDR
+------------------
+ * ForkCDR no longer automatically resets the forked CDR. See the 'r' option
+   for more information.
+
+ * Variables are no longer purged from the original CDR. See the 'v' option for
+   more information.
+
+ * The 'A' option has been removed. The Answer time on a CDR is never updated
+   once set.
+
+ * The 'd' option has been removed. The disposition on a CDR is a function of
+   the state of the channel and cannot be altered.
+
+ * The 'D' option has been removed. Who the Party B is on a CDR is a function
+   of the state of the respective channels, and cannot be altered.
+
+ * The 'r' option has been changed. Previously, ForkCDR always reset the CDR
+   such that the start time and, if applicable, the answer time was updated.
+   Now, by default, ForkCDR simply forks the CDR, maintaining any times. The
+   'r' option now triggers the Reset, setting the start time (and answer time
+   if applicable) to the current time.
+
+ * The 's' option has been removed. A variable can be set on the original CDR
+   if desired using the CDR function, and removed from a forked CDR using the
+   same function.
+
+ * The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no
+   longer applies in the CDR engine.
+
+ * The 'v' option now prevents the copy of the variables from the original CDR
+   to the forked CDR. Previously the variables were always copied but were
+   removed from the original. Removing variables from a CDR can have unintended
+   side effects - this option allows the user to prevent propagation of
+   variables from the original to the forked without modifying the original.
+
+MeetMe
+-------------------
+* Added the 'n' option to MeetMe to prevent application of the DENOISE function
+  to a channel joining a conference. Some channel drivers that vary the number
+  of audio samples in a voice frame will experience significant quality problems
+  if a denoiser is attached to the channel; this option gives them the ability
+  to remove the denoiser without having to unload func_speex.
+
+NoCDR
+------------------
+ * The NoCDR application is deprecated. Please use the CDR_PROP function to
+   disable CDRs.
+ * While the NoCDR application will prevent CDRs for a channel from being
+   propagated to registered CDR backends, it will not prevent that data from
+   being collected. Hence, a subsequent call to ResetCDR or the CDR_PROP
+   function that enables CDRs on a channel will restore those records that have
+   not yet been finalized.
+
+Queue
+-------------------
+ * Add queue available hint.  exten => 8501,hint,Queue:markq_avail
+   Note: the suffix '_avail' after the queuename.
+   Reports 'InUse' for no logged in agents or no free agents.
+   Reports 'Idle' when an agent is free.
+
+ResetCDR
+------------------
+ * The 'e' option has been deprecated. Use the CDR_PROP function to re-enable
+   CDRs when they were previously disabled on a channel.
+ * The 'w' and 'a' options have been removed. Dispatching CDRs to registered
+   backends occurs on an as-needed basis in order to preserve linkedid
+   propagation and other needed behavior.
+
+SetAMAFlags
+------------------
+ * This application is deprecated in favor of the CHANNEL function.
+
+
+Core
+------------------
+ * Redirecting reasons can now be set to arbitrary strings. This means
+   that the REDIRECTING dialplan function can be used to set the redirecting
+   reason to any string. It also allows for custom strings to be read as the
+   redirecting reason from SIP Diversion headers.
 
 AMI (Asterisk Manager Interface)
 ------------------
@@ -72,6 +160,9 @@ AMI (Asterisk Manager Interface)
    event, the various ChanVariable fields will contain a suffix that specifies
    which channel they correspond to.
 
+* The NewPeerAccount AMI event is no longer raised. The NewAccountCode AMI
+  event always conveys the AMI event for a particular channel.
+
  * All "Reload" events have been consolidated into a single event type. This
    event will always contain a Module field specifying the name of the module
    and a Status field denoting the result of the reload. All modules now issue
@@ -118,33 +209,21 @@ AGI (Asterisk Gateway Interface)
  * The manager event AsyncAGI has been split into AsyncAGIStart, AsyncAGIExec,
    and AsyncAGIEnd.
 
-Channel Drivers
-------------------
- * When a channel driver is configured to enable jiterbuffers, they are now
-   applied unconditionally when a channel joins a bridge. If a jitterbuffer
-   is already set for that channel when it enters, such as by the JITTERBUFFER
-   function, then the existing jitterbuffer will be used and the one set by
-   the channel driver will not be applied.
-
-chan_local
-------------------
- * The /b option is removed.
-
- * chan_local moved into the system core and is no longer a loadable module.
-
-chan_mobile
+CDR (Call Detail Records)
 ------------------
- * Added general support for busy detection.
+ * Significant changes have been made to the behavior of CDRs. For a full
+   definition of CDR behavior in Asterisk 12, please read the specification
+   on the Asterisk wiki (wiki.asterisk.org).
 
- * Added ECAM command support for Sony Ericsson phones.
+ * CDRs will now be created between all participants in a bridge. For each
+   pair of channels in a bridge, a CDR is created to represent the path of
+   communication between those two endpoints. This lets an end user choose who
+   to bill for what during multi-party bridges or bridge operations during
+   transfer scenarios.
 
-chan_sip
-------------------
- * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf
-   using the 'supportpath' setting, either on a global basis or on a peer basis.
-   This setting enables Asterisk to route outgoing out-of-dialog requests via a
-   set of proxies by using a pre-loaded route-set defined by the Path headers in
-   the REGISTER request. See Realtime updates for more configuration information.
+ * When a CDR is dispatched, user defined CDR variables from both parties are
+   included in the resulting CDR. If both parties have the same variable, only
+   the Party A value is provided.
 
 Features
 -------------------
@@ -163,12 +242,6 @@ Features
    and FEATUREMAP() functions inherited to child channels by setting
    FEATURE(inherit)=yes.
 
-Functions
-------------------
- * JITTERBUFFER now accepts an argument of 'disabled' which can be used
-   to remove jitterbuffers previously set on a channel with JITTERBUFFER.
-   The value of this setting is ignored when disabled is used for the argument.
-
 Logging
 -------------------
  * When performing queue pause/unpause on an interface without specifying an
@@ -178,25 +251,12 @@ Logging
  * Added the 'queue_log_realtime_use_gmt' option to have timestamps in GMT
    for realtime queue log entries.
 
-MeetMe
--------------------
-* Added the 'n' option to MeetMe to prevent application of the DENOISE function
-  to a channel joining a conference. Some channel drivers that vary the number
-  of audio samples in a voice frame will experience significant quality problems
-  if a denoiser is attached to the channel; this option gives them the ability
-  to remove the denoiser without having to unload func_speex.
-
 Parking
 -------------------
  * Parking is now implemented as a module instead of as core functionality.
    The preferred way to configure parking is now through res_parking.conf while
    configuration through features.conf is not currently supported.
 
- * res_parking uses the configuration framework. If an invalid configuration is
-   supplied, res_parking will fail to load or fail to reload. Previously parking
-   lots that were misconfigured would generally be accepted with certain
-   configuration problems leading to individual disabled parking lots.
-
  * Parked calls are now placed in bridges. This is a largely architectural change,
    but it could have some implications in allowing for new parked call retrieval
    methods and the contents of parking lots will be visible though certain bridge
@@ -236,63 +296,96 @@ Parking
    by default. Instead, it will follow the timeout rules of the parking lot. The
    old behavior can be reproduced by using the 'c' option.
 
- * Added a channel variable PARKER_FLAT which stores the name of the extension
-   that would be used to come back to if comebacktoorigin was set to use. This can
-   be useful when comebacktoorigin is off if you still want to use the extensions
-   in the park-dial context that are generated to redial the parker on timeout.
+Realtime
+------------------
+ * Dynamic realtime tables for SIP Users can now include a 'path' field. This
+   will store the path information for that peer when it registers. Realtime
+   tables can also use the 'supportpath' field to enable Path header support.
 
-Queue
--------------------
- * Add queue available hint.  exten => 8501,hint,Queue:markq_avail
-   Note: the suffix '_avail' after the queuename.
-   Reports 'InUse' for no logged in agents or no free agents.
-   Reports 'Idle' when an agent is free.
+ * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport
+   objectIdentifier. This maps to the supportpath option in sip.conf.
+
+Sorcery
+------------------
+ * All future modules which utilize Sorcery for object persistence must have a
+   column named "id" within their schema when using the Sorcery realtime module.
+   This column must be able to contain a string of up to 128 characters in length.
 
- * The configuration options eventwhencalled and eventmemberstatus have been
-   removed.  As a result, the AMI events QueueMemberStatus, AgentCalled,
-   AgentConnect, AgentComplete, AgentDump, and AgentRingNoAnswer will always be
-   sent.  The "Variable" fields will also no longer exist on the Agent* events.
 
-Core
+Channel Drivers
 ------------------
- * Redirecting reasons can now be set to arbitrary strings. This means
-   that the REDIRECTING dialplan function can be used to set the redirecting
-   reason to any string. It also allows for custom strings to be read as the
-   redirecting reason from SIP Diversion headers.
+ * When a channel driver is configured to enable jiterbuffers, they are now
+   applied unconditionally when a channel joins a bridge. If a jitterbuffer
+   is already set for that channel when it enters, such as by the JITTERBUFFER
+   function, then the existing jitterbuffer will be used and the one set by
+   the channel driver will not be applied.
+
+chan_agent
+------------------
+ * The updatecdr option has been removed. Altering the names of channels on a
+   CDR is not supported - the name of the channel is the name of the channel,
+   and pretending otherwise helps no one.
+ * The AGENTUPDATECDR channel variable has also been removed, for the same
+   reason as the updatecdr option.
+
+chan_local
+------------------
+ * The /b option is removed.
 
- * For DTMF blind and attended transfers, the channel variable TRANSFER_CONTEXT
-   must be on the channel initiating the transfer to have any effect.
+ * chan_local moved into the system core and is no longer a loadable module.
 
- * The channel variable ATTENDED_TRANSFER_COMPLETE_SOUND is no longer channel
-   driver specific.  If the channel variable is set on the transferrer channel,
-   the sound will be played to the target of an attended transfer.
+chan_mobile
+------------------
+ * Added general support for busy detection.
 
- * The channel variable BRIDGEPEER becomes a comma separated list of peers in
-   a multi-party bridge.  The BRIDGEPEER value can have a maximum of 10 peers
-   listed.  Any more peers in the bridge will not be included in the list.
-   BRIDGEPEER is not valid in holding bridges like parking since those channels
-   do not talk to each other even though they are in a bridge.
+ * Added ECAM command support for Sony Ericsson phones.
 
- * The channel variable BRIDGEPVTCALLID is only valid for two party bridges
-   and will contain a value if the BRIDGEPEER's channel driver supports it.
+chan_sip
+------------------
+ * Added support for RFC 3327 "Path" headers. This can be enabled in sip.conf
+   using the 'supportpath' setting, either on a global basis or on a peer basis.
+   This setting enables Asterisk to route outgoing out-of-dialog requests via a
+   set of proxies by using a pre-loaded route-set defined by the Path headers in
+   the REGISTER request. See Realtime updates for more configuration information.
 
- * The channel variable DYNAMIC_PEERNAME is redundant with BRIDGEPEER and is
-   removed.  The more useful DYNAMIC_WHO_ACTIVATED gives the channel name that
-   activated the dynamic feature.
 
- * The channel variables DYNAMIC_FEATURENAME and DYNAMIC_WHO_ACTIVATED are set
-   only on the channel executing the dynamic feature.  Executing a dynamic
-   feature on the bridge peer in a multi-party bridge will execute it on all
-   peers of the activating channel.
+Functions
+------------------
 
-Realtime
+JITTERBUFFER
 ------------------
- * Dynamic realtime tables for SIP Users can now include a 'path' field. This
-   will store the path information for that peer when it registers. Realtime
-   tables can also use the 'supportpath' field to enable Path header support.
+ * JITTERBUFFER now accepts an argument of 'disabled' which can be used
+   to remove jitterbuffers previously set on a channel with JITTERBUFFER.
+   The value of this setting is ignored when disabled is used for the argument.
 
- * LDAP realtime configurations for SIP Users now have the AstAccountPathSupport
-   objectIdentifier. This maps to the supportpath option in sip.conf.
+CDR (function)
+------------------
+ * The 'amaflags' and 'accountcode' attributes for the CDR function are
+   deprecated. Use the CHANNEL function instead to access these attributes.
+ * The 'l' option has been removed. When reading a CDR attribute, the most
+   recent record is always used. When writing a CDR attribute, all non-finalized
+   CDRs are updated.
+ * The 'r' option has been removed, for the same reason as the 'l' option.
+ * The 's' option has been removed, as LOCKED semantics no longer exist in the
+   CDR engine.
+
+CDR_PROP
+------------------
+ * A new function CDR_PROP has been added. This function lets you set properties
+   on a channel's active CDRs. This function is write-only. Properties accept
+   boolean values to set/clear them on the channel's CDRs. Valid properties
+   include:
+   * 'party_a' - make this channel the preferred Party A in any CDR between two
+     channels. If two channels have this property set, the creation time of the
+     channel is used to determine who is Party A. Note that dialed channels are
+     never Party A in a CDR.
+   * 'disable' - disable CDRs on this channel. This is analogous to the NoCDR
+     application when set to True, and analogous to the 'e' option in ResetCDR
+     when set to False.
+
+
+Resources
+------------------
 
 RTP
 ------------------
diff --git a/UPGRADE.txt b/UPGRADE.txt
index dfe808141ee32f875bd8c7ef98523d0a373a1997..7a5261b94d7da4c2f4fbb45f38617b13f99e296d 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -21,6 +21,49 @@
 ===
 ===========================================================
 
+AgentMonitorOutgoing
+ - The 'c' option has been removed. It is not possible to modify the name of a
+   channel involved in a CDR.
+
+NoCDR:
+ - This application is deprecated. Please use the CDR_PROP function instead.
+
+ResetCDR:
+ - The 'w' and 'a' options have been removed. Dispatching CDRs to registered
+   backends occurs on an as-needed basis in order to preserve linkedid
+   propagation and other needed behavior.
+ - The 'e' option is deprecated. Please use the CDR_PROP function to enable
+   CDRs on a channel that they were previously disabled on.
+ - The ResetCDR application is no longer a part of core Asterisk, and instead
+   is now delivered as part of app_cdr.
+
+ForkCDR:
+ - ForkCDR no longer automatically resets the forked CDR. See the 'r' option
+   for more information.
+ - Variables are no longer purged from the original CDR. See the 'v' option for
+   more information.
+ - The 'A' option has been removed. The Answer time on a CDR is never updated
+   once set.
+ - The 'd' option has been removed. The disposition on a CDR is a function of
+   the state of the channel and cannot be altered.
+ - The 'D' option has been removed. Who the Party B is on a CDR is a function
+   of the state of the respective channels, and cannot be altered.
+ - The 'r' option has been changed. Previously, ForkCDR always reset the CDR
+   such that the start time and, if applicable, the answer time was updated.
+   Now, by default, ForkCDR simply forks the CDR, maintaining any times. The
+   'r' option now triggers the Reset, setting the start time (and answer time
+   if applicable) to the current time.
+ - The 's' option has been removed. A variable can be set on the original CDR
+   if desired using the CDR function, and removed from a forked CDR using the
+   same function.
+ - The 'T' option has been removed. The concept of DONT_TOUCH and LOCKED no
+   longer applies in the CDR engine.
+ - The 'v' option now prevents the copy of the variables from the original CDR
+   to the forked CDR. Previously the variables were always copied but were
+   removed from the original. Removing variables from a CDR can have unintended
+   side effects - this option allows the user to prevent propagation of
+   variables from the original to the forked without modifying the original.
+
 AMI:
  - The SIP SIPqualifypeer action now sends a response indicating it will qualify
    a peer once a peer has been found to qualify.  Once the qualify has been
@@ -72,6 +115,16 @@ SendDTMF:
  - Now recognizes 'W' to pause sending DTMF for one second in addition to
    the previously existing 'w' that paused sending DTMF for half a second.
 
+SetAMAFlags
+ - This application is deprecated in favor of the CHANNEL function.
+
+chan_agent:
+ - The updatecdr option has been removed. Altering the names of channels on a
+   CDR is not supported - the name of the channel is the name of the channel,
+   and pretending otherwise helps no one.
+ - The AGENTUPDATECDR channel variable has also been removed, for the same
+   reason as the updatecdr option.
+
 chan_dahdi:
  - Analog port dialing and deferred DTMF dialing for PRI now distinguishes
    between 'w' and 'W'.  The 'w' pauses dialing for half a second.  The 'W'
@@ -79,7 +132,7 @@ chan_dahdi:
  - The default for inband_on_proceeding has changed to no.
 
 chan_local:
- - The /b option is removed.
+ - The /b option has been removed.
 
 Dialplan:
  - All channel and global variable names are evaluated in a case-sensitive manner.
diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c
index b87d8c6aa14d59a4bfbcccacdf3469c023e36e7f..23e96c562f5e533e6cf97dc7e48c389ee8e349e4 100644
--- a/addons/cdr_mysql.c
+++ b/addons/cdr_mysql.c
@@ -251,7 +251,7 @@ db_reconnect:
 					char timestr[128];
 					ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
 					ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
-					ast_cdr_setvar(cdr, "calldate", timestr, 0);
+					ast_cdr_setvar(cdr, "calldate", timestr);
 					cdrname = "calldate";
 				} else {
 					cdrname = "start";
@@ -277,9 +277,9 @@ db_reconnect:
 				 strstr(entry->type, "real") ||
 				 strstr(entry->type, "numeric") ||
 				 strstr(entry->type, "fixed"))) {
-				ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 1);
+				ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1);
 			} else {
-				ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 0);
+				ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0);
 			}
 
 			if (value) {
diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c
index a4f62acd8993032f0c14a5be0576981a080163fa..303b06857a4cfb4dce5bab22e8f1843c08e0f9e7 100644
--- a/addons/chan_ooh323.c
+++ b/addons/chan_ooh323.c
@@ -2376,7 +2376,7 @@ static struct ooh323_user *build_user(const char *name, struct ast_variable *v)
 				ast_parse_allow_disallow(&user->prefs,
 					 user->cap,  tcodecs, 1);
 			} else if (!strcasecmp(v->name, "amaflags")) {
-				user->amaflags = ast_cdr_amaflags2int(v->value);
+				user->amaflags = ast_channel_string2amaflag(v->value);
          		} else if (!strcasecmp(v->name, "ip") || !strcasecmp(v->name, "host")) {
 				struct ast_sockaddr p;
 				if (!ast_parse_arg(v->value, PARSE_ADDR, &p)) {
@@ -2560,7 +2560,7 @@ static struct ooh323_peer *build_peer(const char *name, struct ast_variable *v,
 				ast_parse_allow_disallow(&peer->prefs, peer->cap, 
 												 tcodecs, 1);				 
 			} else if (!strcasecmp(v->name,  "amaflags")) {
-				peer->amaflags = ast_cdr_amaflags2int(v->value);
+				peer->amaflags = ast_channel_string2amaflag(v->value);
 			} else if (!strcasecmp(v->name, "roundtrip")) {
 				sscanf(v->value, "%d,%d", &peer->rtdrcount, &peer->rtdrinterval);
 			} else if (!strcasecmp(v->name, "dtmfmode")) {
@@ -2917,7 +2917,7 @@ int reload_config(int reload)
 											"'lowdelay', 'throughput', 'reliability', "
 											"'mincost', or 'none'\n", v->lineno);
 		} else if (!strcasecmp(v->name, "amaflags")) {
-			gAMAFLAGS = ast_cdr_amaflags2int(v->value);
+			gAMAFLAGS = ast_channel_string2amaflag(v->value);
 		} else if (!strcasecmp(v->name, "accountcode")) {
          ast_copy_string(gAccountcode, v->value, sizeof(gAccountcode));
 		} else if (!strcasecmp(v->name, "disallow")) {
@@ -3117,7 +3117,7 @@ static char *handle_cli_ooh323_show_peer(struct ast_cli_entry *e, int cmd, struc
 		}
 
 		ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", peer->accountcode);
-		ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(peer->amaflags));
+		ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_channel_amaflags2string(peer->amaflags));
 		ast_cli(a->fd, "%-15.15s%s\n", "IP:Port: ", ip_port);
 		ast_cli(a->fd, "%-15.15s%d\n", "OutgoingLimit: ", peer->outgoinglimit);
 		ast_cli(a->fd, "%-15.15s%d\n", "rtptimeout: ", peer->rtptimeout);
@@ -3276,7 +3276,7 @@ static char *handle_cli_ooh323_show_user(struct ast_cli_entry *e, int cmd, struc
 		}
 
 		ast_cli(a->fd, "%-15.15s%s\n", "AccountCode: ", user->accountcode);
-		ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_cdr_flags2str(user->amaflags));
+		ast_cli(a->fd, "%-15.15s%s\n", "AMA flags: ", ast_channel_amaflags2string(user->amaflags));
 		ast_cli(a->fd, "%-15.15s%s\n", "Context: ", user->context);
 		ast_cli(a->fd, "%-15.15s%d\n", "IncomingLimit: ", user->incominglimit);
 		ast_cli(a->fd, "%-15.15s%d\n", "InUse: ", user->inUse);
@@ -3524,7 +3524,7 @@ static char *handle_cli_ooh323_show_config(struct ast_cli_entry *e, int cmd, str
 
 	ast_cli(a->fd, "%-20s%ld\n", "Call counter: ", callnumber);
 	ast_cli(a->fd, "%-20s%s\n", "AccountCode: ", gAccountcode);
-	ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_cdr_flags2str(gAMAFLAGS));
+	ast_cli(a->fd, "%-20s%s\n", "AMA flags: ", ast_channel_amaflags2string(gAMAFLAGS));
 
 	pAlias = gAliasList;
 	if(pAlias) {
diff --git a/apps/app_authenticate.c b/apps/app_authenticate.c
index fbb430089524a38723f1bf789aabdc1a8462131a..a8370588b8fbf9829e6fb91264f200e9fd817f7f 100644
--- a/apps/app_authenticate.c
+++ b/apps/app_authenticate.c
@@ -213,9 +213,9 @@ static int auth_exec(struct ast_channel *chan, const char *data)
 						continue;
 					ast_md5_hash(md5passwd, passwd);
 					if (!strcmp(md5passwd, md5secret)) {
-						if (ast_test_flag(&flags,OPT_ACCOUNT)) {
+						if (ast_test_flag(&flags, OPT_ACCOUNT)) {
 							ast_channel_lock(chan);
-							ast_cdr_setaccount(chan, buf);
+							ast_channel_accountcode_set(chan, buf);
 							ast_channel_unlock(chan);
 						}
 						break;
@@ -224,7 +224,7 @@ static int auth_exec(struct ast_channel *chan, const char *data)
 					if (!strcmp(passwd, buf)) {
 						if (ast_test_flag(&flags, OPT_ACCOUNT)) {
 							ast_channel_lock(chan);
-							ast_cdr_setaccount(chan, buf);
+							ast_channel_accountcode_set(chan, buf);
 							ast_channel_unlock(chan);
 						}
 						break;
@@ -250,7 +250,7 @@ static int auth_exec(struct ast_channel *chan, const char *data)
 	if ((retries < 3) && !res) {
 		if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE)) {
 			ast_channel_lock(chan);
-			ast_cdr_setaccount(chan, passwd);
+			ast_channel_accountcode_set(chan, passwd);
 			ast_channel_unlock(chan);
 		}
 		if (!(res = ast_streamfile(chan, "auth-thankyou", ast_channel_language(chan))))
diff --git a/apps/app_cdr.c b/apps/app_cdr.c
index 3c244712bb8a217b3fbd03c552fc06b598768abe..ba7139cf1a5b77da99559ab089d3e87b6de49095 100644
--- a/apps/app_cdr.c
+++ b/apps/app_cdr.c
@@ -35,25 +35,114 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "asterisk/channel.h"
 #include "asterisk/module.h"
+#include "asterisk/app.h"
 
 /*** DOCUMENTATION
 	<application name="NoCDR" language="en_US">
 		<synopsis>
-			Tell Asterisk to not maintain a CDR for the current call
+			Tell Asterisk to not maintain a CDR for this channel.
 		</synopsis>
 		<syntax />
 		<description>
-			<para>This application will tell Asterisk not to maintain a CDR for the current call.</para>
+			<para>This application will tell Asterisk not to maintain a CDR for
+			the current channel. This does <emphasis>NOT</emphasis> mean that
+			information is not tracked; rather, if the channel is hung up no
+			CDRs will be created for that channel.</para>
+			<para>If a subsequent call to ResetCDR occurs, all non-finalized
+			CDRs created for the channel will be enabled.</para>
+			<note><para>This application is deprecated. Please use the CDR_PROP
+			function to disable CDRs on a channel.</para></note>
 		</description>
+		<see-also>
+			<ref type="application">ResetCDR</ref>
+			<ref type="function">CDR_PROP</ref>
+		</see-also>
+	</application>
+	<application name="ResetCDR" language="en_US">
+		<synopsis>
+			Resets the Call Data Record.
+		</synopsis>
+		<syntax>
+			<parameter name="options">
+				<optionlist>
+					<option name="v">
+						<para>Save the CDR variables during the reset.</para>
+					</option>
+					<option name="e">
+						<para>Enable the CDRs for this channel only (negate
+						effects of NoCDR).</para>
+					</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application causes the Call Data Record to be reset.
+			Depending on the flags passed in, this can have several effects.
+			With no options, a reset does the following:</para>
+			<para>1. The <literal>start</literal> time is set to the current time.</para>
+			<para>2. If the channel is answered, the <literal>answer</literal> time is set to the
+			current time.</para>
+			<para>3. All variables are wiped from the CDR. Note that this step
+			can be prevented with the <literal>v</literal> option.</para>
+			<para>On the other hand, if the <literal>e</literal> option is
+			specified, the effects of the NoCDR application will be lifted. CDRs
+			will be re-enabled for this channel.</para>
+			<note><para>The <literal>e</literal> option is deprecated. Please
+			use the CDR_PROP function instead.</para></note>
+		</description>
+		<see-also>
+			<ref type="application">ForkCDR</ref>
+			<ref type="application">NoCDR</ref>
+			<ref type="function">CDR_PROP</ref>
+		</see-also>
 	</application>
  ***/
 
 static const char nocdr_app[] = "NoCDR";
+static const char resetcdr_app[] = "ResetCDR";
+
+enum reset_cdr_options {
+	OPT_DISABLE_DISPATCH = (1 << 0),
+	OPT_KEEP_VARS = (1 << 1),
+	OPT_ENABLE = (1 << 2),
+};
+
+AST_APP_OPTIONS(resetcdr_opts, {
+	AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
+	AST_APP_OPTION('e', AST_CDR_FLAG_DISABLE_ALL),
+});
+
+static int resetcdr_exec(struct ast_channel *chan, const char *data)
+{
+	char *args;
+	struct ast_flags flags = { 0 };
+	int res = 0;
+
+	if (!ast_strlen_zero(data)) {
+		args = ast_strdupa(data);
+		ast_app_parse_options(resetcdr_opts, &flags, NULL, args);
+	}
+
+	if (ast_test_flag(&flags, AST_CDR_FLAG_DISABLE_ALL)) {
+		if (ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
+			res = 1;
+		}
+	}
+	if (ast_cdr_reset(ast_channel_name(chan), &flags)) {
+		res = 1;
+	}
+
+	if (res) {
+		ast_log(AST_LOG_WARNING, "Failed to reset CDR for channel %s\n", ast_channel_name(chan));
+	}
+	return res;
+}
 
 static int nocdr_exec(struct ast_channel *chan, const char *data)
 {
-	if (ast_channel_cdr(chan))
-		ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED);
+	if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
+		ast_log(AST_LOG_WARNING, "Failed to disable CDR for channel %s\n", ast_channel_name(chan));
+	}
 
 	return 0;
 }
@@ -65,8 +154,14 @@ static int unload_module(void)
 
 static int load_module(void)
 {
-	if (ast_register_application_xml(nocdr_app, nocdr_exec))
+	int res = 0;
+
+	res |= ast_register_application_xml(nocdr_app, nocdr_exec);
+	res |= ast_register_application_xml(resetcdr_app, resetcdr_exec);
+
+	if (res) {
 		return AST_MODULE_LOAD_FAILURE;
+	}
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
diff --git a/apps/app_dial.c b/apps/app_dial.c
index b0caa5c6be4535bce95ed68a8f66bf3852f1e29a..c75eb2d3a4f7ea1bbf70af28a85d539aace85704 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -55,7 +55,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/app.h"
 #include "asterisk/causes.h"
 #include "asterisk/rtp_engine.h"
-#include "asterisk/cdr.h"
 #include "asterisk/manager.h"
 #include "asterisk/privacy.h"
 #include "asterisk/stringfields.h"
@@ -753,36 +752,20 @@ struct cause_args {
 
 static void handle_cause(int cause, struct cause_args *num)
 {
-	struct ast_cdr *cdr = ast_channel_cdr(num->chan);
-
 	switch(cause) {
 	case AST_CAUSE_BUSY:
-		if (cdr)
-			ast_cdr_busy(cdr);
 		num->busy++;
 		break;
-
 	case AST_CAUSE_CONGESTION:
-		if (cdr)
-			ast_cdr_failed(cdr);
 		num->congestion++;
 		break;
-
 	case AST_CAUSE_NO_ROUTE_DESTINATION:
 	case AST_CAUSE_UNREGISTERED:
-		if (cdr)
-			ast_cdr_failed(cdr);
 		num->nochan++;
 		break;
-
 	case AST_CAUSE_NO_ANSWER:
-		if (cdr) {
-			ast_cdr_noanswer(cdr);
-		}
-		break;
 	case AST_CAUSE_NORMAL_CLEARING:
 		break;
-
 	default:
 		num->nochan++;
 		break;
@@ -974,7 +957,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num,
 
 		ast_channel_appl_set(c, "AppDial");
 		ast_channel_data_set(c, "(Outgoing Line)");
-		ast_publish_channel_state(c);
+		ast_channel_publish_snapshot(c);
 
 		ast_channel_unlock(in);
 		if (single && !ast_test_flag64(o, OPT_IGNORE_CONNECTEDLINE)) {
@@ -1096,7 +1079,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 				 */
 				*to = -1;
 				strcpy(pa->status, "CONGESTION");
-				ast_cdr_failed(ast_channel_cdr(in));
 				ast_channel_publish_dial(in, outgoing->chan, NULL, pa->status);
 				return NULL;
 			}
@@ -1301,10 +1283,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 						peer = c;
 						ast_channel_publish_dial(in, peer, NULL, "ANSWER");
 						publish_dial_end_event(in, out_chans, peer, "CANCEL");
-						if (ast_channel_cdr(peer)) {
-							ast_channel_cdr(peer)->answer = ast_tvnow();
-							ast_channel_cdr(peer)->disposition = AST_CDR_ANSWERED;
-						}
 						ast_copy_flags64(peerflags, o,
 							OPT_CALLEE_TRANSFER | OPT_CALLER_TRANSFER |
 							OPT_CALLEE_HANGUP | OPT_CALLER_HANGUP |
@@ -1326,7 +1304,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 				case AST_CONTROL_BUSY:
 					ast_verb(3, "%s is busy\n", ast_channel_name(c));
 					ast_channel_hangupcause_set(in, ast_channel_hangupcause(c));
-					ast_channel_publish_dial(in, c, NULL, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(c)));
+					ast_channel_publish_dial(in, c, NULL, "BUSY");
 					ast_hangup(c);
 					c = o->chan = NULL;
 					ast_clear_flag64(o, DIAL_STILLGOING);
@@ -1335,7 +1313,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 				case AST_CONTROL_CONGESTION:
 					ast_verb(3, "%s is circuit-busy\n", ast_channel_name(c));
 					ast_channel_hangupcause_set(in, ast_channel_hangupcause(c));
-					ast_channel_publish_dial(in, c, NULL, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(c)));
+					ast_channel_publish_dial(in, c, NULL, "CONGESTION");
 					ast_hangup(c);
 					c = o->chan = NULL;
 					ast_clear_flag64(o, DIAL_STILLGOING);
@@ -1542,7 +1520,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 				/* Got hung up */
 				*to = -1;
 				strcpy(pa->status, "CANCEL");
-				ast_cdr_noanswer(ast_channel_cdr(in));
 				publish_dial_end_event(in, out_chans, NULL, pa->status);
 				if (f) {
 					if (f->data.uint32) {
@@ -1565,7 +1542,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 					if (onedigit_goto(in, context, (char) f->subclass.integer, 1)) {
 						ast_verb(3, "User hit %c to disconnect call.\n", f->subclass.integer);
 						*to = 0;
-						ast_cdr_noanswer(ast_channel_cdr(in));
 						*result = f->subclass.integer;
 						strcpy(pa->status, "CANCEL");
 						publish_dial_end_event(in, out_chans, NULL, pa->status);
@@ -1584,7 +1560,6 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 					ast_verb(3, "User requested call disconnect.\n");
 					*to = 0;
 					strcpy(pa->status, "CANCEL");
-					ast_cdr_noanswer(ast_channel_cdr(in));
 					publish_dial_end_event(in, out_chans, NULL, pa->status);
 					ast_frfree(f);
 					if (is_cc_recall) {
@@ -1679,13 +1654,10 @@ skip_frame:;
 		}
 	}
 
-	if (!*to) {
+	if (!*to || ast_check_hangup(in)) {
 		ast_verb(3, "Nobody picked up in %d ms\n", orig);
 		publish_dial_end_event(in, out_chans, NULL, "NOANSWER");
 	}
-	if (!*to || ast_check_hangup(in)) {
-		ast_cdr_noanswer(ast_channel_cdr(in));
-	}
 
 #ifdef HAVE_EPOLL
 	AST_LIST_TRAVERSE(out_chans, epollo, node) {
@@ -1985,22 +1957,13 @@ static void end_bridge_callback(void *data)
 	time_t end;
 	struct ast_channel *chan = data;
 
-	if (!ast_channel_cdr(chan)) {
-		return;
-	}
-
 	time(&end);
 
 	ast_channel_lock(chan);
-	if (ast_channel_cdr(chan)->answer.tv_sec) {
-		snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec);
-		pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
-	}
-
-	if (ast_channel_cdr(chan)->start.tv_sec) {
-		snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec);
-		pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
-	}
+	snprintf(buf, sizeof(buf), "%d", ast_channel_get_up_time(chan));
+	pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
+	snprintf(buf, sizeof(buf), "%d", ast_channel_get_duration(chan));
+	pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
 	ast_channel_unlock(chan);
 }
 
@@ -2294,8 +2257,9 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		ast_channel_unlock(chan);
 	}
 
-	if (ast_test_flag64(&opts, OPT_RESETCDR) && ast_channel_cdr(chan))
-		ast_cdr_reset(ast_channel_cdr(chan), NULL);
+	if (ast_test_flag64(&opts, OPT_RESETCDR)) {
+		ast_cdr_reset(ast_channel_name(chan), 0);
+	}
 	if (ast_test_flag64(&opts, OPT_PRIVACY) && ast_strlen_zero(opt_args[OPT_ARG_PRIVACY]))
 		opt_args[OPT_ARG_PRIVACY] = ast_strdupa(ast_channel_exten(chan));
 
@@ -2489,7 +2453,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 
 		ast_channel_appl_set(tc, "AppDial");
 		ast_channel_data_set(tc, "(Outgoing Line)");
-		ast_publish_channel_state(tc);
+		ast_channel_publish_snapshot(tc);
 
 		memset(ast_channel_whentohangup(tc), 0, sizeof(*ast_channel_whentohangup(tc)));
 
@@ -2620,10 +2584,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		res = ast_call(tmp->chan, tmp->number, 0); /* Place the call, but don't wait on the answer */
 		ast_channel_lock(chan);
 
-		/* Save the info in cdr's that we called them */
-		if (ast_channel_cdr(chan))
-			ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(tmp->chan));
-
 		/* check the results of ast_call */
 		if (res) {
 			/* Again, keep going even if there's an error */
@@ -2738,10 +2698,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		   conversation.  */
 		hanguptree(&out_chans, peer, 1);
 		/* If appropriate, log that we have a destination channel and set the answer time */
-		if (ast_channel_cdr(chan)) {
-			ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
-			ast_cdr_setanswer(ast_channel_cdr(chan), ast_channel_cdr(peer)->answer);
-		}
 		if (ast_channel_name(peer))
 			pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", ast_channel_name(peer));
 		
@@ -2836,10 +2792,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		}
 
 		if (chan && peer && ast_test_flag64(&opts, OPT_GOTO) && !ast_strlen_zero(opt_args[OPT_ARG_GOTO])) {
-			/* chan and peer are going into the PBX, they both
-			 * should probably get CDR records. */
-			ast_clear_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED);
-			ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_DIALED);
+			/* chan and peer are going into the PBX; as such neither are considered
+			 * outgoing channels any longer */
+			ast_clear_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
+			ast_clear_flag(ast_channel_flags(peer), AST_FLAG_OUTGOING);
 
 			ast_replace_subargument_delimiter(opt_args[OPT_ARG_GOTO]);
 			ast_parseable_goto(chan, opt_args[OPT_ARG_GOTO]);
diff --git a/apps/app_disa.c b/apps/app_disa.c
index c43370c95ee3717885f74822fe2f2b1b9951f022..fe53772f1e7dd2d083ed825f7c91bb26ce5816da 100644
--- a/apps/app_disa.c
+++ b/apps/app_disa.c
@@ -362,7 +362,7 @@ static int disa_exec(struct ast_channel *chan, const char *data)
 
 	if (k == 3) {
 		int recheck = 0;
-		struct ast_flags cdr_flags = { AST_CDR_FLAG_POSTED };
+		struct ast_flags cdr_flags = { AST_CDR_FLAG_DISABLE, };
 
 		if (!ast_exists_extension(chan, args.context, exten, 1,
 			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
@@ -384,8 +384,10 @@ static int disa_exec(struct ast_channel *chan, const char *data)
 			if (!ast_strlen_zero(acctcode))
 				ast_channel_accountcode_set(chan, acctcode);
 
-			if (special_noanswer) cdr_flags.flags = 0;
-			ast_cdr_reset(ast_channel_cdr(chan), &cdr_flags);
+			if (special_noanswer) {
+				ast_clear_flag(&cdr_flags, AST_CDR_FLAG_DISABLE);
+			}
+			ast_cdr_reset(ast_channel_name(chan), &cdr_flags);
 			ast_explicit_goto(chan, args.context, exten, 1);
 			return 0;
 		}
diff --git a/apps/app_dumpchan.c b/apps/app_dumpchan.c
index 722f15541757cd9545900d0cfa11a61b8eeaba6c..7613832d4d614bbc4ee2c09ffc1f17d0b7abdd14 100644
--- a/apps/app_dumpchan.c
+++ b/apps/app_dumpchan.c
@@ -70,7 +70,6 @@ static const char app[] = "DumpChan";
 
 static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
 {
-	struct timeval now;
 	long elapsed_seconds = 0;
 	int hour = 0, min = 0, sec = 0;
 	char nf[256];
@@ -80,21 +79,19 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
 	struct ast_str *read_transpath = ast_str_alloca(256);
 	struct ast_bridge *bridge;
 
-	now = ast_tvnow();
 	memset(buf, 0, size);
 	if (!c)
 		return 0;
 
-	if (ast_channel_cdr(c)) {
-		elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
-		hour = elapsed_seconds / 3600;
-		min = (elapsed_seconds % 3600) / 60;
-		sec = elapsed_seconds % 60;
-	}
+	elapsed_seconds = ast_channel_get_duration(c);
+	hour = elapsed_seconds / 3600;
+	min = (elapsed_seconds % 3600) / 60;
+	sec = elapsed_seconds % 60;
 
 	ast_channel_lock(c);
 	bridge = ast_channel_get_bridge(c);
 	ast_channel_unlock(c);
+
 	snprintf(buf,size,
 		"Name=               %s\n"
 		"Type=               %s\n"
diff --git a/apps/app_followme.c b/apps/app_followme.c
index 66980009d4e36243271cde4e861c13ca1dcc8347..d12de3c1a9d044b8e44920566fe8b30d7e335e8c 100644
--- a/apps/app_followme.c
+++ b/apps/app_followme.c
@@ -578,29 +578,6 @@ static void clear_caller(struct findme_user *tmpuser)
 	}
 
 	outbound = tmpuser->ochan;
-	ast_channel_lock(outbound);
-	if (!ast_channel_cdr(outbound)) {
-		ast_channel_cdr_set(outbound, ast_cdr_alloc());
-		if (ast_channel_cdr(outbound)) {
-			ast_cdr_init(ast_channel_cdr(outbound), outbound);
-		}
-	}
-	if (ast_channel_cdr(outbound)) {
-		char tmp[256];
-
-		snprintf(tmp, sizeof(tmp), "Local/%s", tmpuser->dialarg);
-		ast_cdr_setapp(ast_channel_cdr(outbound), "FollowMe", tmp);
-		ast_cdr_update(outbound);
-		ast_cdr_start(ast_channel_cdr(outbound));
-		ast_cdr_end(ast_channel_cdr(outbound));
-		/* If the cause wasn't handled properly */
-		if (ast_cdr_disposition(ast_channel_cdr(outbound), ast_channel_hangupcause(outbound))) {
-			ast_cdr_failed(ast_channel_cdr(outbound));
-		}
-	} else {
-		ast_log(LOG_WARNING, "Unable to create Call Detail Record\n");
-	}
-	ast_channel_unlock(outbound);
 	ast_hangup(outbound);
 	tmpuser->ochan = NULL;
 }
@@ -1127,11 +1104,6 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel
 				 * Destoy all new outgoing calls.
 				 */
 				while ((tmpuser = AST_LIST_REMOVE_HEAD(&new_user_list, entry))) {
-					ast_channel_lock(tmpuser->ochan);
-					if (ast_channel_cdr(tmpuser->ochan)) {
-						ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan);
-					}
-					ast_channel_unlock(tmpuser->ochan);
 					destroy_calling_node(tmpuser);
 				}
 
@@ -1153,11 +1125,6 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel
 				AST_LIST_REMOVE_CURRENT(entry);
 
 				/* Destroy this failed new outgoing call. */
-				ast_channel_lock(tmpuser->ochan);
-				if (ast_channel_cdr(tmpuser->ochan)) {
-					ast_cdr_init(ast_channel_cdr(tmpuser->ochan), tmpuser->ochan);
-				}
-				ast_channel_unlock(tmpuser->ochan);
 				destroy_calling_node(tmpuser);
 				continue;
 			}
@@ -1310,15 +1277,10 @@ static void end_bridge_callback(void *data)
 	time(&end);
 
 	ast_channel_lock(chan);
-	if (ast_channel_cdr(chan)->answer.tv_sec) {
-		snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->answer.tv_sec);
-		pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
-	}
-
-	if (ast_channel_cdr(chan)->start.tv_sec) {
-		snprintf(buf, sizeof(buf), "%ld", (long) end - ast_channel_cdr(chan)->start.tv_sec);
-		pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
-	}
+	snprintf(buf, sizeof(buf), "%d", ast_channel_get_up_time(chan));
+	pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", buf);
+	snprintf(buf, sizeof(buf), "%d", ast_channel_get_duration(chan));
+	pbx_builtin_setvar_helper(chan, "DIALEDTIME", buf);
 	ast_channel_unlock(chan);
 }
 
diff --git a/apps/app_forkcdr.c b/apps/app_forkcdr.c
index 354792fb9a49d80b8b6e7154a95aaff66894a8d4..6231d381f7dad9635234c56605fbbe07ec7cee4c 100644
--- a/apps/app_forkcdr.c
+++ b/apps/app_forkcdr.c
@@ -44,98 +44,46 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 /*** DOCUMENTATION
 	<application name="ForkCDR" language="en_US">
 		<synopsis>
-			Forks the Call Data Record.
+			Forks the current Call Data Record for this channel.
 		</synopsis>
 		<syntax>
 			<parameter name="options">
 				<optionlist>
 					<option name="a">
-						<para>Update the answer time on the NEW CDR just after it's been inited.
-						The new CDR may have been answered already. The reset that forkcdr does
-						will erase the answer time. This will bring it back, but the answer time
-						will be a copy of the fork/start time. It will only do this if the initial
-						cdr was indeed already answered.</para>
-					</option>
-					<option name="A">
-						<para>Lock the original CDR against the answer time being updated. This
-						will allow the disposition on the original CDR to remain the same.</para>
-					</option>
-					<option name="d">
-						<para>Copy the disposition forward from the old cdr, after the init.</para>
-					</option>
-					<option name="D">
-						<para>Clear the <literal>dstchannel</literal> on the new CDR after
-						reset.</para>
+						<para>If the channel is answered, set the answer time on
+						the forked CDR to the current time. If this option is
+						not used, the answer time on the forked CDR will be the
+						answer time on the original CDR. If the channel is not
+						answered, this option has no effect.</para>
+						<para>Note that this option is implicitly assumed if the
+						<literal>r</literal> option is used.</para>
 					</option>
 					<option name="e">
-						<para>End the original CDR. Do this after all the necessary data is copied
-						from the original CDR to the new forked CDR.</para>
+						<para>End (finalize) the original CDR.</para>
 					</option>
 					<option name="r">
-						<para>Do <emphasis>NOT</emphasis> reset the new cdr.</para>
-					</option>
-					<option name="s(name=val)">
-						<para>Set the CDR var <replaceable>name</replaceable> in the original CDR,
-						with value <replaceable>val</replaceable>.</para>
-					</option>
-					<option name="T">
-						<para>Mark the original CDR with a DONT_TOUCH flag. setvar, answer, and end
-						cdr funcs will obey this flag; normally they don't honor the LOCKED flag
-						set on the original CDR record.</para>
-						<note><para>Using this flag may cause CDR's not to have their end times
-						updated! It is suggested that if you specify this flag, you might wish
-						to use the <literal>e</literal> flag as well!.</para></note>
+						<para>Reset the start and answer times on the forked CDR.
+						This will set the start and answer times (if the channel
+						is answered) to be set to the current time.</para>
+						<para>Note that this option implicitly assumes the
+						<literal>a</literal> option.</para>
 					</option>
 					<option name="v">
-						<para>When the new CDR is forked, it gets a copy of the vars attached to
-						the current CDR. The vars attached to the original CDR are removed unless
-						this option is specified.</para>
+						<para>Do not copy CDR variables and attributes from the
+						original CDR to the forked CDR.</para>
+						<warning><para>This option has changed. Previously, the
+						variables were removed from the original CDR. This no
+						longer occurs - this option now controls whether or not
+						a forked CDR inherits the variables from the original
+						CDR.</para></warning>
 					</option>
 				</optionlist>
 			</parameter>
 		</syntax>
 		<description>
-			<para> Causes the Call Data Record to fork an additional cdr record starting from the time
-			of the fork call. This new cdr record will be linked to end of the list of cdr records attached
-			to the channel.	The original CDR has a LOCKED flag set, which forces most cdr operations to skip
-			it, except for the functions that set the answer and end times, which ignore the LOCKED flag. This
-			allows all the cdr records in the channel to be 'ended' together when the channel is closed.</para>
-			<para>The CDR() func (when setting CDR values) normally ignores the LOCKED flag also, but has options
-			to vary its behavior. The 'T' option (described below), can override this behavior, but beware
-			the risks.</para>
-			<para>First, this app finds the last cdr record in the list, and makes a copy of it. This new copy
-			will be the newly forked cdr record. Next, this new record is linked to the end of the cdr record list.
-			Next, The new cdr record is RESET (unless you use an option to prevent this)</para>
-			<para>This means that:</para>
-			<para>   1. All flags are unset on the cdr record</para>
-			<para>   2. the start, end, and answer times are all set to zero.</para>
-			<para>   3. the billsec and duration fields are set to zero.</para>
-			<para>   4. the start time is set to the current time.</para>
-			<para>   5. the disposition is set to NULL.</para>
-			<para>Next, unless you specified the <literal>v</literal> option, all variables will be removed from
-			the original cdr record. Thus, the <literal>v</literal> option allows any CDR variables to be replicated
-			to all new forked cdr records. Without the <literal>v</literal> option, the variables on the original
-			are effectively moved to the new forked cdr record.</para>
-			<para>Next, if the <literal>s</literal> option is set, the provided variable and value are set on the
-			original cdr record.</para>
-			<para>Next, if the <literal>a</literal> option is given, and the original cdr record has an answer time
-			set, then the new forked cdr record will have its answer time set to its start time. If the old answer
-			time were carried forward, the answer time would be earlier than the start time, giving strange
-			duration and billsec times.</para>
-			<para>If the <literal>d</literal> option was specified, the disposition is copied from
-			the original cdr record to the new forked cdr. If the <literal>D</literal> option was specified,
-			the destination channel field in the new forked CDR is erased. If the <literal>e</literal> option
-			was specified, the 'end' time for the original cdr record is set to the current time. Future hang-up or
-			ending events will not override this time stamp. If the <literal>A</literal> option is specified,
-			the original cdr record will have it ANS_LOCKED flag set, which prevent future answer events from updating
-			the original cdr record's disposition. Normally, an <literal>ANSWERED</literal> event would mark all cdr
-			records in the chain as <literal>ANSWERED</literal>. If the <literal>T</literal> option is specified,
-			the original cdr record will have its <literal>DONT_TOUCH</literal> flag set, which will force the
-			cdr_answer, cdr_end, and cdr_setvar functions to leave that cdr record alone.</para>
-			<para>And, last but not least, the original cdr record has its LOCKED flag set. Almost all internal
-			CDR functions (except for the funcs that set the end, and answer times, and set a variable) will honor
-			this flag and leave a LOCKED cdr record alone. This means that the newly created forked cdr record
-			will be affected by events transpiring within Asterisk, with the previously noted exceptions.</para>
+			<para>Causes the Call Data Record engine to fork a new CDR starting
+			from the time the application is executed. The forked CDR will be
+			linked to the end of the CDRs associated with the channel.</para>
 		</description>
 		<see-also>
 			<ref type="function">CDR</ref>
@@ -147,126 +95,34 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 static char *app = "ForkCDR";
 
-enum {
-	OPT_SETANS =            (1 << 0),
-	OPT_SETDISP =           (1 << 1),
-	OPT_RESETDEST =         (1 << 2),
-	OPT_ENDCDR =            (1 << 3),
-	OPT_NORESET =           (1 << 4),
-	OPT_KEEPVARS =          (1 << 5),
-	OPT_VARSET =            (1 << 6),
-	OPT_ANSLOCK =           (1 << 7),
-	OPT_DONTOUCH =          (1 << 8),
-};
-
-enum {
-	OPT_ARG_VARSET = 0,
-	/* note: this entry _MUST_ be the last one in the enum */
-	OPT_ARG_ARRAY_SIZE,
-};
-
 AST_APP_OPTIONS(forkcdr_exec_options, {
-	AST_APP_OPTION('a', OPT_SETANS),
-	AST_APP_OPTION('A', OPT_ANSLOCK),
-	AST_APP_OPTION('d', OPT_SETDISP),
-	AST_APP_OPTION('D', OPT_RESETDEST),
-	AST_APP_OPTION('e', OPT_ENDCDR),
-	AST_APP_OPTION('R', OPT_NORESET),
-	AST_APP_OPTION_ARG('s', OPT_VARSET, OPT_ARG_VARSET),
-	AST_APP_OPTION('T', OPT_DONTOUCH),
-	AST_APP_OPTION('v', OPT_KEEPVARS),
+	AST_APP_OPTION('a', AST_CDR_FLAG_SET_ANSWER),
+	AST_APP_OPTION('e', AST_CDR_FLAG_FINALIZE),
+	AST_APP_OPTION('r', AST_CDR_FLAG_RESET),
+	AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
 });
 
-static void ast_cdr_fork(struct ast_channel *chan, struct ast_flags optflags, char *set) 
-{
-	struct ast_cdr *cdr;
-	struct ast_cdr *newcdr;
-	struct ast_flags flags = { AST_CDR_FLAG_KEEP_VARS };
-
-	cdr = ast_channel_cdr(chan);
-
-	while (cdr->next)
-		cdr = cdr->next;
-	
-	if (!(newcdr = ast_cdr_dup_unique(cdr)))
-		return;
-	
-	/*
-	 * End the original CDR if requested BEFORE appending the new CDR
-	 * otherwise we incorrectly end the new CDR also.
-	 */
-	if (ast_test_flag(&optflags, OPT_ENDCDR)) {
-		ast_cdr_end(cdr);
-	}
-
-	ast_cdr_append(cdr, newcdr);
-
-	if (!ast_test_flag(&optflags, OPT_NORESET))
-		ast_cdr_reset(newcdr, &flags);
-		
-	if (!ast_test_flag(cdr, AST_CDR_FLAG_KEEP_VARS))
-		ast_cdr_free_vars(cdr, 0);
-	
-	if (!ast_strlen_zero(set)) {
-		char *varname = ast_strdupa(set), *varval;
-		varval = strchr(varname,'=');
-		if (varval) {
-			*varval = 0;
-			varval++;
-			ast_cdr_setvar(cdr, varname, varval, 0);
-		}
-	}
-	
-	if (ast_test_flag(&optflags, OPT_SETANS) && !ast_tvzero(cdr->answer))
-		newcdr->answer = newcdr->start;
-
-	if (ast_test_flag(&optflags, OPT_SETDISP))
-		newcdr->disposition = cdr->disposition;
-	
-	if (ast_test_flag(&optflags, OPT_RESETDEST))
-		newcdr->dstchannel[0] = 0;
-	
-	if (ast_test_flag(&optflags, OPT_ANSLOCK))
-		ast_set_flag(cdr, AST_CDR_FLAG_ANSLOCKED);
-	
-	if (ast_test_flag(&optflags, OPT_DONTOUCH))
-		ast_set_flag(cdr, AST_CDR_FLAG_DONT_TOUCH);
-		
-	ast_set_flag(cdr, AST_CDR_FLAG_CHILD | AST_CDR_FLAG_LOCKED);
-}
-
 static int forkcdr_exec(struct ast_channel *chan, const char *data)
 {
-	int res = 0;
-	char *argcopy = NULL;
-	struct ast_flags flags = {0};
-	char *opts[OPT_ARG_ARRAY_SIZE];
-	AST_DECLARE_APP_ARGS(arglist,
+	char *parse;
+	struct ast_flags flags = { 0, };
+	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(options);
 	);
 
-	if (!ast_channel_cdr(chan)) {
-		ast_log(LOG_WARNING, "Channel does not have a CDR\n");
-		return 0;
-	}
-
-	argcopy = ast_strdupa(data);
+	parse = ast_strdupa(data);
 
-	AST_STANDARD_APP_ARGS(arglist, argcopy);
+	AST_STANDARD_APP_ARGS(args, parse);
 
-	opts[OPT_ARG_VARSET] = 0;
-
-	if (!ast_strlen_zero(arglist.options))
-		ast_app_parse_options(forkcdr_exec_options, &flags, opts, arglist.options);
+	if (!ast_strlen_zero(args.options)) {
+		ast_app_parse_options(forkcdr_exec_options, &flags, NULL, args.options);
+	}
 
-	if (!ast_strlen_zero(data)) {
-		int keepvars = ast_test_flag(&flags, OPT_KEEPVARS) ? 1 : 0;
-		ast_set2_flag(ast_channel_cdr(chan), keepvars, AST_CDR_FLAG_KEEP_VARS);
+	if (ast_cdr_fork(ast_channel_name(chan), &flags)) {
+		ast_log(AST_LOG_WARNING, "Failed to fork CDR for channel %s\n", ast_channel_name(chan));
 	}
-	
-	ast_cdr_fork(chan, flags, opts[OPT_ARG_VARSET]);
 
-	return res;
+	return 0;
 }
 
 static int unload_module(void)
diff --git a/apps/app_osplookup.c b/apps/app_osplookup.c
index b37a2ae63a91d161e7d60e006c9e85c61fcf5f8a..5ab497f5e43be274ed0500866c4d7237454de5d7 100644
--- a/apps/app_osplookup.c
+++ b/apps/app_osplookup.c
@@ -2814,7 +2814,7 @@ static int ospfinished_exec(
 	int inhandle = OSP_INVALID_HANDLE;
 	int outhandle = OSP_INVALID_HANDLE;
 	int recorded = 0;
-	time_t start, connect, end;
+	time_t start = 0, connect = 0, end = 0;
 	unsigned int release;
 	char buffer[OSP_SIZE_INTSTR];
 	char inqos[OSP_SIZE_QOSSTR] = { 0 };
@@ -2866,19 +2866,18 @@ static int ospfinished_exec(
 	}
 	ast_debug(1, "OSPFinish: cause '%d'\n", cause);
 
-	if (ast_channel_cdr(chan)) {
-		start = ast_channel_cdr(chan)->start.tv_sec;
-		connect = ast_channel_cdr(chan)->answer.tv_sec;
-		if (connect) {
-			end = time(NULL);
-		} else {
-			end = connect;
-		}
+	if (!ast_tvzero(ast_channel_creationtime(chan))) {
+		start = ast_channel_creationtime(chan).tv_sec;
+	}
+	if (!ast_tvzero(ast_channel_answertime(chan))) {
+		connect = ast_channel_answertime(chan).tv_sec;
+	}
+	if (connect) {
+		end = time(NULL);
 	} else {
-		start = 0;
-		connect = 0;
-		end = 0;
+		end = connect;
 	}
+
 	ast_debug(1, "OSPFinish: start '%ld'\n", start);
 	ast_debug(1, "OSPFinish: connect '%ld'\n", connect);
 	ast_debug(1, "OSPFinish: end '%ld'\n", end);
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 4c96583137dfa5b076756580b3e804073a4edfaa..724ea47ff3cb74e448e9b5e666ac8656ac6ba697 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -3666,10 +3666,10 @@ static void publish_dial_end_event(struct ast_channel *in, struct callattempt *o
 	struct callattempt *cur;
 
 	for (cur = outgoing; cur; cur = cur->q_next) {
-                if (cur->chan && cur->chan != exception) {
+		if (cur->chan && cur->chan != exception) {
 			ast_channel_publish_dial(in, cur->chan, NULL, status);
-                }
-        }
+		}
+	}
 }
 
 /*! \brief Hang up a list of outgoing calls */
@@ -3931,9 +3931,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
 
 		member_call_pending_clear(tmp->member);
 
-		if (ast_channel_cdr(qe->chan)) {
-			ast_cdr_busy(ast_channel_cdr(qe->chan));
-		}
+		/* BUGBUG: Raise a BUSY dial end message here */
 		tmp->stillgoing = 0;
 		++*busies;
 		return 0;
@@ -3987,21 +3985,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
 	} else {
 		ast_channel_exten_set(tmp->chan, ast_channel_exten(qe->chan));
 	}
-	if (ast_cdr_isset_unanswered()) {
-		/* they want to see the unanswered dial attempts! */
-		/* set up the CDR fields on all the CDRs to give sensical information */
-		ast_cdr_setdestchan(ast_channel_cdr(tmp->chan), ast_channel_name(tmp->chan));
-		strcpy(ast_channel_cdr(tmp->chan)->clid, ast_channel_cdr(qe->chan)->clid);
-		strcpy(ast_channel_cdr(tmp->chan)->channel, ast_channel_cdr(qe->chan)->channel);
-		strcpy(ast_channel_cdr(tmp->chan)->src, ast_channel_cdr(qe->chan)->src);
-		strcpy(ast_channel_cdr(tmp->chan)->dst, ast_channel_exten(qe->chan));
-		strcpy(ast_channel_cdr(tmp->chan)->dcontext, ast_channel_context(qe->chan));
-		strcpy(ast_channel_cdr(tmp->chan)->lastapp, ast_channel_cdr(qe->chan)->lastapp);
-		strcpy(ast_channel_cdr(tmp->chan)->lastdata, ast_channel_cdr(qe->chan)->lastdata);
-		ast_channel_cdr(tmp->chan)->amaflags = ast_channel_cdr(qe->chan)->amaflags;
-		strcpy(ast_channel_cdr(tmp->chan)->accountcode, ast_channel_cdr(qe->chan)->accountcode);
-		strcpy(ast_channel_cdr(tmp->chan)->userfield, ast_channel_cdr(qe->chan)->userfield);
-	}
 
 	ast_channel_unlock(tmp->chan);
 	ast_channel_unlock(qe->chan);
@@ -4371,14 +4354,14 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
 		if (pos == 1 /* not found */) {
 			if (numlines == (numbusies + numnochan)) {
 				ast_debug(1, "Everyone is busy at this time\n");
-				if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
-					ast_cdr_busy(ast_channel_cdr(in));
-				}
+				/* BUGBUG: We shouldn't have to set anything here, as each
+				 * individual dial attempt should have set that CDR to busy
+				 */
 			} else {
 				ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan);
-				if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
-					ast_cdr_failed(ast_channel_cdr(in));
-				}
+				/* BUGBUG: We shouldn't have to set anything here, as each
+				 * individual dial attempt should have set that CDR to busy
+				 */
 			}
 			*to = 0;
 			return NULL;
@@ -4609,9 +4592,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
 							break;
 						case AST_CONTROL_BUSY:
 							ast_verb(3, "%s is busy\n", ochan_name);
-							if (ast_channel_cdr(in)) {
-								ast_cdr_busy(ast_channel_cdr(in));
-							}
 							ast_channel_publish_dial(qe->chan, o->chan, on, "BUSY");
 							do_hang(o);
 							endtime = (long) time(NULL);
@@ -4631,9 +4611,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
 							break;
 						case AST_CONTROL_CONGESTION:
 							ast_verb(3, "%s is circuit-busy\n", ochan_name);
-							if (ast_channel_cdr(in)) {
-								ast_cdr_failed(ast_channel_cdr(in));
-							}
 							ast_channel_publish_dial(qe->chan, o->chan, on, "CONGESTION");
 							endtime = (long) time(NULL);
 							endtime -= starttime;
@@ -4769,9 +4746,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
 				*to = 0;
 				publish_dial_end_event(in, outgoing, NULL, "CANCEL");
 				ast_frfree(f);
-				if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
-					ast_cdr_noanswer(ast_channel_cdr(in));
-				}
 				return NULL;
 			}
 			if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass.integer)) {
@@ -4780,9 +4754,6 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
 				publish_dial_end_event(in, outgoing, NULL, "CANCEL");
 				*digit = f->subclass.integer;
 				ast_frfree(f);
-				if (ast_channel_cdr(in) && ast_channel_state(in) != AST_STATE_UP) {
-					ast_cdr_noanswer(ast_channel_cdr(in));
-				}
 				return NULL;
 			}
 
@@ -4839,11 +4810,6 @@ skip_frame:;
 		}
 
 		publish_dial_end_event(qe->chan, outgoing, NULL, "NOANSWER");
-		if (ast_channel_cdr(in)
-			&& ast_channel_state(in) != AST_STATE_UP
-			&& (!*to || ast_check_hangup(in))) {
-			ast_cdr_noanswer(ast_channel_cdr(in));
-		}
 	}
 
 #ifdef HAVE_EPOLL
@@ -5678,20 +5644,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 		if (res == -1) {
 			ast_debug(1, "%s: Nobody answered.\n", ast_channel_name(qe->chan));
 		}
-		if (ast_cdr_isset_unanswered()) {
-			/* channel contains the name of one of the outgoing channels
-			   in its CDR; zero out this CDR to avoid a dual-posting */
-			struct callattempt *o;
-			for (o = outgoing; o; o = o->q_next) {
-				if (!o->chan) {
-					continue;
-				}
-				if (strcmp(ast_channel_cdr(o->chan)->dstchannel, ast_channel_cdr(qe->chan)->dstchannel) == 0) {
-					ast_set_flag(ast_channel_cdr(o->chan), AST_CDR_FLAG_POST_DISABLED);
-					break;
-				}
-			}
-		}
 	} else { /* peer is valid */
 		RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 		/* Ah ha!  Someone answered within the desired timeframe.  Of course after this
@@ -5785,17 +5737,14 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 		} else {
 			ast_moh_stop(qe->chan);
 		}
-		/* If appropriate, log that we have a destination channel */
-		if (ast_channel_cdr(qe->chan)) {
-			ast_cdr_setdestchan(ast_channel_cdr(qe->chan), ast_channel_name(peer));
-		}
+
 		/* Make sure channels are compatible */
 		res = ast_channel_make_compatible(qe->chan, peer);
 		if (res < 0) {
 			ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "SYSCOMPAT", "%s", "");
 			ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", ast_channel_name(qe->chan), ast_channel_name(peer));
 			record_abandoned(qe);
-			ast_cdr_failed(ast_channel_cdr(qe->chan));
+			ast_channel_publish_dial(qe->chan, peer, member->interface, ast_hangup_cause_to_dial_status(ast_channel_hangupcause(peer)));
 			ast_autoservice_chan_hangup_peer(qe->chan, peer);
 			ao2_ref(member, -1);
 			return -1;
@@ -5855,10 +5804,10 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 				ast_channel_unlock(qe->chan);
 				if (monitorfilename) {
 					ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1, X_REC_IN | X_REC_OUT);
-				} else if (ast_channel_cdr(qe->chan)) {
-					ast_monitor_start(which, qe->parent->monfmt, ast_channel_cdr(qe->chan)->uniqueid, 1, X_REC_IN | X_REC_OUT);
+				} else if (qe->chan) {
+					ast_monitor_start(which, qe->parent->monfmt, ast_channel_uniqueid(qe->chan), 1, X_REC_IN | X_REC_OUT);
 				} else {
-					/* Last ditch effort -- no CDR, make up something */
+					/* Last ditch effort -- no channel, make up something */
 					snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
 					ast_monitor_start(which, qe->parent->monfmt, tmpid, 1, X_REC_IN | X_REC_OUT);
 				}
@@ -5871,8 +5820,8 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 				if (mixmonapp) {
 					ast_debug(1, "Starting MixMonitor as requested.\n");
 					if (!monitorfilename) {
-						if (ast_channel_cdr(qe->chan)) {
-							ast_copy_string(tmpid, ast_channel_cdr(qe->chan)->uniqueid, sizeof(tmpid));
+						if (qe->chan) {
+							ast_copy_string(tmpid, ast_channel_uniqueid(qe->chan), sizeof(tmpid));
 						} else {
 							snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
 						}
@@ -5944,14 +5893,15 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 					}
 					
 					ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs);
-					/* We purposely lock the CDR so that pbx_exec does not update the application data */
-					if (ast_channel_cdr(qe->chan)) {
-						ast_set_flag(ast_channel_cdr(qe->chan), AST_CDR_FLAG_LOCKED);
-					}
+					/* BUGBUG
+					 * This needs to be done differently. We need to start a MixMonitor on
+					 * the actual queue bridge itself, not drop some channel out and pull it
+					 * back. Once the media channel work is done, start a media channel on
+					 * the bridge.
+					 *
+					 * Alternatively, don't use pbx_exec to put an audio hook on a channel.
+					 */
 					pbx_exec(qe->chan, mixmonapp, mixmonargs);
-					if (ast_channel_cdr(qe->chan)) {
-						ast_clear_flag(ast_channel_cdr(qe->chan), AST_CDR_FLAG_LOCKED);
-					}
 				} else {
 					ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n");
 				}
@@ -6039,33 +5989,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 		ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "CONNECT", "%ld|%s|%ld", (long) time(NULL) - qe->start, ast_channel_uniqueid(peer),
 													(long)(orig - to > 0 ? (orig - to) / 1000 : 0));
 
-		if (ast_channel_cdr(qe->chan)) {
-			struct ast_cdr *cdr;
-			struct ast_cdr *newcdr;
-
-			/* Only work with the last CDR in the stack*/
-			cdr = ast_channel_cdr(qe->chan);
-			while (cdr->next) {
-				cdr = cdr->next;
-			}
-
-			/* If this CDR is not related to us add new one*/
-			if ((strcasecmp(cdr->uniqueid, ast_channel_uniqueid(qe->chan))) &&
-			    (strcasecmp(cdr->linkedid, ast_channel_uniqueid(qe->chan))) &&
-			    (newcdr = ast_cdr_dup(cdr))) {
-				ast_channel_lock(qe->chan);
-				ast_cdr_init(newcdr, qe->chan);
-				ast_cdr_reset(newcdr, 0);
-				cdr = ast_cdr_append(cdr, newcdr);
-				cdr = cdr->next;
-				ast_channel_unlock(qe->chan);
-			}
-
-			if (update_cdr) {
-				ast_copy_string(cdr->dstchannel, member->membername, sizeof(cdr->dstchannel));
-			}
-		}
-
 		blob = ast_json_pack("{s: s, s: s, s: s, s: i, s: i}",
 				     "Queue", queuename,
 				     "Interface", member->interface,
diff --git a/cdr/cdr_adaptive_odbc.c b/cdr/cdr_adaptive_odbc.c
index 4bf3602cbc278eeca609860527a7f7d5db6415fa..a590fb32a1ec1dccc49604d090a17b4993d4de53 100644
--- a/cdr/cdr_adaptive_odbc.c
+++ b/cdr/cdr_adaptive_odbc.c
@@ -433,7 +433,7 @@ static int odbc_log(struct ast_cdr *cdr)
 				ast_strftime(colbuf, sizeof(colbuf), "%Y-%m-%d %H:%M:%S", &tm);
 				colptr = colbuf;
 			} else {
-				ast_cdr_getvar(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), 0, datefield ? 0 : 1);
+				ast_cdr_format_var(cdr, entry->cdrname, &colptr, colbuf, sizeof(colbuf), datefield ? 0 : 1);
 			}
 
 			if (colptr) {
@@ -472,9 +472,9 @@ static int odbc_log(struct ast_cdr *cdr)
 					 * form (but only when we're dealing with a character-based field).
 					 */
 					if (strcasecmp(entry->name, "disposition") == 0) {
-						ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
+						ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0);
 					} else if (strcasecmp(entry->name, "amaflags") == 0) {
-						ast_cdr_getvar(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0, 0);
+						ast_cdr_format_var(cdr, entry->name, &colptr, colbuf, sizeof(colbuf), 0);
 					}
 
 					/* Truncate too-long fields */
diff --git a/cdr/cdr_csv.c b/cdr/cdr_csv.c
index 5cfde82d7da7086b303146c8a07f05aaabe7f51c..a6f8a4dc0a33cb508fd96bb4b69e61b9f4a8f424 100644
--- a/cdr/cdr_csv.c
+++ b/cdr/cdr_csv.c
@@ -234,7 +234,7 @@ static int build_csv_record(char *buf, size_t bufsize, struct ast_cdr *cdr)
 	/* Disposition */
 	append_string(buf, ast_cdr_disp2str(cdr->disposition), bufsize);
 	/* AMA Flags */
-	append_string(buf, ast_cdr_flags2str(cdr->amaflags), bufsize);
+	append_string(buf, ast_channel_amaflags2string(cdr->amaflags), bufsize);
 	/* Unique ID */
 	if (loguniqueid)
 		append_string(buf, cdr->uniqueid, bufsize);
@@ -285,9 +285,6 @@ static int csv_log(struct ast_cdr *cdr)
 	char buf[1024];
 	char csvmaster[PATH_MAX];
 	snprintf(csvmaster, sizeof(csvmaster),"%s/%s/%s", ast_config_AST_LOG_DIR, CSV_LOG_DIR, CSV_MASTER);
-#if 0
-	printf("[CDR] %s ('%s' -> '%s') Dur: %ds Bill: %ds Disp: %s Flags: %s Account: [%s]\n", cdr->channel, cdr->src, cdr->dst, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), cdr->accountcode);
-#endif
 	if (build_csv_record(buf, sizeof(buf), cdr)) {
 		ast_log(LOG_WARNING, "Unable to create CSV record in %d bytes.  CDR not recorded!\n", (int)sizeof(buf));
 		return 0;
diff --git a/cdr/cdr_custom.c b/cdr/cdr_custom.c
index 290e5344da31f3100ef857ff240b57e96ea5ea6a..2a3b1a1dd06c23b2986bc5de6a5df7ccb4754017 100644
--- a/cdr/cdr_custom.c
+++ b/cdr/cdr_custom.c
@@ -67,20 +67,20 @@ AST_THREADSTORAGE(custom_buf);
 
 static const char name[] = "cdr-custom";
 
-struct cdr_config {
+struct cdr_custom_config {
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(filename);
 		AST_STRING_FIELD(format);
 		);
 	ast_mutex_t lock;
-	AST_RWLIST_ENTRY(cdr_config) list;
+	AST_RWLIST_ENTRY(cdr_custom_config) list;
 };
 
-static AST_RWLIST_HEAD_STATIC(sinks, cdr_config);
+static AST_RWLIST_HEAD_STATIC(sinks, cdr_custom_config);
 
 static void free_config(void)
 {
-	struct cdr_config *sink;
+	struct cdr_custom_config *sink;
 	while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) {
 		ast_mutex_destroy(&sink->lock);
 		ast_free(sink);
@@ -103,7 +103,7 @@ static int load_config(void)
 	var = ast_variable_browse(cfg, "mappings");
 	while (var) {
 		if (!ast_strlen_zero(var->name) && !ast_strlen_zero(var->value)) {
-			struct cdr_config *sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
+			struct cdr_custom_config *sink = ast_calloc_with_stringfields(1, struct cdr_custom_config, 1024);
 
 			if (!sink) {
 				ast_log(LOG_ERROR, "Unable to allocate memory for configuration settings.\n");
@@ -130,7 +130,7 @@ static int custom_log(struct ast_cdr *cdr)
 {
 	struct ast_channel *dummy;
 	struct ast_str *str;
-	struct cdr_config *config;
+	struct cdr_custom_config *config;
 
 	/* Batching saves memory management here.  Otherwise, it's the same as doing an allocation and free each time. */
 	if (!(str = ast_str_thread_get(&custom_buf, 16))) {
diff --git a/cdr/cdr_manager.c b/cdr/cdr_manager.c
index a82bcf9895497a682973bf5b6003345dbe55bc57..e3ae7a57d215da46cce019925a4feaf294eb4193 100644
--- a/cdr/cdr_manager.c
+++ b/cdr/cdr_manager.c
@@ -203,7 +203,7 @@ static int manager_log(struct ast_cdr *cdr)
 	    cdr->accountcode, cdr->src, cdr->dst, cdr->dcontext, cdr->clid, cdr->channel,
 	    cdr->dstchannel, cdr->lastapp, cdr->lastdata, strStartTime, strAnswerTime, strEndTime,
 	    cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition),
-	    ast_cdr_flags2str(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf);
+	    ast_channel_amaflags2string(cdr->amaflags), cdr->uniqueid, cdr->userfield,buf);
 
 	return 0;
 }
diff --git a/cdr/cdr_odbc.c b/cdr/cdr_odbc.c
index 7ea2f041febe1b6a7b4e09aab8124a8964684998..022d75210e5bbe59712d0a85e227cff936e012cf 100644
--- a/cdr/cdr_odbc.c
+++ b/cdr/cdr_odbc.c
@@ -124,10 +124,13 @@ static SQLHSTMT execute_cb(struct odbc_obj *obj, void *data)
 		SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL);
 	}
 
-	if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING))
-		SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL);
-	else
+	if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING)) {
+		char *disposition;
+		disposition = ast_strdupa(ast_cdr_disp2str(cdr->disposition));
+		SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(disposition) + 1, 0, disposition, 0, NULL);
+	} else {
 		SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->disposition, 0, NULL);
+	}
 	SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->amaflags, 0, NULL);
 	SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->accountcode), 0, cdr->accountcode, 0, NULL);
 
diff --git a/cdr/cdr_pgsql.c b/cdr/cdr_pgsql.c
index 906f0227c7094d97f916c683bab8de903ebc90fc..dc73de4779385b069a569a19a490a83bc7282bba 100644
--- a/cdr/cdr_pgsql.c
+++ b/cdr/cdr_pgsql.c
@@ -222,9 +222,9 @@ static int pgsql_log(struct ast_cdr *cdr)
 		AST_RWLIST_RDLOCK(&psql_columns);
 		AST_RWLIST_TRAVERSE(&psql_columns, cur, list) {
 			/* For fields not set, simply skip them */
-			ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+			ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
 			if (strcmp(cur->name, "calldate") == 0 && !value) {
-				ast_cdr_getvar(cdr, "start", &value, buf, sizeof(buf), 0, 0);
+				ast_cdr_format_var(cdr, "start", &value, buf, sizeof(buf), 0);
 			}
 			if (!value) {
 				if (cur->notnull && !cur->hasdefault) {
@@ -286,7 +286,7 @@ static int pgsql_log(struct ast_cdr *cdr)
 			} else if (strcmp(cur->name, "duration") == 0 || strcmp(cur->name, "billsec") == 0) {
 				if (cur->type[0] == 'i') {
 					/* Get integer, no need to escape anything */
-					ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+					ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
 					LENGTHEN_BUF2(13);
 					ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
 				} else if (strncmp(cur->type, "float", 5) == 0) {
@@ -302,18 +302,18 @@ static int pgsql_log(struct ast_cdr *cdr)
 			} else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) {
 				if (strncmp(cur->type, "int", 3) == 0) {
 					/* Integer, no need to escape anything */
-					ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 1);
+					ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 1);
 					LENGTHEN_BUF2(13);
 					ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", value);
 				} else {
 					/* Although this is a char field, there are no special characters in the values for these fields */
-					ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+					ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
 					LENGTHEN_BUF2(31);
 					ast_str_append(&sql2, 0, "%s'%s'", first ? "" : ",", value);
 				}
 			} else {
 				/* Arbitrary field, could be anything */
-				ast_cdr_getvar(cdr, cur->name, &value, buf, sizeof(buf), 0, 0);
+				ast_cdr_format_var(cdr, cur->name, &value, buf, sizeof(buf), 0);
 				if (strncmp(cur->type, "int", 3) == 0) {
 					long long whatever;
 					if (value && sscanf(value, "%30lld", &whatever) == 1) {
diff --git a/cdr/cdr_radius.c b/cdr/cdr_radius.c
index 92ec8a4b48dc83b61709fb6e79861ab0ddfcaae1..2bf2002feb52d9f42dc74e5b253c1063361b8f25 100644
--- a/cdr/cdr_radius.c
+++ b/cdr/cdr_radius.c
@@ -170,12 +170,12 @@ static int build_radius_record(VALUE_PAIR **tosend, struct ast_cdr *cdr)
 		return -1;
 
 	/* Disposition */
-	tmp = ast_cdr_disp2str(cdr->disposition);
+	tmp = ast_strdupa(ast_cdr_disp2str(cdr->disposition));
 	if (!rc_avpair_add(rh, tosend, PW_AST_DISPOSITION, tmp, strlen(tmp), VENDOR_CODE))
 		return -1;
 
 	/* AMA Flags */
-	tmp = ast_cdr_flags2str(cdr->amaflags);
+	tmp = ast_strdupa(ast_channel_amaflags2string(cdr->amaflags));
 	if (!rc_avpair_add(rh, tosend, PW_AST_AMA_FLAGS, tmp, strlen(tmp), VENDOR_CODE))
 		return -1;
 
diff --git a/cdr/cdr_syslog.c b/cdr/cdr_syslog.c
index 8a7f07713a4e0eb300ae634a2a4803b4748d76a3..dec4d65e9d3225e560b89c6e0d1d3cb84097a965 100644
--- a/cdr/cdr_syslog.c
+++ b/cdr/cdr_syslog.c
@@ -60,7 +60,7 @@ AST_THREADSTORAGE(syslog_buf);
 
 static const char name[] = "cdr-syslog";
 
-struct cdr_config {
+struct cdr_syslog_config {
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(ident);
 		AST_STRING_FIELD(format);
@@ -68,14 +68,14 @@ struct cdr_config {
 	int facility;
 	int priority;
 	ast_mutex_t lock;
-	AST_LIST_ENTRY(cdr_config) list;
+	AST_LIST_ENTRY(cdr_syslog_config) list;
 };
 
-static AST_RWLIST_HEAD_STATIC(sinks, cdr_config);
+static AST_RWLIST_HEAD_STATIC(sinks, cdr_syslog_config);
 
 static void free_config(void)
 {
-	struct cdr_config *sink;
+	struct cdr_syslog_config *sink;
 	while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) {
 		ast_mutex_destroy(&sink->lock);
 		ast_free(sink);
@@ -86,7 +86,7 @@ static int syslog_log(struct ast_cdr *cdr)
 {
 	struct ast_channel *dummy;
 	struct ast_str *str;
-	struct cdr_config *sink;
+	struct cdr_syslog_config *sink;
 
 	/* Batching saves memory management here.  Otherwise, it's the same as doing an
 	   allocation and free each time. */
@@ -174,7 +174,7 @@ static int load_config(int reload)
 	}
 
 	while ((catg = ast_category_browse(cfg, catg))) {
-		struct cdr_config *sink;
+		struct cdr_syslog_config *sink;
 
 		if (!strcasecmp(catg, "general")) {
 			continue;
@@ -186,7 +186,7 @@ static int load_config(int reload)
 			continue;
 		}
 
-		sink = ast_calloc_with_stringfields(1, struct cdr_config, 1024);
+		sink = ast_calloc_with_stringfields(1, struct cdr_syslog_config, 1024);
 
 		if (!sink) {
 			ast_log(AST_LOG_ERROR,
diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c
index dd75dbb464741f4da1ffa3f3ae9c35eced32f765..aef57b55d15aa19d15632550fb6aebe2f4f59acd 100644
--- a/cdr/cdr_tds.c
+++ b/cdr/cdr_tds.c
@@ -176,7 +176,7 @@ retry:
 					 settings->table,
 					 accountcode, src, dst, dcontext, clid, channel,
 					 dstchannel, lastapp, lastdata, start, answer, end, hrduration,
-					 hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
+					 hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
 					 userfield
 			);
 		} else {
@@ -196,7 +196,7 @@ retry:
 					 settings->table,
 					 accountcode, src, dst, dcontext, clid, channel,
 					 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
-					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid,
+					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
 					 userfield
 			);
 		}
@@ -226,7 +226,7 @@ retry:
 					 settings->table,
 					 accountcode, src, dst, dcontext, clid, channel,
 					 dstchannel, lastapp, lastdata, start, answer, end, hrduration,
-					 hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
+					 hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
 			);
 		} else {
 			erc = dbfcmd(settings->dbproc,
@@ -245,7 +245,7 @@ retry:
 					 settings->table,
 					 accountcode, src, dst, dcontext, clid, channel,
 					 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
-					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid
+					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
 			);
 		}
 	}
diff --git a/cel/cel_manager.c b/cel/cel_manager.c
index e1d0dc148c03102c421484f5a18f6d70a0e2f929..245c7800d463e1a3b1e2e793857b429c7a2f47b2 100644
--- a/cel/cel_manager.c
+++ b/cel/cel_manager.c
@@ -129,7 +129,7 @@ static void manager_log(const struct ast_event *event, void *userdata)
 		record.application_name,
 		record.application_data,
 		start_time,
-		ast_cel_get_ama_flag_name(record.amaflag),
+		ast_channel_amaflags2string(record.amaflag),
 		record.unique_id,
 		record.linked_id,
 		record.user_field,
diff --git a/cel/cel_radius.c b/cel/cel_radius.c
index 0edf57f4ce97c6236e6250a9e629d5dcba19b5af..9067a049188d68cb60e2fc6dede07433bbbf21af 100644
--- a/cel/cel_radius.c
+++ b/cel/cel_radius.c
@@ -150,7 +150,7 @@ static int build_radius_record(VALUE_PAIR **send, struct ast_cel_event_record *r
 		return -1;
 	}
 	/* AMA Flags */
-	amaflags = ast_strdupa(ast_cel_get_ama_flag_name(record->amaflag));
+	amaflags = ast_strdupa(ast_channel_amaflags2string(record->amaflag));
 	if (!rc_avpair_add(rh, send, PW_AST_AMA_FLAGS, amaflags, strlen(amaflags), VENDOR_CODE)) {
 		return -1;
 	}
diff --git a/cel/cel_tds.c b/cel/cel_tds.c
index df2b573bf252c7cd61ab10b3b158581bc0e91981..1bb4d517e65b6f4597d413bac957ddf707980bc7 100644
--- a/cel/cel_tds.c
+++ b/cel/cel_tds.c
@@ -206,7 +206,7 @@ retry:
 		ciddnid_ai, exten_ai, context_ai, channel_ai, app_ai, appdata_ai, start,
 		(record.event_type == AST_CEL_USER_DEFINED)
 			? record.user_defined_name : record.event_name,
-		ast_cel_get_ama_flag_name(record.amaflag), uniqueid_ai, linkedid_ai,
+					ast_channel_amaflags2string(record.amaflag), uniqueid_ai, linkedid_ai,
 		userfield_ai, peer_ai);
 
 	if (erc == FAIL) {
diff --git a/channels/chan_agent.c b/channels/chan_agent.c
index d72254ee75d914504ef411d4345caf985ba12898..57f0914cfd1d4cf693e166934f875c3f0109d925 100644
--- a/channels/chan_agent.c
+++ b/channels/chan_agent.c
@@ -112,10 +112,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 					<option name="d">
 						<para>make the app return <literal>-1</literal> if there is an error condition.</para>
 					</option>
-					<option name="c">
-						<para>change the CDR so that the source of the call is
-						<literal>Agent/agent_id</literal></para>
-					</option>
 					<option name="n">
 						<para>don't generate the warnings when there is no callerid or the
 						agentid is not known. It's handy if you want to have one context
@@ -234,7 +230,6 @@ static char recordformat[AST_MAX_BUF] = "";
 static char recordformatext[AST_MAX_BUF] = "";
 static char urlprefix[AST_MAX_BUF] = "";
 static char savecallsin[AST_MAX_BUF] = "";
-static int updatecdr = 0;
 static char beep[AST_MAX_BUF] = "beep";
 
 #define GETAGENTBYCALLERID	"AGENTBYCALLERID"
@@ -573,7 +568,7 @@ static int agent_answer(struct ast_channel *ast)
 
 static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p, int needlock)
 {
-	char tmp[AST_MAX_BUF],tmp2[AST_MAX_BUF], *pointer;
+	char tmp[AST_MAX_BUF], tmp2[AST_MAX_BUF], *pointer;
 	char filename[AST_MAX_BUF];
 	int res = -1;
 	if (!p)
@@ -590,9 +585,7 @@ static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p
 #if 0
 		ast_verbose("name is %s, link is %s\n",tmp, tmp2);
 #endif
-		if (!ast_channel_cdr(ast))
-			ast_channel_cdr_set(ast, ast_cdr_alloc());
-		ast_cdr_setuserfield(ast, tmp2);
+		ast_cdr_setuserfield(ast_channel_name(ast), tmp2);
 		res = 0;
 	} else
 		ast_log(LOG_ERROR, "Recording already started on that call.\n");
@@ -1199,11 +1192,6 @@ static int read_agent_config(int reload)
 			strcpy(agentgoodbye,v->value);
 		} else if (!strcasecmp(v->name, "musiconhold")) {
 			ast_copy_string(moh, v->value, sizeof(moh));
-		} else if (!strcasecmp(v->name, "updatecdr")) {
-			if (ast_true(v->value))
-				updatecdr = 1;
-			else
-				updatecdr = 0;
 		} else if (!strcasecmp(v->name, "autologoffunavail")) {
 			if (ast_true(v->value))
 				autologoffunavail = 1;
@@ -1898,7 +1886,6 @@ static int login_exec(struct ast_channel *chan, const char *data)
 	const char *tmpoptions = NULL;
 	int play_announcement = 1;
 	char agent_goodbye[AST_MAX_FILENAME_LEN];
-	int update_cdr = updatecdr;
 
 	user[0] = '\0';
 	xpass[0] = '\0';
@@ -1918,14 +1905,6 @@ static int login_exec(struct ast_channel *chan, const char *data)
 		tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTMAXLOGINTRIES");
 		ast_verb(3, "Saw variable AGENTMAXLOGINTRIES=%s, setting max_login_tries to: %d on Channel '%s'.\n",tmpoptions,max_login_tries,ast_channel_name(chan));
 	}
-	if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR"))) {
-		if (ast_true(pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR")))
-			update_cdr = 1;
-		else
-			update_cdr = 0;
-		tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTUPDATECDR");
-		ast_verb(3, "Saw variable AGENTUPDATECDR=%s, setting update_cdr to: %d on Channel '%s'.\n",tmpoptions,update_cdr,ast_channel_name(chan));
-	}
 	if (!ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"))) {
 		strcpy(agent_goodbye, pbx_builtin_getvar_helper(chan, "AGENTGOODBYE"));
 		tmpoptions=pbx_builtin_getvar_helper(chan, "AGENTGOODBYE");
@@ -2093,8 +2072,6 @@ static int login_exec(struct ast_channel *chan, const char *data)
 							      "Channel: %s\r\n"
 							      "Uniqueid: %s\r\n",
 							      p->agent, ast_channel_name(chan), ast_channel_uniqueid(chan));
-						if (update_cdr && ast_channel_cdr(chan))
-							snprintf(ast_channel_cdr(chan)->channel, sizeof(ast_channel_cdr(chan)->channel), "%s", agent);
 						ast_queue_log("NONE", ast_channel_uniqueid(chan), agent, "AGENTLOGIN", "%s", ast_channel_name(chan));
 						ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", p->agent,
 								    ast_getformatname(ast_channel_readformat(chan)), ast_getformatname(ast_channel_writeformat(chan)));
@@ -2242,17 +2219,16 @@ static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data)
 {
 	int exitifnoagentid = 0;
 	int nowarnings = 0;
-	int changeoutgoing = 0;
 	int res = 0;
 	char agent[AST_MAX_AGENT];
 
 	if (data) {
-		if (strchr(data, 'd'))
+		if (strchr(data, 'd')) {
 			exitifnoagentid = 1;
-		if (strchr(data, 'n'))
+		}
+		if (strchr(data, 'n')) {
 			nowarnings = 1;
-		if (strchr(data, 'c'))
-			changeoutgoing = 1;
+		}
 	}
 	if (ast_channel_caller(chan)->id.number.valid
 		&& !ast_strlen_zero(ast_channel_caller(chan)->id.number.str)) {
@@ -2266,7 +2242,6 @@ static int agentmonitoroutgoing_exec(struct ast_channel *chan, const char *data)
 			AST_LIST_LOCK(&agents);
 			AST_LIST_TRAVERSE(&agents, p, list) {
 				if (!strcasecmp(p->agent, tmp)) {
-					if (changeoutgoing) snprintf(ast_channel_cdr(chan)->channel, sizeof(ast_channel_cdr(chan)->channel), "Agent/%s", p->agent);
 					__agent_start_monitoring(chan, p, 1);
 					break;
 				}
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 109fac06d36f772aebf202a9cb53c4675fb921b5..24337e385747a1aea941d7b67e992ef4fde05f6e 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -107,7 +107,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/callerid.h"
 #include "asterisk/adsi.h"
 #include "asterisk/cli.h"
-#include "asterisk/cdr.h"
 #include "asterisk/cel.h"
 #include "asterisk/features.h"
 #include "asterisk/musiconhold.h"
@@ -17726,7 +17725,7 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
 		} else if (!strcasecmp(v->name, "accountcode")) {
 			ast_copy_string(confp->chan.accountcode, v->value, sizeof(confp->chan.accountcode));
 		} else if (!strcasecmp(v->name, "amaflags")) {
-			y = ast_cdr_amaflags2int(v->value);
+			y = ast_channel_string2amaflag(v->value);
 			if (y < 0)
 				ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d.\n", v->value, v->lineno);
 			else
diff --git a/channels/chan_h323.c b/channels/chan_h323.c
index 61817dbff9c78ef78349898020e126327d7bc84f..e26bb5fb643715c62421c570e9a0a301f2632086 100644
--- a/channels/chan_h323.c
+++ b/channels/chan_h323.c
@@ -1482,7 +1482,7 @@ static struct oh323_user *build_user(const char *name, struct ast_variable *v, s
 			/* Let us know we need to use ip authentication */
 			user->host = 1;
 		} else if (!strcasecmp(v->name, "amaflags")) {
-			format = ast_cdr_amaflags2int(v->value);
+			format = ast_channel_string2amaflag(v->value);
 			if (format < 0) {
 				ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
 			} else {
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 7c0de993633b5dbcbe6575c8eaf026c5a4ccf2c0..486af52a77639fb00a47576ce41b4f08c762d027 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -77,7 +77,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/translate.h"
 #include "asterisk/md5.h"
-#include "asterisk/cdr.h"
 #include "asterisk/crypto.h"
 #include "asterisk/acl.h"
 #include "asterisk/manager.h"
@@ -12802,7 +12801,7 @@ static struct iax2_user *build_user(const char *name, struct ast_variable *v, st
 			} else if (!strcasecmp(v->name, "language")) {
 				ast_string_field_set(user, language, v->value);
 			} else if (!strcasecmp(v->name, "amaflags")) {
-				format = ast_cdr_amaflags2int(v->value);
+				format = ast_channel_string2amaflag(v->value);
 				if (format < 0) {
 					ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
 				} else {
@@ -13273,7 +13272,7 @@ static int set_config(const char *config_file, int reload, int forced)
 		} else if (!strcasecmp(v->name, "mohsuggest")) {
 			ast_copy_string(mohsuggest, v->value, sizeof(mohsuggest));
 		} else if (!strcasecmp(v->name, "amaflags")) {
-			format = ast_cdr_amaflags2int(v->value);
+			format = ast_channel_string2amaflag(v->value);
 			if (format < 0) {
 				ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
 			} else {
@@ -14513,7 +14512,7 @@ static int users_data_provider_get(const struct ast_data_search *search,
 			continue;
 		}
 		ast_data_add_int(data_enum_node, "value", user->amaflags);
-		ast_data_add_str(data_enum_node, "text", ast_cdr_flags2str(user->amaflags));
+		ast_data_add_str(data_enum_node, "text", ast_channel_amaflags2string(user->amaflags));
 
 		ast_data_add_bool(data_user, "access-control", ast_acl_list_is_empty(user->acl) ? 0 : 1);
 
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index 9080dbaf0380741fd371ab52195b2a0997eed7b4..5a0b7ad84c80f0a65da58e2996e598bdeda9ec25 100644
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -67,7 +67,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/callerid.h"
 #include "asterisk/cli.h"
 #include "asterisk/say.h"
-#include "asterisk/cdr.h"
 #include "asterisk/astdb.h"
 #include "asterisk/features.h"
 #include "asterisk/app.h"
@@ -4087,7 +4086,7 @@ static struct mgcp_gateway *build_gateway(char *cat, struct ast_variable *v)
 		} else if (!strcasecmp(v->name, "accountcode")) {
 			ast_copy_string(accountcode, v->value, sizeof(accountcode));
 		} else if (!strcasecmp(v->name, "amaflags")) {
-			y = ast_cdr_amaflags2int(v->value);
+			y = ast_channel_string2amaflag(v->value);
 			if (y < 0) {
 				ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
 			} else {
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index eb79d237ef8c9dd2be84e7f317398189e629d41b..fbd7f1c221326290eb37a47bb6d0405f00cb1d52 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -20224,7 +20224,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
 		ast_cli(fd, "  Tonezone     : %s\n", peer->zone[0] != '\0' ? peer->zone : "<Not set>");
 		if (!ast_strlen_zero(peer->accountcode))
 			ast_cli(fd, "  Accountcode  : %s\n", peer->accountcode);
-		ast_cli(fd, "  AMA flags    : %s\n", ast_cdr_flags2str(peer->amaflags));
+		ast_cli(fd, "  AMA flags    : %s\n", ast_channel_amaflags2string(peer->amaflags));
 		ast_cli(fd, "  Transfer mode: %s\n", transfermode2str(peer->allowtransfer));
 		ast_cli(fd, "  CallingPres  : %s\n", ast_describe_caller_presentation(peer->callingpres));
 		if (!ast_strlen_zero(peer->fromuser))
@@ -20362,7 +20362,7 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct
 		astman_append(s, "ToneZone: %s\r\n", peer->zone[0] != '\0' ? peer->zone : "<Not set>");
 		if (!ast_strlen_zero(peer->accountcode))
 			astman_append(s, "Accountcode: %s\r\n", peer->accountcode);
-		astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(peer->amaflags));
+		astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(peer->amaflags));
 		astman_append(s, "CID-CallingPres: %s\r\n", ast_describe_caller_presentation(peer->callingpres));
 		if (!ast_strlen_zero(peer->fromuser))
 			astman_append(s, "SIP-FromUser: %s\r\n", peer->fromuser);
@@ -20537,7 +20537,7 @@ static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 		ast_cli(a->fd, "  Language     : %s\n", user->language);
 		if (!ast_strlen_zero(user->accountcode))
 			ast_cli(a->fd, "  Accountcode  : %s\n", user->accountcode);
-		ast_cli(a->fd, "  AMA flags    : %s\n", ast_cdr_flags2str(user->amaflags));
+		ast_cli(a->fd, "  AMA flags    : %s\n", ast_channel_amaflags2string(user->amaflags));
 		ast_cli(a->fd, "  Tonezone     : %s\n", user->zone[0] != '\0' ? user->zone : "<Not set>");
 		ast_cli(a->fd, "  Transfer mode: %s\n", transfermode2str(user->allowtransfer));
 		ast_cli(a->fd, "  MaxCallBR    : %d kbps\n", user->maxcallbitrate);
@@ -20724,8 +20724,6 @@ static int show_chanstats_cb(void *__cur, void *__arg, int flags)
 	struct sip_pvt *cur = __cur;
 	struct ast_rtp_instance_stats stats;
 	char durbuf[10];
-	int duration;
-	int durh, durm, durs;
 	struct ast_channel *c;
 	struct __show_chan_arg *arg = __arg;
 	int fd = arg->fd;
@@ -20756,12 +20754,8 @@ static int show_chanstats_cb(void *__cur, void *__arg, int flags)
 		return 0;
 	}
 
-	if (c && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
-		duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000);
-		durh = duration / 3600;
-		durm = (duration % 3600) / 60;
-		durs = duration % 60;
-		snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
+	if (c) {
+		ast_format_duration_hh_mm_ss(ast_channel_get_duration(c), durbuf, sizeof(durbuf));
 	} else {
 		durbuf[0] = '\0';
 	}
@@ -21694,11 +21688,8 @@ static void handle_request_info(struct sip_pvt *p, struct sip_request *req)
 	} else if (!ast_strlen_zero(c = sip_get_header(req, "X-ClientCode"))) {
 		/* Client code (from SNOM phone) */
 		if (ast_test_flag(&p->flags[0], SIP_USECLIENTCODE)) {
-			if (p->owner && ast_channel_cdr(p->owner)) {
-				ast_cdr_setuserfield(p->owner, c);
-			}
-			if (p->owner && ast_bridged_channel(p->owner) && ast_channel_cdr(ast_bridged_channel(p->owner))) {
-				ast_cdr_setuserfield(ast_bridged_channel(p->owner), c);
+			if (p->owner) {
+				ast_cdr_setuserfield(ast_channel_name(p->owner), c);
 			}
 			transmit_response(p, "200 OK", req);
 		} else {
@@ -24831,7 +24822,7 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req,
 	ast_channel_unlock(c);
 	sip_pvt_unlock(p);
 
-	ast_raw_answer(c, 1);
+	ast_raw_answer(c);
 
 	ast_channel_lock(replaces_chan);
 	bridge = ast_channel_get_bridge(replaces_chan);
@@ -30458,7 +30449,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str
 			} else if (!strcasecmp(v->name, "callbackextension")) {
 				ast_string_field_set(peer, callback, v->value);
 			} else if (!strcasecmp(v->name, "amaflags")) {
-				format = ast_cdr_amaflags2int(v->value);
+				format = ast_channel_string2amaflag(v->value);
 				if (format < 0) {
 					ast_log(LOG_WARNING, "Invalid AMA Flags for peer: %s at line %d\n", v->value, v->lineno);
 				} else {
@@ -34023,7 +34014,7 @@ static int peers_data_provider_get(const struct ast_data_search *search,
 			continue;
 		}
 		ast_data_add_int(enum_node, "value", peer->amaflags);
-		ast_data_add_str(enum_node, "text", ast_cdr_flags2str(peer->amaflags));
+		ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(peer->amaflags));
 
 		/* sip options */
 		data_sip_options = ast_data_add_node(data_peer, "sipoptions");
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index 5e0756654273a29e20c5abd0f2435d1b453e68bf..ad51edf10279efb2b4a5edfe80d8f5ae8703592c 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -67,7 +67,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/manager.h"
 #include "asterisk/say.h"
-#include "asterisk/cdr.h"
 #include "asterisk/astdb.h"
 #include "asterisk/features.h"
 #include "asterisk/app.h"
@@ -4477,7 +4476,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str
 				ast_str_reset(tmp_str);
 				ast_cli(fd, "Language:         %s\n", S_OR(l->language, "<not set>"));
 				ast_cli(fd, "Accountcode:      %s\n", S_OR(l->accountcode, "<not set>"));
-				ast_cli(fd, "AmaFlag:          %s\n", ast_cdr_flags2str(l->amaflags));
+				ast_cli(fd, "AmaFlag:          %s\n", ast_channel_amaflags2string(l->amaflags));
 				ast_cli(fd, "CallerId Number:  %s\n", S_OR(l->cid_num, "<not set>"));
 				ast_cli(fd, "CallerId Name:    %s\n", S_OR(l->cid_name, "<not set>"));
 				ast_cli(fd, "Hide CallerId:    %s\n", (l->hidecallerid ? "Yes" : "No"));
@@ -4535,7 +4534,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str
 				ast_str_reset(tmp_str);
 				astman_append(s, "Language: %s\r\n", S_OR(l->language, "<not set>"));
 				astman_append(s, "Accountcode: %s\r\n", S_OR(l->accountcode, "<not set>"));
-				astman_append(s, "AMAflags: %s\r\n", ast_cdr_flags2str(l->amaflags));
+				astman_append(s, "AMAflags: %s\r\n", ast_channel_amaflags2string(l->amaflags));
 				astman_append(s, "Callerid: %s\r\n", ast_callerid_merge(cbuf, sizeof(cbuf), l->cid_name, l->cid_num, ""));
 				astman_append(s, "HideCallerId: %s\r\n", (l->hidecallerid ? "Yes" : "No"));
 				astman_append(s, "CFwdAll: %s\r\n", S_COR((l->cfwdtype & SKINNY_CFWD_ALL), l->call_forward_all, "<not set>"));
@@ -7809,7 +7808,7 @@ static void config_parse_variables(int type, void *item, struct ast_variable *vp
 			}
 		} else if (!strcasecmp(v->name, "amaflags")) {
 			if (type & (TYPE_DEF_LINE | TYPE_LINE)) {
-				int tempamaflags = ast_cdr_amaflags2int(v->value);
+				int tempamaflags = ast_channel_string2amaflag(v->value);
 				if (tempamaflags < 0) {
 					ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value, v->lineno);
 				} else {
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index fbc6adda24181ee8181cc2c166af6e035d3ec47a..f512b0f6f7e97c133f5f924521d5832d6f16ed69 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -6392,7 +6392,7 @@ static struct unistim_device *build_device(const char *cat, const struct ast_var
 			ast_copy_string(lt->accountcode, v->value, sizeof(lt->accountcode));
 		} else if (!strcasecmp(v->name, "amaflags")) {
 			int y;
-			y = ast_cdr_amaflags2int(v->value);
+			y = ast_channel_string2amaflag(v->value);
 			if (y < 0) {
 				ast_log(LOG_WARNING, "Invalid AMA flags: %s at line %d\n", v->value,
 						v->lineno);
diff --git a/funcs/func_callerid.c b/funcs/func_callerid.c
index 30026af3d30e890a2c6753fa4ac7d722221e27bb..b4649c17765327464e84c397a9419f5aadf39c3d 100644
--- a/funcs/func_callerid.c
+++ b/funcs/func_callerid.c
@@ -1142,9 +1142,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 		ast_channel_redirecting(chan)->from.number.valid = 1;
 		ast_free(ast_channel_redirecting(chan)->from.number.str);
 		ast_channel_redirecting(chan)->from.number.str = ast_strdup(value);
-		if (ast_channel_cdr(chan)) {
-			ast_cdr_setcid(ast_channel_cdr(chan), chan);
-		}
 	} else if (!strcasecmp("dnid", member.argv[0])) {
 		ast_party_dialed_set_init(&dialed, ast_channel_dialed(chan));
 		if (member.argc == 1) {
@@ -1162,9 +1159,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 				dialed.number.str = ast_strdup(value);
 				ast_trim_blanks(dialed.number.str);
 				ast_party_dialed_set(ast_channel_dialed(chan), &dialed);
-				if (ast_channel_cdr(chan)) {
-					ast_cdr_setcid(ast_channel_cdr(chan), chan);
-				}
 			} else if (member.argc == 3 && !strcasecmp("plan", member.argv[2])) {
 				/* dnid-num-plan */
 				val = ast_strdupa(value);
@@ -1172,9 +1166,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 
 				if (('0' <= val[0]) && (val[0] <= '9')) {
 					ast_channel_dialed(chan)->number.plan = atoi(val);
-					if (ast_channel_cdr(chan)) {
-						ast_cdr_setcid(ast_channel_cdr(chan), chan);
-					}
 				} else {
 					ast_log(LOG_ERROR,
 						"Unknown type-of-number/numbering-plan '%s', value unchanged\n", val);
@@ -1192,9 +1183,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 			switch (status) {
 			case ID_FIELD_VALID:
 				ast_party_dialed_set(ast_channel_dialed(chan), &dialed);
-				if (ast_channel_cdr(chan)) {
-					ast_cdr_setcid(ast_channel_cdr(chan), chan);
-				}
 				break;
 			case ID_FIELD_INVALID:
 				break;
@@ -1212,9 +1200,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 
 		if (('0' <= val[0]) && (val[0] <= '9')) {
 			ast_channel_caller(chan)->ani2 = atoi(val);
-			if (ast_channel_cdr(chan)) {
-				ast_cdr_setcid(ast_channel_cdr(chan), chan);
-			}
 		} else {
 			ast_log(LOG_ERROR, "Unknown callerid ani2 '%s', value unchanged\n", val);
 		}
@@ -1229,9 +1214,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 		switch (status) {
 		case ID_FIELD_VALID:
 			ast_party_caller_set(ast_channel_caller(chan), &caller, NULL);
-			if (ast_channel_cdr(chan)) {
-				ast_cdr_setcid(ast_channel_cdr(chan), chan);
-			}
 			break;
 		case ID_FIELD_INVALID:
 			break;
@@ -1246,9 +1228,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 		switch (status) {
 		case ID_FIELD_VALID:
 			ast_party_caller_set(ast_channel_caller(chan), &caller, NULL);
-			if (ast_channel_cdr(chan)) {
-				ast_cdr_setcid(ast_channel_cdr(chan), chan);
-			}
 			break;
 		case ID_FIELD_INVALID:
 			break;
@@ -1263,9 +1242,6 @@ static int callerid_write(struct ast_channel *chan, const char *cmd, char *data,
 		switch (status) {
 		case ID_FIELD_VALID:
 			ast_channel_set_caller_event(chan, &caller, NULL);
-			if (ast_channel_cdr(chan)) {
-				ast_cdr_setcid(ast_channel_cdr(chan), chan);
-			}
 			break;
 		case ID_FIELD_INVALID:
 			break;
diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c
index adb42742b628fe99db9080c6dd591e8c31119a66..0f900feda8d28284b87a82bb7b520054f034865c 100644
--- a/funcs/func_cdr.c
+++ b/funcs/func_cdr.c
@@ -56,7 +56,27 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 						<para>Last application arguments.</para>
 					</enum>
 					<enum name="disposition">
-						<para>ANSWERED, NO ANSWER, BUSY, FAILED.</para>
+						<para>The final state of the CDR.</para>
+						<enumlist>
+							<enum name="0">
+								<para><literal>NO ANSWER</literal></para>
+							</enum>
+							<enum name="1">
+								<para><literal>NO ANSWER</literal> (NULL record)</para>
+							</enum>
+							<enum name="2">
+								<para><literal>FAILED</literal></para>
+							</enum>
+							<enum name="4">
+								<para><literal>BUSY</literal></para>
+							</enum>
+							<enum name="8">
+								<para><literal>ANSWERED</literal></para>
+							</enum>
+							<enum name="16">
+								<para><literal>CONGESTION</literal></para>
+							</enum>
+						</enumlist>
 					</enum>
 					<enum name="src">
 						<para>Source.</para>
@@ -65,7 +85,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 						<para>Time the call started.</para>
 					</enum>
 					<enum name="amaflags">
-						<para>DOCUMENTATION, BILL, IGNORE, etc.</para>
+						<para>R/W the Automatic Message Accounting (AMA) flags on the channel.
+						When read from a channel, the integer value will always be returned.
+						When written to a channel, both the string format or integer value
+						is accepted.</para>
+						<enumlist>
+							<enum name="1"><para><literal>OMIT</literal></para></enum>
+							<enum name="2"><para><literal>BILLING</literal></para></enum>
+							<enum name="3"><para><literal>DOCUMENTATION</literal></para></enum>
+						</enumlist>
+						<warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
 					</enum>
 					<enum name="dst">
 						<para>Destination.</para>
@@ -75,6 +104,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 					</enum>
 					<enum name="accountcode">
 						<para>The channel's account code.</para>
+						<warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
 					</enum>
 					<enum name="dcontext">
 						<para>Destination context.</para>
@@ -113,16 +143,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 					<option name="f">
 						<para>Returns billsec or duration fields as floating point values.</para>
 					</option>
-					<option name="l">
-						<para>Uses the most recent CDR on a channel with multiple records</para>
-					</option>
-					<option name="r">
-						<para>Searches the entire stack of CDRs on the channel.</para>
-					</option>
-					<option name="s">
-						<para>Skips any CDR's that are marked 'LOCKED' due to forkCDR() calls.
-						(on setting/writing CDR vars only)</para>
-					</option>
 					<option name="u">
 						<para>Retrieves the raw, unprocessed value.</para>
 						<para>For example, 'start', 'answer', and 'end' will be retrieved as epoch
@@ -137,138 +157,132 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<para>All of the CDR field names are read-only, except for <literal>accountcode</literal>,
 			<literal>userfield</literal>, and <literal>amaflags</literal>. You may, however, supply
 			a name not on the above list, and create your own variable, whose value can be changed
-			with this function, and this variable will be stored on the cdr.</para>
-			<note><para>For setting CDR values, the <literal>l</literal> flag does not apply to
-			setting the <literal>accountcode</literal>, <literal>userfield</literal>, or
-			<literal>amaflags</literal>.</para><para>CDRs can only be modified before the bridge
-			between two channels is torn down. For example, CDRs may not be modified after the
-			<literal>Dial</literal> application has returned.</para></note>
-			<para>Raw values for <literal>disposition</literal>:</para>
-			<enumlist>
-				<enum name="0">
-					<para>NO ANSWER</para>
-				</enum>
-				<enum name="1">
-					<para>NO ANSWER (NULL record)</para>
-				</enum>
-				<enum name="2">
-					<para>FAILED</para>
-				</enum>
-				<enum name="4">
-					<para>BUSY</para>
-				</enum>
-				<enum name="8">
-					<para>ANSWERED</para>
-				</enum>
-			</enumlist>
-			<para>Raw values for <literal>amaflags</literal>:</para>
-			<enumlist>
-				<enum name="1">
-					<para>OMIT</para>
-				</enum>
-				<enum name="2">
-					<para>BILLING</para>
-				</enum>
-				<enum name="3">
-					<para>DOCUMENTATION</para>
-				</enum>
-			</enumlist>
+			with this function, and this variable will be stored on the CDR.</para>
+			<note><para>CDRs can only be modified before the bridge between two channels is
+			torn down. For example, CDRs may not be modified after the <literal>Dial</literal>
+			application has returned.</para></note>
 			<para>Example: exten => 1,1,Set(CDR(userfield)=test)</para>
 		</description>
 	</function>
+	<function name="CDR_PROP" language="en_US">
+		<synopsis>
+			Set a property on a channel's CDR.
+		</synopsis>
+		<syntax>
+			<parameter name="name" required="true">
+				<para>The property to set on the CDR.</para>
+				<enumlist>
+					<enum name="party_a">
+						<para>Set this channel as the preferred Party A when
+						channels are associated together.</para>
+						<para>Write-Only</para>
+					</enum>
+					<enum name="disable">
+						<para>Disable CDRs for this channel.</para>
+						<para>Write-Only</para>
+					</enum>
+				</enumlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This function sets a property on a channel's CDR. Properties
+			alter the behavior of how the CDR operates for that channel.</para>
+		</description>
+	</function>
  ***/
 
 enum cdr_option_flags {
-	OPT_RECURSIVE = (1 << 0),
 	OPT_UNPARSED = (1 << 1),
-	OPT_LAST = (1 << 2),
-	OPT_SKIPLOCKED = (1 << 3),
-	OPT_FLOAT = (1 << 4),
+	OPT_FLOAT = (1 << 2),
 };
 
 AST_APP_OPTIONS(cdr_func_options, {
 	AST_APP_OPTION('f', OPT_FLOAT),
-	AST_APP_OPTION('l', OPT_LAST),
-	AST_APP_OPTION('r', OPT_RECURSIVE),
-	AST_APP_OPTION('s', OPT_SKIPLOCKED),
 	AST_APP_OPTION('u', OPT_UNPARSED),
 });
 
 static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
 		    char *buf, size_t len)
 {
-	char *ret = NULL;
+	char format_buf[128];
 	struct ast_flags flags = { 0 };
-	struct ast_cdr *cdr;
+	char tempbuf[128];
+	char *info;
 	AST_DECLARE_APP_ARGS(args,
 			     AST_APP_ARG(variable);
 			     AST_APP_ARG(options);
 	);
 
-	if (ast_strlen_zero(parse) || !chan)
+	if (!chan) {
 		return -1;
+	}
 
-	ast_channel_lock(chan);
-	cdr = ast_channel_cdr(chan);
-	if (!cdr) {
-		ast_channel_unlock(chan);
+	if (ast_strlen_zero(parse)) {
+		ast_log(AST_LOG_WARNING, "FUNC_CDR requires a variable (FUNC_CDR(variable[,option]))\n)");
 		return -1;
 	}
+	info = ast_strdupa(parse);
+	AST_STANDARD_APP_ARGS(args, info);
 
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	if (!ast_strlen_zero(args.options))
+	if (!ast_strlen_zero(args.options)) {
 		ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
+	}
 
-	if (ast_test_flag(&flags, OPT_LAST))
-		while (cdr->next)
-			cdr = cdr->next;
-
-	if (ast_test_flag(&flags, OPT_SKIPLOCKED))
-		while (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED) && cdr->next)
-			cdr = cdr->next;
-
-	if (!strcasecmp("billsec", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) {
-		if (!ast_tvzero(cdr->answer)) {
-			double hrtime;
-
-			if(!ast_tvzero(cdr->end))
-				hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0);
-			else
-				hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->answer) / 1000000.0);
+	if (ast_cdr_getvar(ast_channel_name(chan), args.variable, tempbuf, sizeof(tempbuf))) {
+		return 0;
+	}
 
-			snprintf(buf, len, "%lf", hrtime);
-		} else {
-			snprintf(buf, len, "%lf", 0.0);
+	if (ast_test_flag(&flags, OPT_FLOAT) && (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
+		long ms;
+		double dtime;
+		if (sscanf(tempbuf, "%30ld", &ms) != 1) {
+			ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+					args.variable, tempbuf, ast_channel_name(chan));
+			return 0;
 		}
-		ret = buf;
-	} else if (!strcasecmp("duration", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) {
-			double hrtime;
-
-			if(!ast_tvzero(cdr->end))
-				hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0);
-			else
-				hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->start) / 1000000.0);
-
-			snprintf(buf, len, "%lf", hrtime);
-
-		if (!ast_strlen_zero(buf)) {
-			ret = buf;
+		dtime = (double)(ms / 1000.0);
+		sprintf(tempbuf, "%lf", dtime);
+	} else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
+		if (!strcasecmp("start", args.variable)
+				|| !strcasecmp("end", args.variable)
+				|| !strcasecmp("answer", args.variable)) {
+			struct timeval fmt_time;
+			struct ast_tm tm;
+			if (sscanf(tempbuf, "%ld.%ld", &fmt_time.tv_sec, &fmt_time.tv_usec) != 2) {
+				ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+						args.variable, tempbuf, ast_channel_name(chan));
+				return 0;
+			}
+			ast_localtime(&fmt_time, &tm, NULL);
+			ast_strftime(tempbuf, sizeof(*tempbuf), "%Y-%m-%d %T", &tm);
+		} else if (!strcasecmp("disposition", args.variable)) {
+			int disposition;
+			if (sscanf(tempbuf, "%8d", &disposition) != 1) {
+				ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+						args.variable, tempbuf, ast_channel_name(chan));
+				return 0;
+			}
+			sprintf(format_buf, "%s", ast_cdr_disp2str(disposition));
+			strcpy(tempbuf, format_buf);
+		} else if (!strcasecmp("amaflags", args.variable)) {
+			int amaflags;
+			if (sscanf(tempbuf, "%8d", &amaflags) != 1) {
+				ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
+						args.variable, tempbuf, ast_channel_name(chan));
+				return 0;
+			}
+			sprintf(format_buf, "%s", ast_channel_amaflags2string(amaflags));
+			strcpy(tempbuf, format_buf);
 		}
-	} else {
-		ast_cdr_getvar(cdr, args.variable, &ret, buf, len,
-			       ast_test_flag(&flags, OPT_RECURSIVE),
-				   ast_test_flag(&flags, OPT_UNPARSED));
 	}
 
-	ast_channel_unlock(chan);
-	return ret ? 0 : -1;
+	ast_copy_string(buf, tempbuf, len);
+	return 0;
 }
 
 static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse,
 		     const char *value)
 {
-	struct ast_cdr *cdr;
 	struct ast_flags flags = { 0 };
 	AST_DECLARE_APP_ARGS(args,
 			     AST_APP_ARG(variable);
@@ -278,36 +292,59 @@ static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse,
 	if (ast_strlen_zero(parse) || !value || !chan)
 		return -1;
 
-	ast_channel_lock(chan);
-	cdr = ast_channel_cdr(chan);
-	if (!cdr) {
-		ast_channel_unlock(chan);
-		return -1;
-	}
-
 	AST_STANDARD_APP_ARGS(args, parse);
 
 	if (!ast_strlen_zero(args.options))
 		ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
 
-	if (ast_test_flag(&flags, OPT_LAST))
-		while (cdr->next)
-			cdr = cdr->next;
-
-	if (!strcasecmp(args.variable, "accountcode"))  /* the 'l' flag doesn't apply to setting the accountcode, userfield, or amaflags */
-		ast_cdr_setaccount(chan, value);
-	else if (!strcasecmp(args.variable, "peeraccount"))
-		ast_cdr_setpeeraccount(chan, value);
-	else if (!strcasecmp(args.variable, "userfield"))
-		ast_cdr_setuserfield(chan, value);
-	else if (!strcasecmp(args.variable, "amaflags"))
-		ast_cdr_setamaflags(chan, value);
-	else
-		ast_cdr_setvar(cdr, args.variable, value, ast_test_flag(&flags, OPT_RECURSIVE));
-		/* No need to worry about the u flag, as all fields for which setting
-		 * 'u' would do anything are marked as readonly. */
-
-	ast_channel_unlock(chan);
+	if (!strcasecmp(args.variable, "accountcode")) {
+		ast_log(AST_LOG_WARNING, "Using the CDR function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n");
+		ast_channel_lock(chan);
+		ast_channel_accountcode_set(chan, value);
+		ast_channel_unlock(chan);
+	} else if (!strcasecmp(args.variable, "peeraccount")) {
+		ast_log(AST_LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
+	} else if (!strcasecmp(args.variable, "userfield")) {
+		ast_cdr_setuserfield(ast_channel_name(chan), value);
+	} else if (!strcasecmp(args.variable, "amaflags")) {
+		ast_log(AST_LOG_WARNING, "Using the CDR function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n");
+		if (isdigit(*value)) {
+			int amaflags;
+			sscanf(value, "%30d", &amaflags);
+			ast_channel_lock(chan);
+			ast_channel_amaflags_set(chan, amaflags);
+			ast_channel_unlock(chan);
+		} else {
+			ast_channel_lock(chan);
+			ast_channel_amaflags_set(chan, ast_channel_string2amaflag(value));
+			ast_channel_unlock(chan);
+		}
+	} else {
+		ast_cdr_setvar(ast_channel_name(chan), args.variable, value);
+	}
+
+	return 0;
+}
+
+static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse,
+		     const char *value)
+{
+	enum ast_cdr_options option;
+
+	if (!strcasecmp("party_a", cmd)) {
+		option = AST_CDR_FLAG_PARTY_A;
+	} else if (!strcasecmp("disable", cmd)) {
+		option = AST_CDR_FLAG_DISABLE_ALL;
+	} else {
+		ast_log(AST_LOG_WARNING, "Unknown option %s used with CDR_PROP\n", cmd);
+		return 0;
+	}
+
+	if (ast_true(value)) {
+		ast_cdr_set_property(ast_channel_name(chan), option);
+	} else {
+		ast_cdr_clear_property(ast_channel_name(chan), option);
+	}
 	return 0;
 }
 
@@ -317,14 +354,30 @@ static struct ast_custom_function cdr_function = {
 	.write = cdr_write,
 };
 
+static struct ast_custom_function cdr_prop_function = {
+	.name = "CDR_PROP",
+	.read = NULL,
+	.write = cdr_prop_write,
+};
+
 static int unload_module(void)
 {
-	return ast_custom_function_unregister(&cdr_function);
+	int res = 0;
+
+	res |= ast_custom_function_unregister(&cdr_function);
+	res |= ast_custom_function_unregister(&cdr_prop_function);
+
+	return res;
 }
 
 static int load_module(void)
 {
-	return ast_custom_function_register(&cdr_function);
+	int res = 0;
+
+	res |= ast_custom_function_register(&cdr_function);
+	res |= ast_custom_function_register(&cdr_prop_function);
+
+	return res;
 }
 
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan function");
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan functions");
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 93792440074295aebeb9082f5348d69ecd1a89d5..4327fd257b179bf4124ed901e24458bf9da6bf25 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -343,13 +343,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 	</function>
  ***/
 
-/*
- * BUGBUG add CHANNEL(after_bridge_goto)=<parseable-goto> Sets an after bridge goto datastore property on the channel.
- * CHANNEL(after_bridge_goto)=<empty> Deletes any after bridge goto datastore property on the channel.
- *
- * BUGBUG add CHANNEL(dtmf_features)=tkhwx sets channel dtmf features to specified. (transfer, park, hangup, monitor, mixmonitor)
- */
-
 #define locked_copy_string(chan, dest, source, len) \
 	do { \
 		ast_channel_lock(chan); \
@@ -450,7 +443,7 @@ static int func_channel_read(struct ast_channel *chan, const char *function,
 
 		ast_channel_lock(chan);
 		p = ast_bridged_channel(chan);
-		if (p || ast_channel_tech(chan) || ast_channel_cdr(chan)) /* dummy channel? if so, we hid the peer name in the language */
+		if (p || ast_channel_tech(chan)) /* dummy channel? if so, we hid the peer name in the language */
 			ast_copy_string(buf, (p ? ast_channel_name(p) : ""), len);
 		else {
 			/* a dummy channel can still pass along bridged peer info via
@@ -525,7 +518,7 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio
 		locked_string_field_set(chan, userfield, value);
 	else if (!strcasecmp(data, "amaflags")) {
 		ast_channel_lock(chan);
-		if(isdigit(*value)) {
+		if (isdigit(*value)) {
 			int amaflags;
 			sscanf(value, "%30d", &amaflags);
 			ast_channel_amaflags_set(chan, amaflags);
diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h
index 9d3f5b3cf21fd903393993a08d7f5d1a78720780..0bfcc325495f3d47dc151e05bde1328241f64cfe 100644
--- a/include/asterisk/bridging.h
+++ b/include/asterisk/bridging.h
@@ -1031,6 +1031,37 @@ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast
  */
 int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action);
 
+/*!
+ * \brief Update the linkedid for all channels in a bridge
+ * \since 12.0.0
+ *
+ * \param bridge The bridge to update the linkedids on
+ * \param bridge_channel The channel joining the bridge
+ * \param swap The channel being swapped out of the bridge. May be NULL.
+ *
+ * \note The bridge must be locked prior to calling this function.
+ * \note This API call is meant for internal bridging operations.
+ */
+void ast_bridge_update_linkedids(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap);
+
+/*!
+ * \brief Update the accountcodes for a channel entering a bridge
+ * \since 12.0.0
+ *
+ * This function updates the accountcode and peeraccount on channels in two-party
+ * bridges. In multi-party bridges, peeraccount is not set - it doesn't make much sense -
+ * however accountcode propagation will still occur if the channel joining has an
+ * accountcode.
+ *
+ * \param bridge The bridge to update the accountcodes in
+ * \param bridge_channel The channel joining the bridge
+ * \param swap The channel being swapped out of the bridge. May be NULL.
+ *
+ * \note The bridge must be locked prior to calling this function.
+ * \note This API call is meant for internal bridging operations.
+ */
+void ast_bridge_update_accountcodes(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap);
+
 /*!
  * \brief Write a frame to the specified bridge_channel.
  * \since 12.0.0
diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h
index c2268e312148c959dfdbcb114eb67cdee2ec13fe..548ca994dee702ed9fa5d4182cf79a568d7e3983 100644
--- a/include/asterisk/cdr.h
+++ b/include/asterisk/cdr.h
@@ -26,33 +26,216 @@
 #ifndef _ASTERISK_CDR_H
 #define _ASTERISK_CDR_H
 
-#include <sys/time.h>
+#include "asterisk/channel.h"
+
+/*! \file
+ *
+ * \since 12
+ *
+ * \brief Call Detail Record Engine.
+ *
+ * \page CDR Call Detail Record Engine
+ *
+ * \par Intro
+ *
+ * The Call Detail Record (CDR) engine uses the \ref stasis Stasis Message Bus
+ * to build records for the channels in Asterisk. As the state of a channel and
+ * the bridges it participates in changes, notifications are sent over the
+ * Stasis Message Bus. The CDR engine consumes these notifications and builds
+ * records that reflect that state. Over the lifetime of a channel, many CDRs
+ * may be generated for that channel or that involve that channel.
+ *
+ * CDRs have a lifecycle that is a subset of the channel that they reflect. A
+ * single CDR for a channel represents a path of communication between the
+ * endpoint behind a channel and Asterisk, or between two endpoints. When a
+ * channel establishes a new path of communication, a new CDR is created for the
+ * channel. Likewise, when a path of communication is terminated, a CDR is
+ * finalized. Finally, when a channel is no longer present in Asterisk, all CDRs
+ * for that channel are dispatched for recording.
+ *
+ * Dispatching of CDRs occurs to registered CDR backends. CDR backends register
+ * through \ref ast_cdr_register and are responsible for taking the produced
+ * CDRs and storing them in permanent storage.
+ *
+ * \par CDR attributes
+ *
+ * While a CDR can have many attributes, all CDRs have two parties: a Party A
+ * and a Party B. The Party A is \em always the channel that owns the CDR. A CDR
+ * may or may not have a Party B, depending on its state.
+ *
+ * For the most part, attributes on a CDR are reflective of those same
+ * attributes on the channel at the time when the CDR was finalized. Specific
+ * CDR attributes include:
+ * \li \c start The time when the CDR was created
+ * \li \c answer The time when the Party A was answered, or when the path of
+ * communication between Party A and Party B was established
+ * \li \c end The time when the CDR was finalized
+ * \li \c duration \c end - \c start. If \c end is not known, the current time
+ * is used
+ * \li \c billsec \c end - \c answer. If \c end is not known, the current time
+ * is used
+ * \li \c userfield User set data on some party in the CDR
+ *
+ * Note that \c accountcode and \c amaflags are actually properties of a
+ * channel, not the CDR.
+ *
+ * \par CDR States
+ *
+ * CDRs go through various states during their lifetime. State transitions occur
+ * due to messages received over the \ref stasis Stasis Message Bus. The
+ * following describes the possible states a CDR can be in, and how it
+ * transitions through the states.
+ *
+ * \par Single
+ *
+ * When a CDR is created, it is put into the Single state. The Single state
+ * represents a CDR for a channel that has no Party B. CDRs can be unanswered
+ * or answered while in the Single state.
+ *
+ * The following transitions can occur while in the Single state:
+ * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, the
+ * state transitions to Dial
+ * \li If a \ref ast_channel_snapshot is received indicating that the channel
+ * has hung up, the state transitions to Finalized
+ * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
+ * state transitions to Bridge
+ *
+ * \par Dial
+ *
+ * This state represents a dial that is occurring within Asterisk. The Party A
+ * can either be the caller for a two party dial, or it can be the dialed party
+ * if the calling party is Asterisk (that is, an Originated channel). In the
+ * first case, the Party B is \em always the dialed channel; in the second case,
+ * the channel is not considered to be a "dialed" channel as it is alone in the
+ * dialed operation.
+ *
+ * While in the Dial state, multiple CDRs can be created for the Party A if a
+ * parallel dial occurs. Each dialed party receives its own CDR with Party A.
+ *
+ * The following transitions can occur while in the Dial state:
+ * \li If a \ref ast_channel_dial_type indicating a Dial End is received where
+ * the \ref dial_status is not ANSWER, the state transitions to Finalized
+ * \li If a \ref ast_channel_snapshot is received indicating that the channel
+ * has hung up, the state transitions to Finalized
+ * \li If a \ref ast_channel_dial_type indicating a Dial End is received where
+ * the \ref dial_status is ANSWER, the state transitions to DialedPending
+ * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
+ * state transitions to Bridge
+ *
+ * \par DialedPending
+ *
+ * Technically, after being dialed, a CDR does not have to transition to the
+ * Bridge state. If the channel being dialed was originated, the channel may
+ * being executing dialplan. Strangely enough, it is also valid to have both
+ * Party A and Party B - after a dial - to not be bridged and instead execute
+ * dialplan. DialedPending handles the state where we figure out if the CDR
+ * showing the dial needs to move to the Bridge state; if the CDR should show
+ * that we started executing dialplan; of if we need a new CDR.
+ *
+ * The following transition can occur while in the DialedPending state:
+ * \li If a \ref ast_channel_snapshot is received that indicates that the
+ * channel has begun executing dialplan, we transition to the Finalized state
+ * if we have a Party B. Otherwise, we transition to the Single state.
+ * \li If a \ref ast_bridge_blob_type is received indicating a Bridge Enter, the
+ * state transitions to Bridge (through the Dial state)
+ *
+ * \par Bridge
+ *
+ * The Bridge state represents a path of communication between Party A and one
+ * or more other parties. When a CDR enters into the Bridge state, the following
+ * occurs:
+ * \li The CDR attempts to find a Party B. If the CDR has a Party B, it looks
+ * for that channel in the bridge and updates itself accordingly. If the CDR
+ * does not yet have a Party B, it attempts to find a channel that can be its
+ * Party B. If it finds one, it updates itself; otherwise, the CDR is
+ * temporarily finalized.
+ * \li Once the CDR has a Party B or it is determined that it cannot have a
+ * Party B, new CDRs are created for each pairing of channels with the CDR's
+ * Party A.
+ *
+ * As an example, consider the following:
+ * \li A Dials B - both answer
+ * \li B joins a bridge. Since no one is in the bridge and it was a dialed
+ * channel, it cannot have a Party B.
+ * \li A joins the bridge. Since A's Party B is B, A updates itself with B.
+ * \li Now say an Originated channel, C, joins the bridge. The bridge becomes
+ * a multi-party bridge.
+ * \li C attempts to get a Party B. A cannot be C's Party B, as it was created
+ * before it. B is a dialed channel and can thus be C's Party B, so C's CDR
+ * updates its Party B to B.
+ * \li New CDRs are now generated. A gets a new CDR for A -> C. B is dialed, and
+ * hence cannot get any CDR.
+ * \li Now say another Originated channel, D, joins the bridge. Say D has the
+ * \ref party_a flag set on it, such that it is always the preferred Party A.
+ * As such, it takes A as its Party B.
+ * \li New CDRs are generated. D gets new CDRs for D -> B and D -> C.
+ *
+ * The following transitions can occur while in the Bridge state:
+ * \li If a \ref ast_bridge_blob_type message indicating a leave is received,
+ * the state transitions to the Pending state
+ *
+ * \par Pending
+ *
+ * After a channel leaves a bridge, we often don't know what's going to happen
+ * to it. It can enter another bridge; it can be hung up; it can continue on
+ * in the dialplan. It can even enter into limbo! Pending holds the state of the
+ * CDR until we get a subsequent Stasis message telling us what should happen.
+ *
+ * The following transitions can occur while in the Pending state:
+ * \li If a \ref ast_bridge_blob_type message is received, a new CDR is created
+ * and it is transitioned to the Bridge state
+ * \li If a \ref ast_channel_dial_type indicating a Dial Begin is received, a
+ * new CDR is created and it is transitioned to the Dial state
+ * \li If a \ref ast_channel_cache_update is received indicating a change in
+ * Context/Extension/Priority, a new CDR is created and transitioned to the
+ * Single state. If the update indicates that the party has been hung up, the
+ * CDR is transitioned to the Finalized state.
+ *
+ * \par Finalized
+ *
+ * Once a CDR enters the finalized state, it is finished. No further updates
+ * can be made to the party information, and the CDR cannot be changed.
+ *
+ * One exception to this occurs during linkedid propagation, in which the CDRs
+ * linkedids are updated based on who the channel is bridged with. In general,
+ * however, a finalized CDR is waiting for dispatch to the CDR backends.
+ */
+
+/*! \brief CDR engine settings */
+enum ast_cdr_settings {
+	CDR_ENABLED = 1 << 0,				/*< Enable CDRs */
+	CDR_BATCHMODE = 1 << 1,				/*< Whether or not we should dispatch CDRs in batches */
+	CDR_UNANSWERED = 1 << 2,			/*< Log unanswered CDRs */
+	CDR_CONGESTION = 1 << 3,			/*< Treat congestion as if it were a failed call */
+	CDR_END_BEFORE_H_EXTEN = 1 << 4,	/*< End the CDR before the 'h' extension runs */
+	CDR_INITIATED_SECONDS = 1 << 5,		/*< Include microseconds into the billing time */
+	CDR_DEBUG = 1 << 6,					/*< Enables extra debug statements */
+};
 
-#include "asterisk/data.h"
+/*! \brief CDR Batch Mode settings */
+enum ast_cdr_batch_mode_settings {
+	BATCH_MODE_SCHEDULER_ONLY = 1 << 0,	/*< Don't spawn a thread to handle the batches - do it on the scheduler */
+	BATCH_MODE_SAFE_SHUTDOWN = 1 << 1,	/*< During safe shutdown, submit the batched CDRs */
+};
 
 /*!
- * \brief CDR Flags
+ * \brief CDR manipulation options. Certain function calls will manipulate the
+ * state of a CDR object based on these flags.
  */
-enum {
-	AST_CDR_FLAG_KEEP_VARS     = (1 << 0),
-	AST_CDR_FLAG_POSTED        = (1 << 1),
-	AST_CDR_FLAG_LOCKED        = (1 << 2),
-	AST_CDR_FLAG_CHILD         = (1 << 3),
-	AST_CDR_FLAG_POST_DISABLED = (1 << 4),
-	AST_CDR_FLAG_BRIDGED       = (1 << 5),
-	AST_CDR_FLAG_MAIN          = (1 << 6),
-	AST_CDR_FLAG_ENABLE        = (1 << 7),
-	AST_CDR_FLAG_ANSLOCKED     = (1 << 8),
-	AST_CDR_FLAG_DONT_TOUCH    = (1 << 9),
-	AST_CDR_FLAG_POST_ENABLE   = (1 << 10),
-	AST_CDR_FLAG_DIALED        = (1 << 11),
-	AST_CDR_FLAG_ORIGINATED    = (1 << 12),
+enum ast_cdr_options {
+	AST_CDR_FLAG_KEEP_VARS = (1 << 0),			/*< Copy variables during the operation */
+	AST_CDR_FLAG_DISABLE = (1 << 1),			/*< Disable the current CDR */
+	AST_CDR_FLAG_DISABLE_ALL = (3 << 1),		/*< Disable the CDR and all future CDRs */
+	AST_CDR_FLAG_PARTY_A = (1 << 3),			/*< Set the channel as party A */
+	AST_CDR_FLAG_FINALIZE = (1 << 4),			/*< Finalize the current CDRs */
+	AST_CDR_FLAG_SET_ANSWER = (1 << 5),			/*< If the channel is answered, set the answer time to now */
+	AST_CDR_FLAG_RESET = (1 << 6),				/*< If set, set the start and answer time to now */
 };
 
 /*!
  * \brief CDR Flags - Disposition
  */
-enum {
+enum ast_cdr_disposition {
 	AST_CDR_NOANSWER = 0,
 	AST_CDR_NULL     = (1 << 0),
 	AST_CDR_FAILED   = (1 << 1),
@@ -61,21 +244,16 @@ enum {
 	AST_CDR_CONGESTION = (1 << 4),
 };
 
-/*!
- * \brief CDR AMA Flags
- */
-enum {
-	AST_CDR_OMIT          = 1,
-	AST_CDR_BILLING       = 2,
-	AST_CDR_DOCUMENTATION = 3,
-};
-
-#define AST_MAX_USER_FIELD     256
-#define AST_MAX_ACCOUNT_CODE   20
 
-/* Include channel.h after relevant declarations it will need */
-#include "asterisk/channel.h"
-#include "asterisk/utils.h"
+/*! \brief The global options available for CDRs */
+struct ast_cdr_config {
+	struct ast_flags settings;			/*< CDR settings */
+	struct batch_settings {
+		unsigned int time;				/*< Time between batches */
+		unsigned int size;				/*< Size to trigger a batch */
+		struct ast_flags settings;		/*< Settings for batches */
+	} batch_settings;
+};
 
 /*!
  * \brief Responsible for call detail data
@@ -133,249 +311,186 @@ struct ast_cdr {
 	struct ast_cdr *next;
 };
 
-int ast_cdr_isset_unanswered(void);
-int ast_cdr_isset_congestion(void);
-void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw);
-int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur);
-int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur);
-void ast_cdr_free_vars(struct ast_cdr *cdr, int recur);
-int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr);
-
-/*!
- * \brief CDR backend callback
- * \warning CDR backends should NOT attempt to access the channel associated
- * with a CDR record.  This channel is not guaranteed to exist when the CDR
- * backend is invoked.
- */
-typedef int (*ast_cdrbe)(struct ast_cdr *cdr);
-
-/*! \brief Return TRUE if CDR subsystem is enabled */
-int check_cdr_enabled(void);
-
 /*!
- * \brief Allocate a CDR record
- * \retval a malloc'd ast_cdr structure
- * \retval NULL on error (malloc failure)
- */
-struct ast_cdr *ast_cdr_alloc(void);
-
-/*!
- * \brief Duplicate a record and increment the sequence number.
- * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure,
- * \retval NULL on error (malloc failure)
- * \see ast_cdr_dup()
- * \see ast_cdr_dup_unique_swap()
- */
-struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr);
-
-/*!
- * \brief Duplicate a record and increment the sequence number of the old
- * record.
- * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure,
- * \retval NULL on error (malloc failure)
- * \note This version increments the original CDR's sequence number rather than
- * the duplicate's sequence number. The effect is as if the original CDR's
- * sequence number was swapped with the duplicate's sequence number.
+ * \since 12
+ * \brief Obtain the current CDR configuration
  *
- * \see ast_cdr_dup()
- * \see ast_cdr_dup_unique()
- */
-struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr);
-
-/*!
- * \brief Duplicate a record
- * \param cdr the record to duplicate
- * \retval a malloc'd ast_cdr structure,
- * \retval NULL on error (malloc failure)
- * \see ast_cdr_dup_unique()
- * \see ast_cdr_dup_unique_swap()
- */
-struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr);
-
-/*!
- * \brief Free a CDR record
- * \param cdr ast_cdr structure to free
- * Returns nothing
- */
-void ast_cdr_free(struct ast_cdr *cdr);
-
-/*!
- * \brief Discard and free a CDR record
- * \param cdr ast_cdr structure to free
- * Returns nothing  -- same as free, but no checks or complaints
+ * The configuration is a ref counted object. The caller of this function must
+ * decrement the ref count when finished with the configuration.
+ *
+ * \retval NULL on error
+ * \retval The current CDR configuration
  */
-void ast_cdr_discard(struct ast_cdr *cdr);
+struct ast_cdr_config *ast_cdr_get_config(void);
 
 /*!
- * \brief Initialize based on a channel
- * \param cdr Call Detail Record to use for channel
- * \param chan Channel to bind CDR with
- * Initializes a CDR and associates it with a particular channel
- * \note The channel should be locked before calling.
- * \return 0 by default
+ * \since 12
+ * \brief Set the current CDR configuration
+ *
+ * \param config The new CDR configuration
  */
-int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *chan);
+void ast_cdr_set_config(struct ast_cdr_config *config);
 
 /*!
- * \brief Initialize based on a channel
- * \param cdr Call Detail Record to use for channel
- * \param chan Channel to bind CDR with
- * Initializes a CDR and associates it with a particular channel
- * \note The channel should be locked before calling.
- * \return 0 by default
+ * \since 12
+ * \brief Format a CDR variable from an already posted CDR
+ *
+ * \param cdr The dispatched CDR to process
+ * \param name The name of the variable
+ * \param ret Pointer to the formatted buffer
+ * \param workspace A pointer to the buffer to use to format the variable
+ * \param workspacelen The size of \ref workspace
+ * \param raw If non-zero and a date/time is extraced, provide epoch seconds. Otherwise format as a date/time stamp
  */
-int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *chan);
+void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw);
 
 /*!
- * \brief Register a CDR handling engine
- * \param name name associated with the particular CDR handler
- * \param desc description of the CDR handler
- * \param be function pointer to a CDR handler
- * Used to register a Call Detail Record handler.
- * \retval 0 on success.
- * \retval -1 on error
+ * \since 12
+ * \brief Retrieve a CDR variable from a channel's current CDR
+ *
+ * \param channel_name The name of the party A channel that the CDR is associated with
+ * \param name The name of the variable to retrieve
+ * \param value Buffer to hold the value
+ * \param length The size of the buffer
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
  */
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be);
+int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length);
 
 /*!
- * \brief Unregister a CDR handling engine
- * \param name name of CDR handler to unregister
- * Unregisters a CDR by it's name
+ * \since 12
+ * \brief Set a variable on a CDR
+ *
+ * \param channel_name The channel to set the variable on
+ * \param name The name of the variable to set
+ * \param value The value of the variable to set
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
  */
-void ast_cdr_unregister(const char *name);
+int ast_cdr_setvar(const char *channel_name, const char *name, const char *value);
 
 /*!
- * \brief Start a call
- * \param cdr the cdr you wish to associate with the call
- * Starts all CDR stuff necessary for monitoring a call
- * Returns nothing
- */
-void ast_cdr_start(struct ast_cdr *cdr);
-
-/*! \brief Answer a call
- * \param cdr the cdr you wish to associate with the call
- * Starts all CDR stuff necessary for doing CDR when answering a call
- * \note NULL argument is just fine.
+ * \since 12
+ * \brief Fork a CDR
+ *
+ * \param channel_name The name of the channel whose CDR should be forked
+ * \param options Options to control how the fork occurs.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
  */
-void ast_cdr_answer(struct ast_cdr *cdr);
+int ast_cdr_fork(const char *channel_name, struct ast_flags *options);
 
 /*!
- * \brief A call wasn't answered
- * \param cdr the cdr you wish to associate with the call
- * Marks the channel disposition as "NO ANSWER"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application.
+ * \since 12
+ * \brief Set a property on a CDR for a channel
+ *
+ * This function sets specific administrative properties on a CDR for a channel.
+ * This includes properties like preventing a CDR from being dispatched, to
+ * setting the channel as the preferred Party A in future CDRs. See
+ * \ref enum ast_cdr_options for more information.
+ *
+ * \param channel_name The CDR's channel
+ * \param option Option to apply to the CDR
+ *
+ * \retval 0 on success
+ * \retval 1 on error
  */
-extern void ast_cdr_noanswer(struct ast_cdr *cdr);
+int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option);
 
 /*!
- * \brief A call was set to congestion
- * \param cdr the cdr you wish to associate with the call
- * Markst he channel disposition as "CONGESTION"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application
+ * \since 12
+ * \brief Clear a property on a CDR for a channel
+ *
+ * Clears a flag previously set by \ref ast_cdr_set_property
+ *
+ * \param channel_name The CDR's channel
+ * \param option Option to clear from the CDR
+ *
+ * \retval 0 on success
+ * \retval 1 on error
  */
-extern void ast_cdr_congestion(struct ast_cdr *cdr);
+int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option);
 
 /*!
- * \brief Busy a call
- * \param cdr the cdr you wish to associate with the call
- * Marks the channel disposition as "BUSY"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application.
- * Returns nothing
+ * \brief Reset the detail record
+ * \param channel_name The channel that the CDR is associated with
+ * \param options Options that control what the reset operation does.
+ *
+ * Valid options are:
+ * \ref AST_CDR_FLAG_KEEP_VARS - keep the variables during the reset
+ * \ref AST_CDR_FLAG_DISABLE_ALL - when used with \ref ast_cdr_reset, re-enables
+ * the CDR
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
  */
-void ast_cdr_busy(struct ast_cdr *cdr);
+int ast_cdr_reset(const char *channel_name, struct ast_flags *options);
 
 /*!
- * \brief Fail a call
- * \param cdr the cdr you wish to associate with the call
- * Marks the channel disposition as "FAILED"
- * Will skip CDR's in chain with ANS_LOCK bit set. (see
- * forkCDR() application.
- * Returns nothing
+ * \brief Serializes all the data and variables for a current CDR record
+ * \param channel_name The channel to get the CDR for
+ * \param buf A buffer to use for formatting the data
+ * \param delim A delimeter to use to separate variable keys/values
+ * \param sep A separator to use between nestings
+ * \retval the total number of serialized variables
  */
-void ast_cdr_failed(struct ast_cdr *cdr);
+int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep);
 
 /*!
- * \brief Save the result of the call based on the AST_CAUSE_*
- * \param cdr the cdr you wish to associate with the call
- * \param cause the AST_CAUSE_*
- * Returns nothing
+ * \brief CDR backend callback
+ * \warning CDR backends should NOT attempt to access the channel associated
+ * with a CDR record.  This channel is not guaranteed to exist when the CDR
+ * backend is invoked.
  */
-int ast_cdr_disposition(struct ast_cdr *cdr, int cause);
+typedef int (*ast_cdrbe)(struct ast_cdr *cdr);
 
-/*!
- * \brief End a call
- * \param cdr the cdr you have associated the call with
- * Registers the end of call time in the cdr structure.
- * Returns nothing
- */
-void ast_cdr_end(struct ast_cdr *cdr);
+/*! \brief Return TRUE if CDR subsystem is enabled */
+int ast_cdr_is_enabled(void);
 
 /*!
- * \brief Detaches the detail record for posting (and freeing) either now or at a
- * later time in bulk with other records during batch mode operation.
- * \param cdr Which CDR to detach from the channel thread
- * Prevents the channel thread from blocking on the CDR handling
- * Returns nothing
+ * \brief Allocate a CDR record
+ * \retval a malloc'd ast_cdr structure
+ * \retval NULL on error (malloc failure)
  */
-void ast_cdr_detach(struct ast_cdr *cdr);
+struct ast_cdr *ast_cdr_alloc(void);
 
-/*!
- * \brief Spawns (possibly) a new thread to submit a batch of CDRs to the backend engines
- * \param shutdown Whether or not we are shutting down
- * Blocks the asterisk shutdown procedures until the CDR data is submitted.
- * Returns nothing
- */
-void ast_cdr_submit_batch(int shutdown);
 
 /*!
- * \brief Set the destination channel, if there was one
- * \param cdr Which cdr it's applied to
- * \param chan Channel to which dest will be
- * Sets the destination channel the CDR is applied to
- * Returns nothing
+ * \brief Duplicate a public CDR
+ * \param cdr the record to duplicate
+ *
+ * \retval a malloc'd ast_cdr structure,
+ * \retval NULL on error (malloc failure)
  */
-void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chan);
+struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr);
 
 /*!
- * \brief Set the last executed application
- * \param cdr which cdr to act upon
- * \param app the name of the app you wish to change it to
- * \param data the data you want in the data field of app you set it to
- * Changes the value of the last executed app
+ * \brief Free a CDR record
+ * \param cdr ast_cdr structure to free
  * Returns nothing
  */
-void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data);
-
-/*!
- * \brief Set the answer time for a call
- * \param cdr the cdr you wish to associate with the call
- * \param t the answer time
- * Starts all CDR stuff necessary for doing CDR when answering a call
- * NULL argument is just fine.
- */
-void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t);
+void ast_cdr_free(struct ast_cdr *cdr);
 
 /*!
- * \brief Set the disposition for a call
- * \param cdr the cdr you wish to associate with the call
- * \param disposition the new disposition
- * Set the disposition on a call.
- * NULL argument is just fine.
+ * \brief Register a CDR handling engine
+ * \param name name associated with the particular CDR handler
+ * \param desc description of the CDR handler
+ * \param be function pointer to a CDR handler
+ * Used to register a Call Detail Record handler.
+ * \retval 0 on success.
+ * \retval -1 on error
  */
-void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition);
+int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be);
 
 /*!
- * \brief Convert a string to a detail record AMA flag
- * \param flag string form of flag
- * Converts the string form of the flag to the binary form.
- * \return the binary form of the flag
+ * \brief Unregister a CDR handling engine
+ * \param name name of CDR handler to unregister
+ * Unregisters a CDR by it's name
  */
-int ast_cdr_amaflags2int(const char *flag);
+void ast_cdr_unregister(const char *name);
 
 /*!
  * \brief Disposition to a string
@@ -383,81 +498,15 @@ int ast_cdr_amaflags2int(const char *flag);
  * Converts the binary form of a disposition to string form.
  * \return a pointer to the string form
  */
-char *ast_cdr_disp2str(int disposition);
-
-/*!
- * \brief Reset the detail record, optionally posting it first
- * \param cdr which cdr to act upon
- * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it
- *              |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's
- */
-void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *flags);
-
-/*! Reset the detail record times, flags */
-/*!
- * \param cdr which cdr to act upon
- * \param flags |AST_CDR_FLAG_POSTED whether or not to post the cdr first before resetting it
- *              |AST_CDR_FLAG_LOCKED whether or not to reset locked CDR's
- */
-void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *flags);
-
-/*! Flags to a string */
-/*!
- * \param flags binary flag
- * Converts binary flags to string flags
- * Returns string with flag name
- */
-char *ast_cdr_flags2str(int flags);
-
-/*!
- * \brief Move the non-null data from the "from" cdr to the "to" cdr
- * \param to the cdr to get the goodies
- * \param from the cdr to give the goodies
- */
-void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from);
-
-/*!
- * \brief Set account code, will generate AMI event
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setaccount(struct ast_channel *chan, const char *account);
-
-/*!
- * \brief Set the peer account
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account);
-
-/*!
- * \brief Set AMA flags for channel
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setamaflags(struct ast_channel *chan, const char *amaflags);
+const char *ast_cdr_disp2str(int disposition);
 
 /*!
  * \brief Set CDR user field for channel (stored in CDR)
- * \note The channel should be locked before calling.
- */
-int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield);
-/*!
- * \brief Append to CDR user field for channel (stored in CDR)
- * \note The channel should be locked before calling.
- */
-int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield);
-
-
-/*!
- * \brief Update CDR on a channel
- * \note The channel should be locked before calling.
+ *
+ * \param channel_name The name of the channel that owns the CDR
+ * \param userfield The user field to set
  */
-int ast_cdr_update(struct ast_channel *chan);
-
-
-extern int ast_default_amaflags;
-
-extern char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
-
-struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr);
+void ast_cdr_setuserfield(const char *channel_name, const char *userfield);
 
 /*! \brief Reload the configuration file cdr.conf and start/stop CDR scheduling thread */
 int ast_cdr_engine_reload(void);
@@ -468,14 +517,4 @@ int ast_cdr_engine_init(void);
 /*! Submit any remaining CDRs and prepare for shutdown */
 void ast_cdr_engine_term(void);
 
-/*!
- * \brief
- * \param[in] tree Where to insert the cdr.
- * \param[in] cdr The cdr structure to insert in 'tree'.
- * \param[in] recur Go throw all the cdr levels.
- * \retval <0 on error.
- * \retval 0 on success.
- */
-int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur);
-
 #endif /* _ASTERISK_CDR_H */
diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h
index 034a96ab9bb48cfa421871988b48fc93ca5e9ecd..914037d4c164c75e16967d20064320419b6b0f9d 100644
--- a/include/asterisk/cel.h
+++ b/include/asterisk/cel.h
@@ -35,20 +35,6 @@ extern "C" {
 
 #include "asterisk/event.h"
 
-/*!
- * \brief AMA Flags
- *
- * \note This must much up with the AST_CDR_* defines for AMA flags.
- */
-enum ast_cel_ama_flag {
-	AST_CEL_AMA_FLAG_NONE,
-	AST_CEL_AMA_FLAG_OMIT,
-	AST_CEL_AMA_FLAG_BILLING,
-	AST_CEL_AMA_FLAG_DOCUMENTATION,
-	/*! \brief Must be final entry */
-	AST_CEL_AMA_FLAG_TOTAL,
-};
-
 /*!
  * \brief CEL event types
  */
@@ -162,17 +148,6 @@ const char *ast_cel_get_type_name(enum ast_cel_event_type type);
  */
 enum ast_cel_event_type ast_cel_str_to_event_type(const char *name);
 
-/*!
- * \brief Convert AMA flag to printable string
- * 
- * \param[in] flag the flag to convert to a string
- *
- * \since 1.8
- *
- * \return the string representation of the flag
- */
-const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag);
-
 /*! 
  * \brief Check and potentially retire a Linked ID
  *
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 42d50f21bf599505ae2b5bc49cd6a18ef745a80a..1b36c14ae30b2003127888dd6b2256732cdd0a20 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -131,11 +131,13 @@ References:
 extern "C" {
 #endif
 
-#define AST_MAX_EXTENSION	80	/*!< Max length of an extension */
-#define AST_MAX_CONTEXT		80	/*!< Max length of a context */
-#define AST_CHANNEL_NAME	80	/*!< Max length of an ast_channel name */
-#define MAX_LANGUAGE		40	/*!< Max length of the language setting */
-#define MAX_MUSICCLASS		80	/*!< Max length of the music class setting */
+#define AST_MAX_EXTENSION       80  /*!< Max length of an extension */
+#define AST_MAX_CONTEXT         80  /*!< Max length of a context */
+#define AST_MAX_ACCOUNT_CODE    20  /*!< Max length of an account code */
+#define AST_CHANNEL_NAME        80  /*!< Max length of an ast_channel name */
+#define MAX_LANGUAGE            40  /*!< Max length of the language setting */
+#define MAX_MUSICCLASS          80  /*!< Max length of the music class setting */
+#define AST_MAX_USER_FIELD      256 /*!< Max length of the channel user field */
 
 #include "asterisk/frame.h"
 #include "asterisk/chanvars.h"
@@ -915,6 +917,10 @@ enum {
 	 * to continue.
 	 */
 	AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT = (1 << 22),
+	/*!
+	 * This flag indicates that the channel was originated.
+	 */
+	AST_FLAG_ORIGINATED = (1 << 23),
 };
 
 /*! \brief ast_bridge_config flags */
@@ -1027,6 +1033,16 @@ enum channelreloadreason {
 	CHANNEL_ACL_RELOAD,
 };
 
+/*!
+ * \brief Channel AMA Flags
+ */
+enum ama_flags {
+	AST_AMA_NONE = 0,
+	AST_AMA_OMIT,
+	AST_AMA_BILLING,
+	AST_AMA_DOCUMENTATION,
+};
+
 /*!
  * \note None of the datastore API calls lock the ast_channel they are using.
  *       So, the channel should be locked before calling the functions that
@@ -1100,7 +1116,7 @@ struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 14)))
 	__ast_channel_alloc(int needqueue, int state, const char *cid_num,
 			    const char *cid_name, const char *acctcode,
 			    const char *exten, const char *context,
-			    const char *linkedid, const int amaflag,
+			    const char *linkedid, enum ama_flags amaflag,
 			    const char *file, int line, const char *function,
 			    const char *name_fmt, ...);
 
@@ -1591,7 +1607,6 @@ int ast_answer(struct ast_channel *chan);
  * \brief Answer a channel
  *
  * \param chan channel to answer
- * \param cdr_answer flag to control whether any associated CDR should be marked as 'answered'
  *
  * This function answers a channel and handles all necessary call
  * setup functions.
@@ -1607,14 +1622,13 @@ int ast_answer(struct ast_channel *chan);
  * \retval 0 on success
  * \retval non-zero on failure
  */
-int ast_raw_answer(struct ast_channel *chan, int cdr_answer);
+int ast_raw_answer(struct ast_channel *chan);
 
 /*!
  * \brief Answer a channel, with a selectable delay before returning
  *
  * \param chan channel to answer
  * \param delay maximum amount of time to wait for incoming media
- * \param cdr_answer flag to control whether any associated CDR should be marked as 'answered'
  *
  * This function answers a channel and handles all necessary call
  * setup functions.
@@ -1630,7 +1644,7 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer);
  * \retval 0 on success
  * \retval non-zero on failure
  */
-int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer);
+int __ast_answer(struct ast_channel *chan, unsigned int delay);
 
 /*!
  * \brief Execute a Gosub call on the channel before a call is placed.
@@ -2196,6 +2210,28 @@ int ast_activate_generator(struct ast_channel *chan, struct ast_generator *gen,
 /*! Deactivate an active generator */
 void ast_deactivate_generator(struct ast_channel *chan);
 
+/*!
+ * \since 12
+ * \brief Obtain how long the channel since the channel was created
+ *
+ * \param chan The channel object
+ *
+ * \retval 0 if the time value cannot be computed (or you called this really fast)
+ * \retval The number of seconds the channel has been up
+ */
+int ast_channel_get_duration(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Obtain how long it has been since the channel was answered
+ *
+ * \param chan The channel object
+ *
+ * \retval 0 if the channel isn't answered (or you called this really fast)
+ * \retval The number of seconds the channel has been up
+ */
+int ast_channel_get_up_time(struct ast_channel *chan);
+
 /*!
  * \brief Set caller ID number, name and ANI and generate AMI event.
  *
@@ -2727,12 +2763,6 @@ struct ast_channel *ast_channel_get_by_exten(const char *exten, const char *cont
 
 /*! @} End channel search functions. */
 
-/*!
-  \brief propagate the linked id between chan and peer
- */
-void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer);
-
-
 /*!
  * \brief Initialize the given name structure.
  * \since 1.8
@@ -3806,6 +3836,26 @@ void ast_channel_unlink(struct ast_channel *chan);
  */
 void ast_channel_hangupcause_hash_set(struct ast_channel *chan, const struct ast_control_pvt_cause_code *cause_code, int datalen);
 
+/*!
+ * \since 12
+ * \brief Convert a string to a detail record AMA flag
+ *
+ * \param flag string form of flag
+ *
+ * \retval the enum (integer) form of the flag
+ */
+enum ama_flags ast_channel_string2amaflag(const char *flag);
+
+/*!
+ * \since 12
+ * \brief Convert the enum representation of an AMA flag to a string representation
+ *
+ * \param flags integer flag
+ *
+ * \retval A string representation of the flag
+ */
+const char *ast_channel_amaflags2string(enum ama_flags flags);
+
 /* ACCESSOR FUNTIONS */
 /*! \brief Set the channel name */
 void ast_channel_name_set(struct ast_channel *chan, const char *name);
@@ -3863,8 +3913,8 @@ char ast_channel_sending_dtmf_digit(const struct ast_channel *chan);
 void ast_channel_sending_dtmf_digit_set(struct ast_channel *chan, char value);
 struct timeval ast_channel_sending_dtmf_tv(const struct ast_channel *chan);
 void ast_channel_sending_dtmf_tv_set(struct ast_channel *chan, struct timeval value);
-int ast_channel_amaflags(const struct ast_channel *chan);
-void ast_channel_amaflags_set(struct ast_channel *chan, int value);
+enum ama_flags ast_channel_amaflags(const struct ast_channel *chan);
+void ast_channel_amaflags_set(struct ast_channel *chan, enum ama_flags value);
 int ast_channel_epfd(const struct ast_channel *chan);
 void ast_channel_epfd_set(struct ast_channel *chan, int value);
 int ast_channel_fdno(const struct ast_channel *chan);
@@ -3988,6 +4038,8 @@ void ast_channel_whentohangup_set(struct ast_channel *chan, struct timeval *valu
 void ast_channel_varshead_set(struct ast_channel *chan, struct varshead *value);
 struct timeval ast_channel_creationtime(struct ast_channel *chan);
 void ast_channel_creationtime_set(struct ast_channel *chan, struct timeval *value);
+struct timeval ast_channel_answertime(struct ast_channel *chan);
+void ast_channel_answertime_set(struct ast_channel *chan, struct timeval *value);
 
 /* List getters */
 struct ast_hangup_handler_list *ast_channel_hangup_handlers(struct ast_channel *chan);
@@ -4278,4 +4330,27 @@ int ast_channel_move(struct ast_channel *dest, struct ast_channel *source);
  */
 int ast_channel_forward_endpoint(struct ast_channel *chan, struct ast_endpoint *endpoint);
 
+/*!
+ * \brief Return the oldest linkedid between two channels.
+ *
+ * A channel linkedid is derived from the channel uniqueid which is formed like this:
+ * [systemname-]ctime.seq
+ *
+ * The systemname, and the dash are optional, followed by the epoch time followed by an
+ * integer sequence.  Note that this is not a decimal number, since 1.2 is less than 1.11
+ * in uniqueid land.
+ *
+ * To compare two uniqueids, we parse out the integer values of the time and the sequence
+ * numbers and compare them, with time trumping sequence.
+ *
+ * \param a The linkedid value of the first channel to compare
+ * \param b The linkedid value of the second channel to compare
+ *
+ * \retval NULL on failure
+ * \retval The oldest linkedid value
+ * \since 12.0.0
+*/
+const char *ast_channel_oldest_linkedid(const char *a, const char *b);
+
+
 #endif /* _ASTERISK_CHANNEL_H */
diff --git a/include/asterisk/channel_internal.h b/include/asterisk/channel_internal.h
index 38776c1e1dfd4ecadf7c6c15b8d2bc152c7b93ab..a54a1b8483b36a46183d3d96699fc6d928f93660 100644
--- a/include/asterisk/channel_internal.h
+++ b/include/asterisk/channel_internal.h
@@ -18,8 +18,8 @@
  * \brief Internal channel functions for channel.c to use
  */
 
-#define ast_channel_internal_alloc(destructor) __ast_channel_internal_alloc(destructor, __FILE__, __LINE__, __PRETTY_FUNCTION__)
-struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *file, int line, const char *function);
+#define ast_channel_internal_alloc(destructor, linkedid) __ast_channel_internal_alloc(destructor, linkedid, __FILE__, __LINE__, __PRETTY_FUNCTION__)
+struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *linkedid, const char *file, int line, const char *function);
 void ast_channel_internal_finalize(struct ast_channel *chan);
 int ast_channel_internal_is_finalized(struct ast_channel *chan);
 void ast_channel_internal_cleanup(struct ast_channel *chan);
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 339c77274781084f0e856dbc2aeeb10719706901..ca075ae6902540313d1df3bfb19bfaa7eda1f385 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -38,36 +38,38 @@
  */
 struct ast_channel_snapshot {
 	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(name);			/*!< ASCII unique channel name */
-		AST_STRING_FIELD(accountcode);		/*!< Account code for billing */
-		AST_STRING_FIELD(peeraccount);		/*!< Peer account code for billing */
-		AST_STRING_FIELD(userfield);		/*!< Userfield for CEL billing */
-		AST_STRING_FIELD(uniqueid);		/*!< Unique Channel Identifier */
-		AST_STRING_FIELD(linkedid);		/*!< Linked Channel Identifier -- gets propagated by linkage */
-		AST_STRING_FIELD(parkinglot);		/*!< Default parking lot, if empty, default parking lot */
-		AST_STRING_FIELD(hangupsource);		/*!< Who is responsible for hanging up this channel */
-		AST_STRING_FIELD(appl);			/*!< Current application */
-		AST_STRING_FIELD(data);			/*!< Data passed to current application */
-		AST_STRING_FIELD(context);		/*!< Dialplan: Current extension context */
-		AST_STRING_FIELD(exten);		/*!< Dialplan: Current extension number */
-		AST_STRING_FIELD(caller_name);		/*!< Caller ID Name */
-		AST_STRING_FIELD(caller_number);	/*!< Caller ID Number */
-		AST_STRING_FIELD(caller_ani);		/*!< Caller ID ANI Number */
-		AST_STRING_FIELD(caller_rdnis);		/*!< Caller ID RDNIS Number */
-		AST_STRING_FIELD(caller_dnid);		/*!< Caller ID DNID Number */
-		AST_STRING_FIELD(connected_name);	/*!< Connected Line Name */
-		AST_STRING_FIELD(connected_number);	/*!< Connected Line Number */
-		AST_STRING_FIELD(language);		/*!< The default spoken language for the channel */
+		AST_STRING_FIELD(name);             /*!< ASCII unique channel name */
+		AST_STRING_FIELD(accountcode);      /*!< Account code for billing */
+		AST_STRING_FIELD(peeraccount);      /*!< Peer account code for billing */
+		AST_STRING_FIELD(userfield);        /*!< Userfield for CEL billing */
+		AST_STRING_FIELD(uniqueid);         /*!< Unique Channel Identifier */
+		AST_STRING_FIELD(linkedid);         /*!< Linked Channel Identifier -- gets propagated by linkage */
+		AST_STRING_FIELD(parkinglot);       /*!< Default parking lot, if empty, default parking lot */
+		AST_STRING_FIELD(hangupsource);     /*!< Who is responsible for hanging up this channel */
+		AST_STRING_FIELD(appl);             /*!< Current application */
+		AST_STRING_FIELD(data);             /*!< Data passed to current application */
+		AST_STRING_FIELD(context);          /*!< Dialplan: Current extension context */
+		AST_STRING_FIELD(exten);            /*!< Dialplan: Current extension number */
+		AST_STRING_FIELD(caller_name);      /*!< Caller ID Name */
+		AST_STRING_FIELD(caller_number);    /*!< Caller ID Number */
+		AST_STRING_FIELD(caller_dnid);      /*!< Dialed ID Number */
+		AST_STRING_FIELD(caller_ani);       /*< Caller ID ANI Number */
+		AST_STRING_FIELD(caller_rdnis);     /*!< Caller ID RDNIS Number */
+		AST_STRING_FIELD(caller_subaddr);   /*!< Caller subaddress */
+		AST_STRING_FIELD(dialed_subaddr);   /*!< Dialed subaddress */
+		AST_STRING_FIELD(connected_name);   /*!< Connected Line Name */
+		AST_STRING_FIELD(connected_number); /*!< Connected Line Number */
+		AST_STRING_FIELD(language);         /*!< The default spoken language for the channel */
 	);
 
-	struct timeval creationtime;	/*!< The time of channel creation */
-	enum ast_channel_state state;	/*!< State of line */
-	int priority;			/*!< Dialplan: Current extension priority */
-	int amaflags;			/*!< AMA flags for billing */
-	int hangupcause;		/*!< Why is the channel hanged up. See causes.h */
-	int caller_pres;		/*!< Caller ID presentation. */
-	struct ast_flags flags;		/*!< channel flags of AST_FLAG_ type */
-	struct varshead *manager_vars;	/*!< Variables to be appended to manager events */
+	struct timeval creationtime;            /*!< The time of channel creation */
+	enum ast_channel_state state;           /*!< State of line */
+	int priority;                           /*!< Dialplan: Current extension priority */
+	int amaflags;                           /*!< AMA flags for billing */
+	int hangupcause;                        /*!< Why is the channel hanged up. See causes.h */
+	int caller_pres;                        /*!< Caller ID presentation. */
+	struct ast_flags flags;                 /*!< channel flags of AST_FLAG_ type */
+	struct varshead *manager_vars;          /*!< Variables to be appended to manager events */
 };
 
 /*!
@@ -300,7 +302,7 @@ void ast_channel_publish_snapshot(struct ast_channel *chan);
  * \since 12
  * \brief Publish a \ref ast_channel_varset for a channel.
  *
- * \param chan Channel to pulish the event for, or \c NULL for 'none'.
+ * \param chan Channel to publish the event for, or \c NULL for 'none'.
  * \param variable Name of the variable being set
  * \param value Value.
  */
diff --git a/include/asterisk/stasis_internal.h b/include/asterisk/stasis_internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..67ab88ff04d1d8a9e73abf03688c7d5ee8cf91b4
--- /dev/null
+++ b/include/asterisk/stasis_internal.h
@@ -0,0 +1,69 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef STASIS_INTERNAL_H_
+#define STASIS_INTERNAL_H_
+
+/*! \file
+ *
+ * \brief Internal Stasis APIs.
+ *
+ * This header file is used to define functions that are shared between files that make
+ * up \ref stasis. Functions declared here should not be used by any module outside of
+ * Stasis.
+ *
+ * If you find yourself needing to call one of these functions directly, something has
+ * probably gone horribly wrong.
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ */
+
+struct stasis_topic;
+struct stasis_subscription;
+struct stasis_message;
+
+/*!
+ * \brief Create a subscription.
+ *
+ * In addition to being AO2 managed memory (requiring an ao2_cleanup() to free
+ * up this reference), the subscription must be explicitly unsubscribed from its
+ * topic using stasis_unsubscribe().
+ *
+ * The invocations of the callback are serialized, but may not always occur on
+ * the same thread. The invocation order of different subscriptions is
+ * unspecified.
+ *
+ * Note: modules outside of Stasis should use \ref stasis_subscribe.
+ *
+ * \param topic Topic to subscribe to.
+ * \param callback Callback function for subscription messages.
+ * \param data Data to be passed to the callback, in addition to the message.
+ * \param needs_mailbox Determines whether or not the subscription requires a mailbox.
+ *  Subscriptions with mailboxes will be delivered on a thread in the Stasis threadpool;
+ *  subscriptions without mailboxes will be delivered on the publisher thread.
+ * \return New \ref stasis_subscription object.
+ * \return \c NULL on error.
+ * \since 12
+ */
+struct stasis_subscription *internal_stasis_subscribe(
+	struct stasis_topic *topic,
+	void (*stasis_subscription_cb)(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message),
+	void *data,
+	int needs_mailbox);
+
+#endif /* STASIS_INTERNAL_H_ */
diff --git a/include/asterisk/test.h b/include/asterisk/test.h
index d890b7a3e6de24690ac4ec8c7470bbca44881e14..d29969f8bb7edb95cf2782e044ffca5f5cc0d528 100644
--- a/include/asterisk/test.h
+++ b/include/asterisk/test.h
@@ -238,7 +238,9 @@ struct ast_test_info {
 /*!
  * \brief Generic test callback function
  *
- * \param error buffer string for failure results
+ * \param info The test info object
+ * \param cmd What to perform in the test
+ * \param test The actual test object being manipulated
  *
  * \retval AST_TEST_PASS for pass
  * \retval AST_TEST_FAIL for failure
@@ -246,6 +248,30 @@ struct ast_test_info {
 typedef enum ast_test_result_state (ast_test_cb_t)(struct ast_test_info *info,
 	enum ast_test_command cmd, struct ast_test *test);
 
+/*!
+ * \since 12
+ * \brief A test initialization callback function
+ *
+ * \param info The test info object
+ * \param test The actual test object that will be manipulated
+ *
+ * \retval 0 success
+ * \retval other failure. This will fail the test.
+ */
+typedef int (ast_test_init_cb_t)(struct ast_test_info *info, struct ast_test *test);
+
+/*!
+ * \since 12
+ * \brief A test cleanup callback function
+ *
+ * \param info The test info object
+ * \param test The actual test object that was executed
+ *
+ * \retval 0 success
+ * \retval other failure. This will fail the test.
+ */
+typedef int (ast_test_cleanup_cb_t)(struct ast_test_info *info, struct ast_test *test);
+
 /*!
  * \brief unregisters a test with the test framework
  *
@@ -266,6 +292,40 @@ int ast_test_unregister(ast_test_cb_t *cb);
  */
 int ast_test_register(ast_test_cb_t *cb);
 
+/*!
+ * \since 12
+ * \brief Register an initialization function to be run before each test
+ * executes
+ *
+ * This function lets a registered test have an initialization function that
+ * will be run prior to test execution. Each category may have a single init
+ * function.
+ *
+ * If the initialization function returns a non-zero value, the test will not
+ * be executed and the result will be set to \ref AST_TEST_FAIL.
+ *
+ * \retval 0 success
+ * \retval other failure
+ */
+int ast_test_register_init(const char *category, ast_test_init_cb_t *cb);
+
+/*!
+ * \since 12
+ * \brief Register a cleanup function to be run after each test executes
+ *
+ * This function lets a registered test have a cleanup function that will be
+ * run immediately after test execution. Each category may have a single
+ * cleanup function.
+ *
+ * If the cleanup function returns a non-zero value, the test result will be
+ * set to \ref AST_TEST_FAIL.
+ *
+ * \retval 0 success
+ * \retval other failure
+ */
+int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb);
+
+
 /*!
  * \brief Unit test debug output.
  * \since 12.0.0
@@ -277,6 +337,17 @@ int ast_test_register(ast_test_cb_t *cb);
  */
 void ast_test_debug(struct ast_test *test, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
 
+/*!
+ * \brief Set the result of a test.
+ *
+ * If the caller of this function sets the result to AST_TEST_FAIL, returning
+ * AST_TEST_PASS from the test will not pass the test. This lets a test writer
+ * end and fail a test and continue on with logic, catching multiple failure
+ * conditions within a single test.
+ */
+void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state);
+
+
 /*!
  * \brief update test's status during testing.
  *
diff --git a/include/asterisk/time.h b/include/asterisk/time.h
index dd68db7044e50498c845ba9ef18876183cac100d..f2382df338ce953e97ffc75620bae99a1868020c 100644
--- a/include/asterisk/time.h
+++ b/include/asterisk/time.h
@@ -151,6 +151,17 @@ struct timeval ast_tvadd(struct timeval a, struct timeval b);
  */
 struct timeval ast_tvsub(struct timeval a, struct timeval b);
 
+/*!
+ * \since 12
+ * \brief Formats a duration into HH:MM:SS
+ *
+ * \param duration The time (in seconds) to format
+ * \param buf A buffer to hold the formatted string'
+ * \param length The size of the buffer
+ */
+void ast_format_duration_hh_mm_ss(int duration, char *buf, size_t length);
+
+
 /*!
  * \brief Calculate remaining milliseconds given a starting timestamp
  * and upper bound
diff --git a/main/asterisk.c b/main/asterisk.c
index 99da006a52fe57b0df8701e8e4a304b56fa4b81d..871cd979ab9e2e29c6b0dcfa015949e1b52a0e70 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -643,7 +643,7 @@ static char *handle_show_settings(struct ast_cli_entry *e, int cmd, struct ast_c
 	ast_cli(a->fd, "  -------------\n");
 	ast_cli(a->fd, "  Manager (AMI):               %s\n", check_manager_enabled() ? "Enabled" : "Disabled");
 	ast_cli(a->fd, "  Web Manager (AMI/HTTP):      %s\n", check_webmanager_enabled() ? "Enabled" : "Disabled");
-	ast_cli(a->fd, "  Call data records:           %s\n", check_cdr_enabled() ? "Enabled" : "Disabled");
+	ast_cli(a->fd, "  Call data records:           %s\n", ast_cdr_is_enabled() ? "Enabled" : "Disabled");
 	ast_cli(a->fd, "  Realtime Architecture (ARA): %s\n", ast_realtime_enabled() ? "Enabled" : "Disabled");
 
 	/*! \todo we could check musiconhold, voicemail, smdi, adsi, queues  */
@@ -4318,50 +4318,50 @@ int main(int argc, char *argv[])
 
 	ast_http_init();		/* Start the HTTP server, if needed */
 
-	if (ast_cdr_engine_init()) {
+	if (ast_indications_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_device_state_engine_init()) {
+	if (ast_features_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_presence_state_engine_init()) {
+	if (ast_bridging_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	ast_dsp_init();
-	ast_udptl_init();
-
-	if (ast_image_init()) {
+	if (ast_cdr_engine_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_file_init()) {
+	if (ast_device_state_engine_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (load_pbx()) {
+	if (ast_presence_state_engine_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_indications_init()) {
+	ast_dsp_init();
+	ast_udptl_init();
+
+	if (ast_image_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_features_init()) {
+	if (ast_file_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_bridging_init()) {
+	if (load_pbx()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
diff --git a/main/bridging.c b/main/bridging.c
index 73005047340dd6d2b468f92eae0d68b20144e70b..93e4ec2d808e10abe048dda856fa61b7ceebf913 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -292,6 +292,75 @@ int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action)
 	return 0;
 }
 
+void ast_bridge_update_accountcodes(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+	struct ast_bridge_channel *other = NULL;
+
+	AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+		if (other == swap) {
+			continue;
+		}
+
+		if (!ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan)) && ast_strlen_zero(ast_channel_peeraccount(other->chan))) {
+			ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n",
+					ast_channel_accountcode(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
+			ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan));
+		}
+		if (!ast_strlen_zero(ast_channel_accountcode(other->chan)) && ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan))) {
+			ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n",
+					ast_channel_accountcode(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
+			ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
+		}
+		if (!ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan)) && ast_strlen_zero(ast_channel_accountcode(other->chan))) {
+			ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n",
+					ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
+			ast_channel_accountcode_set(other->chan, ast_channel_peeraccount(bridge_channel->chan));
+		}
+		if (!ast_strlen_zero(ast_channel_peeraccount(other->chan)) && ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan))) {
+			ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n",
+					ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
+			ast_channel_accountcode_set(bridge_channel->chan, ast_channel_peeraccount(other->chan));
+		}
+		if (bridge->num_channels == 2) {
+			if (strcmp(ast_channel_accountcode(bridge_channel->chan), ast_channel_peeraccount(other->chan))) {
+				ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n",
+						ast_channel_peeraccount(other->chan), ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
+				ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan));
+			}
+			if (strcmp(ast_channel_accountcode(other->chan), ast_channel_peeraccount(bridge_channel->chan))) {
+				ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n",
+						ast_channel_peeraccount(bridge_channel->chan), ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
+				ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
+			}
+		}
+	}
+}
+
+void ast_bridge_update_linkedids(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+	struct ast_bridge_channel *other = NULL;
+	const char *oldest_linkedid = ast_channel_linkedid(bridge_channel->chan);
+
+	AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+		if (other == swap) {
+			continue;
+		}
+		oldest_linkedid = ast_channel_oldest_linkedid(oldest_linkedid, ast_channel_linkedid(other->chan));
+	}
+
+	if (ast_strlen_zero(oldest_linkedid)) {
+		return;
+	}
+
+	ast_channel_linkedid_set(bridge_channel->chan, oldest_linkedid);
+	AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+		if (other == swap) {
+			continue;
+		}
+		ast_channel_linkedid_set(other->chan, oldest_linkedid);
+	}
+}
+
 int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
 {
 	struct ast_frame *dup;
@@ -529,6 +598,14 @@ static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
 
 	ast_bridge_channel_clear_roles(bridge_channel);
 
+	/* If we are not going to be hung up after leaving a bridge, and we were an
+	 * outgoing channel, clear the outgoing flag.
+	 */
+	if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING)
+			&& (ast_channel_softhangup_internal_flag(bridge_channel->chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))) {
+		ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING);
+	}
+
 	bridge_dissolve_check(bridge_channel);
 
 	bridge->reconfigured = 1;
diff --git a/main/bridging_basic.c b/main/bridging_basic.c
index 43862a01396e1c87e22a054933ae0e9d5d980c44..09f2ca55665623a23c55c25d4f02fc3199ee78e3 100644
--- a/main/bridging_basic.c
+++ b/main/bridging_basic.c
@@ -131,6 +131,9 @@ static int bridge_basic_push(struct ast_bridge *self, struct ast_bridge_channel
 		return -1;
 	}
 
+	ast_bridge_update_accountcodes(self, bridge_channel, swap);
+	ast_bridge_update_linkedids(self, bridge_channel, swap);
+
 	return ast_bridge_base_v_table.push(self, bridge_channel, swap);
 }
 
diff --git a/main/cdr.c b/main/cdr.c
index a0560676a546191918225d6e8dd231f722ccf81b..9f710fe0d3f5f710165de8a55e635ab00a71ffe8 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -48,6 +48,7 @@
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include <signal.h>
+#include <inttypes.h>
 
 #include "asterisk/lock.h"
 #include "asterisk/channel.h"
@@ -62,88 +63,2381 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/data.h"
+#include "asterisk/config_options.h"
+#include "asterisk/json.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_message_router.h"
+#include "asterisk/astobj2.h"
 
 /*** DOCUMENTATION
+	<configInfo name="cdr" language="en_US">
+		<synopsis>Call Detail Record configuration</synopsis>
+		<description>
+			<para>CDR is Call Detail Record, which provides logging services via a variety of
+			pluggable backend modules. Detailed call information can be recorded to
+			databases, files, etc. Useful for billing, fraud prevention, compliance with
+			Sarbanes-Oxley aka The Enron Act, QOS evaluations, and more.</para>
+		</description>
+		<configFile name="cdr.conf">
+			<configObject name="general">
+				<synopsis>Global settings applied to the CDR engine.</synopsis>
+				<configOption name="debug">
+					<synopsis>Enable/disable verbose CDR debugging.</synopsis>
+					<description><para>When set to <literal>True</literal>, verbose updates
+					of changes in CDR information will be logged. Note that this is only
+					of use when debugging CDR behavior.</para>
+					</description>
+				</configOption>
+				<configOption name="enable">
+					<synopsis>Enable/disable CDR logging.</synopsis>
+					<description><para>Define whether or not to use CDR logging. Setting this to "no" will override
+					any loading of backend CDR modules.  Default is "yes".</para>
+					</description>
+				</configOption>
+				<configOption name="unanswered">
+					<synopsis>Log calls that are never answered.</synopsis>
+					<description><para>Define whether or not to log unanswered calls. Setting this to "yes" will
+					report every attempt to ring a phone in dialing attempts, when it was not
+					answered. For example, if you try to dial 3 extensions, and this option is "yes",
+					you will get 3 CDR's, one for each phone that was rung. Some find this information horribly
+					useless. Others find it very valuable. Note, in "yes" mode, you will see one CDR, with one of
+					the call targets on one side, and the originating channel on the other, and then one CDR for
+					each channel attempted. This may seem redundant, but cannot be helped.</para>
+					<para>In brief, this option controls the reporting of unanswered calls which only have an A
+					party. Calls which get offered to an outgoing line, but are unanswered, are still
+					logged, and that is the intended behavior. (It also results in some B side CDRs being
+					output, as they have the B side channel as their source channel, and no destination
+					channel.)</para>
+					</description>
+				</configOption>
+				<configOption name="congestion">
+					<synopsis>Log congested calls.</synopsis>
+					<description><para>Define whether or not to log congested calls. Setting this to "yes" will
+					report each call that fails to complete due to congestion conditions.</para>
+					</description>
+				</configOption>
+				<configOption name="endbeforehexten">
+					<synopsis>End the CDR before executing the "h" extension</synopsis>
+					<description><para>Normally, CDR's are not closed out until after all extensions are finished
+					executing.  By enabling this option, the CDR will be ended before executing
+					the <literal>h</literal> extension and hangup handlers so that CDR values such as <literal>end</literal> and
+					<literal>"billsec"</literal> may be retrieved inside of this extension.
+					The default value is "no".</para>
+					</description>
+				</configOption>
+				<configOption name="initiatedseconds">
+					<synopsis>Count microseconds for billsec purposes</synopsis>
+					<description><para>Normally, the <literal>billsec</literal> field logged to the CDR backends
+					is simply the end time (hangup time) minus the answer time in seconds. Internally,
+					asterisk stores the time in terms of microseconds and seconds. By setting
+					initiatedseconds to <literal>yes</literal>, you can force asterisk to report any seconds
+					that were initiated (a sort of round up method). Technically, this is
+					when the microsecond part of the end time is greater than the microsecond
+					part of the answer time, then the billsec time is incremented one second.</para>
+					</description>
+				</configOption>
+				<configOption name="batch">
+					<synopsis>Submit CDRs to the backends for processing in batches</synopsis>
+					<description><para>Define the CDR batch mode, where instead of posting the CDR at the end of
+					every call, the data will be stored in a buffer to help alleviate load on the
+					asterisk server.</para>
+					<warning><para>Use of batch mode may result in data loss after unsafe asterisk termination,
+					i.e., software crash, power failure, kill -9, etc.</para>
+					</warning>
+					</description>
+				</configOption>
+				<configOption name="size">
+					<synopsis>The maximum number of CDRs to accumulate before triggering a batch</synopsis>
+					<description><para>Define the maximum number of CDRs to accumulate in the buffer before posting
+					them to the backend engines. batch must be set to <literal>yes</literal>.</para>
+					</description>
+				</configOption>
+				<configOption name="time">
+					<synopsis>The maximum time to accumulate CDRs before triggering a batch</synopsis>
+					<description><para>Define the maximum time to accumulate CDRs before posting them in a batch to the
+					backend engines. If this time limit is reached, then it will post the records, regardless of the value
+					defined for size. batch must be set to <literal>yes</literal>.</para>
+					<note><para>Time is expressed in seconds.</para></note>
+					</description>
+				</configOption>
+				<configOption name="scheduleronly">
+					<synopsis>Post batched CDRs on their own thread instead of the scheduler</synopsis>
+					<description><para>The CDR engine uses the internal asterisk scheduler to determine when to post
+					records.  Posting can either occur inside the scheduler thread, or a new
+					thread can be spawned for the submission of every batch.  For small batches,
+					it might be acceptable to just use the scheduler thread, so set this to <literal>yes</literal>.
+					For large batches, say anything over size=10, a new thread is recommended, so
+					set this to <literal>no</literal>.</para>
+					</description>
+				</configOption>
+				<configOption name="safeshutdown">
+					<synopsis>Block shutdown of Asterisk until CDRs are submitted</synopsis>
+					<description><para>When shutting down asterisk, you can block until the CDRs are submitted.  If
+					you don't, then data will likely be lost.  You can always check the size of
+					the CDR batch buffer with the CLI <astcli>cdr status</astcli> command.  To enable blocking on
+					submission of CDR data during asterisk shutdown, set this to <literal>yes</literal>.</para>
+					</description>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
  ***/
 
-/*! Default AMA flag for billing records (CDR's) */
-int ast_default_amaflags = AST_CDR_DOCUMENTATION;
-char ast_default_accountcode[AST_MAX_ACCOUNT_CODE];
 
-struct ast_cdr_beitem {
+#define DEFAULT_ENABLED "1"
+#define DEFAULT_BATCHMODE "0"
+#define DEFAULT_UNANSWERED "0"
+#define DEFAULT_CONGESTION "0"
+#define DEFAULT_END_BEFORE_H_EXTEN "0"
+#define DEFAULT_INITIATED_SECONDS "0"
+
+#define DEFAULT_BATCH_SIZE "100"
+#define MAX_BATCH_SIZE 1000
+#define DEFAULT_BATCH_TIME "300"
+#define MAX_BATCH_TIME 86400
+#define DEFAULT_BATCH_SCHEDULER_ONLY "0"
+#define DEFAULT_BATCH_SAFE_SHUTDOWN "1"
+
+#define CDR_DEBUG(mod_cfg, fmt, ...) \
+	do { \
+	if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \
+		ast_verb(1, (fmt), ##__VA_ARGS__); \
+	} } while (0)
+
+static void cdr_detach(struct ast_cdr *cdr);
+static void cdr_submit_batch(int shutdown);
+
+/*! \brief The configuration settings for this module */
+struct module_config {
+	struct ast_cdr_config *general;		/*< CDR global settings */
+};
+
+/*! \brief The container for the module configuration */
+static AO2_GLOBAL_OBJ_STATIC(module_configs);
+
+/*! \brief The type definition for general options */
+static struct aco_type general_option = {
+	.type = ACO_GLOBAL,
+	.name = "general",
+	.item_offset = offsetof(struct module_config, general),
+	.category = "^general$",
+	.category_match = ACO_WHITELIST,
+};
+
+static void *module_config_alloc(void);
+static void module_config_destructor(void *obj);
+
+/*! \brief The file definition */
+static struct aco_file module_file_conf = {
+	.filename = "cdr.conf",
+	.skip_category = "(^csv$|^custom$|^manager$|^odbc$|^pgsql$|^radius$|^sqlite$|^tds$|^mysql$)",
+	.types = ACO_TYPES(&general_option),
+};
+
+CONFIG_INFO_CORE("cdr", cfg_info, module_configs, module_config_alloc,
+	.files = ACO_FILES(&module_file_conf),
+);
+
+static struct aco_type *general_options[] = ACO_TYPES(&general_option);
+
+/*! \brief Dispose of a module config object */
+static void module_config_destructor(void *obj)
+{
+	struct module_config *cfg = obj;
+
+	if (!cfg) {
+		return;
+	}
+	ao2_ref(cfg->general, -1);
+}
+
+/*! \brief Create a new module config object */
+static void *module_config_alloc(void)
+{
+	struct module_config *mod_cfg;
+	struct ast_cdr_config *cdr_config;
+
+	mod_cfg = ao2_alloc(sizeof(*mod_cfg), module_config_destructor);
+	if (!mod_cfg) {
+		return NULL;
+	}
+
+	cdr_config = ao2_alloc(sizeof(*cdr_config), NULL);
+	if (!cdr_config) {
+		ao2_ref(cdr_config, -1);
+		return NULL;
+	}
+	mod_cfg->general = cdr_config;
+
+	return mod_cfg;
+}
+
+/*! \brief Registration object for CDR backends */
+struct cdr_beitem {
 	char name[20];
 	char desc[80];
 	ast_cdrbe be;
-	AST_RWLIST_ENTRY(ast_cdr_beitem) list;
+	AST_RWLIST_ENTRY(cdr_beitem) list;
+};
+
+/*! \brief List of registered backends */
+static AST_RWLIST_HEAD_STATIC(be_list, cdr_beitem);
+
+/*! \brief Queued CDR waiting to be batched */
+struct cdr_batch_item {
+	struct ast_cdr *cdr;
+	struct cdr_batch_item *next;
+};
+
+/*! \brief The actual batch queue */
+static struct cdr_batch {
+	int size;
+	struct cdr_batch_item *head;
+	struct cdr_batch_item *tail;
+} *batch = NULL;
+
+/*! \brief The global sequence counter used for CDRs */
+static int global_cdr_sequence =  0;
+
+/*! \brief Scheduler items */
+static struct ast_sched_context *sched;
+static int cdr_sched = -1;
+AST_MUTEX_DEFINE_STATIC(cdr_sched_lock);
+static pthread_t cdr_thread = AST_PTHREADT_NULL;
+
+/*! \brief Lock protecting modifications to the batch queue */
+AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
+
+/*! \brief These are used to wake up the CDR thread when there's work to do */
+AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
+static ast_cond_t cdr_pending_cond;
+
+/*! \brief A container of the active CDRs indexed by Party A channel name */
+static struct ao2_container *active_cdrs_by_channel;
+
+/*! \brief A container of the active CDRs indexed by the bridge ID */
+static struct ao2_container *active_cdrs_by_bridge;
+
+/*! \brief Message router for stasis messages regarding channel state */
+static struct stasis_message_router *stasis_router;
+
+/*! \brief Our subscription for bridges */
+static struct stasis_subscription *bridge_subscription;
+
+/*! \brief Our subscription for channels */
+static struct stasis_subscription *channel_subscription;
+
+/*! \brief The parent topic for all topics we want to aggregate for CDRs */
+static struct stasis_topic *cdr_topic;
+
+struct cdr_object;
+
+/*!
+ * \brief A virtual table used for \ref cdr_object.
+ *
+ * Note that all functions are optional - if a subclass does not need an
+ * implementation, it is safe to leave it NULL.
+ */
+struct cdr_object_fn_table {
+	/*! \brief Name of the subclass */
+	const char *name;
+
+	/*!
+	 * \brief An initialization function. This will be called automatically
+	 * when a \ref cdr_object is switched to this type in
+	 * \ref cdr_object_transition_state
+	 *
+	 * \param cdr The \ref cdr_object that was just transitioned
+	 */
+	void (* const init_function)(struct cdr_object *cdr);
+
+	/*!
+	 * \brief Process a Party A update for the \ref cdr_object
+	 *
+	 * \param cdr The \ref cdr_object to process the update
+	 * \param snapshot The snapshot for the CDR's Party A
+	 * \retval 0 the CDR handled the update or ignored it
+	 * \retval 1 the CDR is finalized and a new one should be made to handle it
+	 */
+	int (* const process_party_a)(struct cdr_object *cdr,
+			struct ast_channel_snapshot *snapshot);
+
+	/*!
+	 * \brief Process a Party B update for the \ref cdr_object
+	 *
+	 * \param cdr The \ref cdr_object to process the update
+	 * \param snapshot The snapshot for the CDR's Party B
+	 */
+	void (* const process_party_b)(struct cdr_object *cdr,
+			struct ast_channel_snapshot *snapshot);
+
+	/*!
+	 * \brief Process the beginning of a dial. A dial message implies one of two
+	 * things:
+	 * The \ref cdr_object's Party A has been originated
+	 * The \ref cdr_object's Party A is dialing its Party B
+	 *
+	 * \param cdr The \ref cdr_object
+	 * \param caller The originator of the dial attempt
+	 * \param peer The destination of the dial attempt
+	 *
+	 * \retval 0 if the parties in the dial were handled by this CDR
+	 * \retval 1 if the parties could not be handled by this CDR
+	 */
+	int (* const process_dial_begin)(struct cdr_object *cdr,
+			struct ast_channel_snapshot *caller,
+			struct ast_channel_snapshot *peer);
+
+	/*!
+	 * \brief Process the end of a dial. At the end of a dial, a CDR can be
+	 * transitioned into one of two states - DialedPending
+	 * (\ref dialed_pending_state_fn_table) or Finalized
+	 * (\ref finalized_state_fn_table).
+	 *
+	 * \param cdr The \ref cdr_object
+	 * \param caller The originator of the dial attempt
+	 * \param peer the Destination of the dial attempt
+	 * \param dial_status What happened
+	 *
+	 * \retval 0 if the parties in the dial were handled by this CDR
+	 * \retval 1 if the parties could not be handled by this CDR
+	 */
+	int (* const process_dial_end)(struct cdr_object *cdr,
+			struct ast_channel_snapshot *caller,
+			struct ast_channel_snapshot *peer,
+			const char *dial_status);
+
+	/*!
+	 * \brief Process the entering of a bridge by this CDR. The purpose of this
+	 * callback is to have the CDR prepare itself for the bridge and attempt to
+	 * find a valid Party B. The act of creating new CDRs based on the entering
+	 * of this channel into the bridge is handled by the higher level message
+	 * handler.
+	 *
+	 * \param cdr The \ref cdr_object
+	 * \param bridge The bridge that the Party A just entered into
+	 * \param channel The \ref ast_channel_snapshot for this CDR's Party A
+	 *
+	 * \retval 0 This CDR found a Party B for itself and updated it, or there
+	 * was no Party B to find (we're all alone)
+	 * \retval 1 This CDR couldn't find a Party B, and there were options
+	 */
+	int (* const process_bridge_enter)(struct cdr_object *cdr,
+			struct ast_bridge_snapshot *bridge,
+			struct ast_channel_snapshot *channel);
+
+	/*!
+	 * \brief Process the leaving of a bridge by this CDR.
+	 *
+	 * \param cdr The \ref cdr_object
+	 * \param bridge The bridge that the Party A just left
+	 * \param channel The \ref ast_channel_snapshot for this CDR's Party A
+	 *
+	 * \retval 0 This CDR left successfully
+	 * \retval 1 Error
+	 */
+	int (* const process_bridge_leave)(struct cdr_object *cdr,
+			struct ast_bridge_snapshot *bridge,
+			struct ast_channel_snapshot *channel);
+};
+
+static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
+
+static void single_state_init_function(struct cdr_object *cdr);
+static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+
+/*!
+ * \brief The virtual table for the Single state.
+ *
+ * A \ref cdr_object starts off in this state. This represents a channel that
+ * has no Party B information itself.
+ *
+ * A \ref cdr_object from this state can go into any of the following states:
+ * * \ref dial_state_fn_table
+ * * \ref bridge_state_fn_table
+ * * \ref finalized_state_fn_table
+ */
+struct cdr_object_fn_table single_state_fn_table = {
+	.name = "Single",
+	.init_function = single_state_init_function,
+	.process_party_a = base_process_party_a,
+	.process_party_b = single_state_process_party_b,
+	.process_dial_begin = single_state_process_dial_begin,
+	.process_dial_end = base_process_dial_end,
+	.process_bridge_enter = single_state_process_bridge_enter,
+	.process_bridge_leave = base_process_bridge_leave,
+};
+
+static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
+static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+
+/*!
+ * \brief The virtual table for the Dial state.
+ *
+ * A \ref cdr_object that has begun a dial operation. This state is entered when
+ * the Party A for a CDR is determined to be dialing out to a Party B or when
+ * a CDR is for an originated channel (in which case the Party A information is
+ * the originated channel, and there is no Party B).
+ *
+ * A \ref cdr_object from this state can go in any of the following states:
+ * * \ref dialed_pending_state_fn_table
+ * * \ref bridge_state_fn_table
+ * * \ref finalized_state_fn_table
+ */
+struct cdr_object_fn_table dial_state_fn_table = {
+	.name = "Dial",
+	.process_party_a = base_process_party_a,
+	.process_party_b = dial_state_process_party_b,
+	.process_dial_begin = dial_state_process_dial_begin,
+	.process_dial_end = dial_state_process_dial_end,
+	.process_bridge_enter = dial_state_process_bridge_enter,
+	.process_bridge_leave = base_process_bridge_leave,
+};
+
+static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+
+/*!
+ * \brief The virtual table for the Dialed Pending state.
+ *
+ * A \ref cdr_object that has successfully finished a dial operation, but we
+ * don't know what they're going to do yet. It's theoretically possible to dial
+ * a party and then have that party not be bridged with the caller; likewise,
+ * an origination can complete and the channel go off and execute dialplan. The
+ * pending state acts as a bridge between either:
+ * * Entering a bridge
+ * * Getting a new CDR for new dialplan execution
+ * * Switching from being originated to executing dialplan
+ *
+ * A \ref cdr_object from this state can go in any of the following states:
+ * * \ref single_state_fn_table
+ * * \ref dialed_pending_state_fn_table
+ * * \ref bridge_state_fn_table
+ * * \ref finalized_state_fn_table
+ */
+struct cdr_object_fn_table dialed_pending_state_fn_table = {
+	.name = "DialedPending",
+	.process_party_a = dialed_pending_state_process_party_a,
+	.process_dial_begin = dialed_pending_state_process_dial_begin,
+	.process_bridge_enter = dialed_pending_state_process_bridge_enter,
+	.process_bridge_leave = base_process_bridge_leave,
+};
+
+static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+
+/*!
+ * \brief The virtual table for the Bridged state
+ *
+ * A \ref cdr_object enters this state when it receives notification that the
+ * channel has entered a bridge.
+ *
+ * A \ref cdr_object from this state can go to:
+ * * \ref finalized_state_fn_table
+ * * \ref pending_state_fn_table
+ */
+struct cdr_object_fn_table bridge_state_fn_table = {
+	.name = "Bridged",
+	.process_party_a = base_process_party_a,
+	.process_party_b = bridge_state_process_party_b,
+	.process_bridge_leave = bridge_state_process_bridge_leave,
+};
+
+static void pending_state_init_function(struct cdr_object *cdr);
+static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer);
+static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+
+/*!
+ * \brief The virtual table for the Pending state
+ *
+ * At certain times, we don't know where to go with the CDR. A good example is
+ * when a channel leaves a bridge - we don't know if the channel is about to
+ * be hung up; if it is about to go execute dialplan; dial someone; go into
+ * another bridge, etc. At these times, the CDR goes into pending and observes
+ * the messages that come in next to infer where the next logical place to go
+ * is.
+ *
+ * In this state, a CDR can go anywhere!
+ */
+struct cdr_object_fn_table bridged_pending_state_fn_table = {
+	.name = "Pending",
+	.init_function = pending_state_init_function,
+	.process_party_a = pending_state_process_party_a,
+	.process_dial_begin = pending_state_process_dial_begin,
+	.process_dial_end = base_process_dial_end,
+	.process_bridge_enter = pending_state_process_bridge_enter,
+	.process_bridge_leave = base_process_bridge_leave,
+};
+
+static void finalized_state_init_function(struct cdr_object *cdr);
+static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+
+/*!
+ * \brief The virtual table for the finalized state.
+ *
+ * Once in the finalized state, the CDR is done. No modifications can be made
+ * to the CDR.
+ */
+struct cdr_object_fn_table finalized_state_fn_table = {
+	.name = "Finalized",
+	.init_function = finalized_state_init_function,
+	.process_party_a = finalized_state_process_party_a,
+};
+
+/*! \brief A wrapper object around a snapshot.
+ * Fields that are mutable by the CDR engine are replicated here.
+ */
+struct cdr_object_snapshot {
+	struct ast_channel_snapshot *snapshot;  /*!< The channel snapshot */
+	char userfield[AST_MAX_USER_FIELD];     /*!< Userfield for the channel */
+	unsigned int flags;                     /*!< Specific flags for this party */
+	struct varshead variables;              /*!< CDR variables for the channel */
+};
+
+/*! \brief An in-memory representation of an active CDR */
+struct cdr_object {
+	struct cdr_object_snapshot party_a;     /*!< The Party A information */
+	struct cdr_object_snapshot party_b;     /*!< The Party B information */
+	struct cdr_object_fn_table *fn_table;   /*!< The current virtual table */
+
+	enum ast_cdr_disposition disposition;   /*!< The disposition of the CDR */
+	struct timeval start;                   /*!< When this CDR was created */
+	struct timeval answer;                  /*!< Either when the channel was answered, or when the path between channels was established */
+	struct timeval end;                     /*!< When this CDR was finalized */
+	unsigned int sequence;                  /*!< A monotonically increasing number for each CDR */
+	struct ast_flags flags;                 /*!< Flags on the CDR */
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(linkedid);         /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */
+		AST_STRING_FIELD(name);             /*!< Channel name of party A. Cached here as the party A address may change */
+		AST_STRING_FIELD(bridge);           /*!< The bridge the party A happens to be in. */
+		AST_STRING_FIELD(appl);             /*!< The last accepted application party A was in */
+		AST_STRING_FIELD(data);             /*!< The data for the last accepted application party A was in */
+	);
+	struct cdr_object *next;                /*!< The next CDR object in the chain */
+	struct cdr_object *last;                /*!< The last CDR object in the chain */
+};
+
+/*!
+ * \brief Copy variables from one list to another
+ * \param to_list destination
+ * \param from_list source
+ * \retval The number of copied variables
+ */
+static int copy_variables(struct varshead *to_list, struct varshead *from_list)
+{
+	struct ast_var_t *variables, *newvariable = NULL;
+	const char *var, *val;
+	int x = 0;
+
+	AST_LIST_TRAVERSE(from_list, variables, entries) {
+		if (variables &&
+		    (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
+		    !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
+			newvariable = ast_var_assign(var, val);
+			AST_LIST_INSERT_HEAD(to_list, newvariable, entries);
+			x++;
+		}
+	}
+
+	return x;
+}
+
+/*!
+ * \brief Delete all variables from a variable list
+ * \param headp The head pointer to the variable list to delete
+ */
+static void free_variables(struct varshead *headp)
+{
+	struct ast_var_t *vardata;
+
+	while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
+		ast_var_delete(vardata);
+	}
+}
+
+/*!
+ * \brief Copy a snapshot and its details
+ * \param dst The destination
+ * \param src The source
+ */
+static void cdr_object_snapshot_copy(struct cdr_object_snapshot *dst, struct cdr_object_snapshot *src)
+{
+	if (dst->snapshot) {
+		ao2_t_ref(dst->snapshot, -1, "release old snapshot during copy");
+	}
+	dst->snapshot = src->snapshot;
+	ao2_t_ref(dst->snapshot, +1, "bump new snapshot during copy");
+	strcpy(dst->userfield, src->userfield);
+	dst->flags = src->flags;
+	copy_variables(&dst->variables, &src->variables);
+}
+
+/*!
+ * \brief Transition a \ref cdr_object to a new state
+ * \param cdr The \ref cdr_object to transition
+ * \param fn_table The \ref cdr_object_fn_table state to go to
+ */
+static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	CDR_DEBUG(mod_cfg, "%p - Transitioning CDR for %s from state %s to %s\n",
+		cdr, cdr->party_a.snapshot->name,
+		cdr->fn_table ? cdr->fn_table->name : "NONE", fn_table->name);
+	cdr->fn_table = fn_table;
+	if (cdr->fn_table->init_function) {
+		cdr->fn_table->init_function(cdr);
+	}
+}
+/*! \internal
+ * \brief Hash function for containers of CDRs indexing by Party A name */
+static int cdr_object_channel_hash_fn(const void *obj, const int flags)
+{
+	const struct cdr_object *cdr = obj;
+	const char *name = (flags & OBJ_KEY) ? obj : cdr->name;
+	return ast_str_case_hash(name);
+}
+
+/*! \internal
+ * \brief Comparison function for containers of CDRs indexing by Party A name
+ */
+static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
+{
+	struct cdr_object *left = obj;
+	struct cdr_object *right = arg;
+	const char *match = (flags & OBJ_KEY) ? arg : right->name;
+	return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
+}
+
+/*! \internal
+ * \brief Hash function for containers of CDRs indexing by bridge ID
+ */
+static int cdr_object_bridge_hash_fn(const void *obj, const int flags)
+{
+	const struct cdr_object *cdr = obj;
+	const char *id = (flags & OBJ_KEY) ? obj : cdr->bridge;
+	return ast_str_case_hash(id);
+}
+
+/*! \internal
+ * \brief Comparison function for containers of CDRs indexing by bridge. Note
+ * that we expect there to be collisions, as a single bridge may have multiple
+ * CDRs active at one point in time
+ */
+static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
+{
+	struct cdr_object *left = obj;
+	struct cdr_object *right = arg;
+	struct cdr_object *it_cdr;
+	const char *match = (flags & OBJ_KEY) ? arg : right->bridge;
+	for (it_cdr = left; it_cdr; it_cdr = it_cdr->next) {
+		if (!strcasecmp(it_cdr->bridge, match)) {
+			return CMP_MATCH;
+		}
+	}
+	return 0;
+}
+
+/*!
+ * \brief \ref cdr_object Destructor
+ */
+static void cdr_object_dtor(void *obj)
+{
+	struct cdr_object *cdr = obj;
+	struct ast_var_t *it_var;
+
+	if (!cdr) {
+		return;
+	}
+
+	ao2_cleanup(cdr->party_a.snapshot);
+	ao2_cleanup(cdr->party_b.snapshot);
+	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a.variables, entries))) {
+		ast_var_delete(it_var);
+	}
+	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_b.variables, entries))) {
+		ast_var_delete(it_var);
+	}
+	ast_string_field_free_memory(cdr);
+
+	if (cdr->next) {
+		ao2_cleanup(cdr->next);
+	}
+}
+
+/*!
+ * \brief \ref cdr_object constructor
+ * \param chan The \ref ast_channel_snapshot that is the CDR's Party A
+ *
+ * This implicitly sets the state of the newly created CDR to the Single state
+ * (\ref single_state_fn_table)
+ */
+static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct cdr_object *cdr;
+
+	ast_assert(chan != NULL);
+
+	cdr = ao2_alloc(sizeof(*cdr), cdr_object_dtor);
+	if (!cdr) {
+		return NULL;
+	}
+	cdr->last = cdr;
+	if (ast_string_field_init(cdr, 64)) {
+		return NULL;
+	}
+	ast_string_field_set(cdr, name, chan->name);
+	ast_string_field_set(cdr, linkedid, chan->linkedid);
+	cdr->disposition = AST_CDR_NULL;
+	cdr->sequence = ast_atomic_fetchadd_int(&global_cdr_sequence, +1);
+
+	cdr->party_a.snapshot = chan;
+	ao2_t_ref(cdr->party_a.snapshot, +1, "bump snapshot during CDR creation");
+
+	CDR_DEBUG(mod_cfg, "%p - Created CDR for channel %s\n", cdr, chan->name);
+
+	cdr_object_transition_state(cdr, &single_state_fn_table);
+
+	return cdr;
+}
+
+/*!
+ * \brief Create a new \ref cdr_object and append it to an existing chain
+ * \param cdr The \ref cdr_object to append to
+ */
+static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
+{
+	struct cdr_object *new_cdr;
+	struct cdr_object *it_cdr;
+	struct cdr_object *cdr_last;
+
+	cdr_last = cdr->last;
+	new_cdr = cdr_object_alloc(cdr_last->party_a.snapshot);
+	if (!new_cdr) {
+		return NULL;
+	}
+	new_cdr->disposition = AST_CDR_NULL;
+
+	/* Copy over the linkedid, as it may have changed */
+	ast_string_field_set(new_cdr, linkedid, cdr_last->linkedid);
+	ast_string_field_set(new_cdr, appl, cdr_last->appl);
+	ast_string_field_set(new_cdr, data, cdr_last->data);
+
+	/* Copy over other Party A information */
+	cdr_object_snapshot_copy(&new_cdr->party_a, &cdr_last->party_a);
+
+	/* Append the CDR to the end of the list */
+	for (it_cdr = cdr; it_cdr->next; it_cdr = it_cdr->next) {
+		it_cdr->last = new_cdr;
+	}
+	it_cdr->last = new_cdr;
+	it_cdr->next = new_cdr;
+
+	return new_cdr;
+}
+
+/*!
+ * \brief Return whether or not a \ref ast_channel_snapshot is for a channel
+ * that was created as the result of a dial operation
+ *
+ * \retval 0 the channel was not created as the result of a dial
+ * \retval 1 the channel was created as the result of a dial
+ */
+static int snapshot_is_dialed(struct ast_channel_snapshot *snapshot)
+{
+	return (ast_test_flag(&snapshot->flags, AST_FLAG_OUTGOING)
+			&& !(ast_test_flag(&snapshot->flags, AST_FLAG_ORIGINATED)));
+}
+
+/*!
+ * \brief Given two CDR snapshots, figure out who should be Party A for the
+ * resulting CDR
+ * \param left One of the snapshots
+ * \param right The other snapshot
+ * \retval The snapshot that won
+ */
+static struct cdr_object_snapshot *cdr_object_pick_party_a(struct cdr_object_snapshot *left, struct cdr_object_snapshot *right)
+{
+	/* Check whether or not the party is dialed. A dialed party is never the
+	 * Party A with a party that was not dialed.
+	 */
+	if (!snapshot_is_dialed(left->snapshot) && snapshot_is_dialed(right->snapshot)) {
+		return left;
+	} else if (snapshot_is_dialed(left->snapshot) && !snapshot_is_dialed(right->snapshot)) {
+		return right;
+	}
+
+	/* Try the Party A flag */
+	if (ast_test_flag(left, AST_CDR_FLAG_PARTY_A) && !ast_test_flag(right, AST_CDR_FLAG_PARTY_A)) {
+		return left;
+	} else if (!ast_test_flag(right, AST_CDR_FLAG_PARTY_A) && ast_test_flag(right, AST_CDR_FLAG_PARTY_A)) {
+		return right;
+	}
+
+	/* Neither party is dialed and neither has the Party A flag - defer to
+	 * creation time */
+	if (left->snapshot->creationtime.tv_sec < right->snapshot->creationtime.tv_sec) {
+		return left;
+	} else if (left->snapshot->creationtime.tv_sec > right->snapshot->creationtime.tv_sec) {
+		return right;
+	} else if (left->snapshot->creationtime.tv_usec > right->snapshot->creationtime.tv_usec) {
+			return right;
+	} else {
+		/* Okay, fine, take the left one */
+		return left;
+	}
+}
+
+/*!
+ * Compute the duration for a \ref cdr_object
+ */
+static long cdr_object_get_duration(struct cdr_object *cdr)
+{
+	if (ast_tvzero(cdr->end)) {
+		return (long)(ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
+	} else {
+		return (long)(ast_tvdiff_ms(cdr->end, cdr->start) / 1000);
+	}
+}
+
+/*!
+ * \brief Compute the billsec for a \ref cdr_object
+ */
+static long cdr_object_get_billsec(struct cdr_object *cdr)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	long int ms;
+
+	if (ast_tvzero(cdr->answer)) {
+		return 0;
+	}
+	ms = ast_tvdiff_ms(cdr->end, cdr->answer);
+	if (ast_test_flag(&mod_cfg->general->settings, CDR_INITIATED_SECONDS)
+		&& (ms % 1000 >= 500)) {
+		ms = (ms / 1000) + 1;
+	} else {
+		ms = ms / 1000;
+	}
+
+	return ms;
+}
+
+/*!
+ * \brief Create a chain of \ref ast_cdr objects from a chain of \ref cdr_object
+ * suitable for consumption by the registered CDR backends
+ * \param cdr The \ref cdr_object to convert to a public record
+ * \retval A chain of \ref ast_cdr objects on success
+ * \retval NULL on failure
+ */
+static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
+{
+	struct ast_cdr *pub_cdr = NULL, *cdr_prev;
+	struct ast_var_t *it_var, *it_copy_var;
+	struct ast_channel_snapshot *party_a;
+	struct ast_channel_snapshot *party_b;
+
+	while (cdr) {
+		struct ast_cdr *cdr_copy;
+
+		/* Don't create records for CDRs where the party A was a dialed channel */
+		if (snapshot_is_dialed(cdr->party_a.snapshot)) {
+			cdr = cdr->next;
+			continue;
+		}
+
+		cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+		if (!cdr_copy) {
+			ast_free(pub_cdr);
+			return NULL;
+		}
+
+		party_a = cdr->party_a.snapshot;
+		party_b = cdr->party_b.snapshot;
+
+		/* Party A */
+		ast_assert(party_a != NULL);
+		ast_copy_string(cdr_copy->accountcode, party_a->accountcode, sizeof(cdr_copy->accountcode));
+		cdr_copy->amaflags = party_a->amaflags;
+		ast_copy_string(cdr_copy->channel, party_a->name, sizeof(cdr_copy->channel));
+		ast_callerid_merge(cdr_copy->clid, sizeof(cdr_copy->clid), party_a->caller_name, party_a->caller_number, "");
+		ast_copy_string(cdr_copy->src, party_a->caller_number, sizeof(cdr_copy->src));
+		ast_copy_string(cdr_copy->uniqueid, party_a->uniqueid, sizeof(cdr_copy->uniqueid));
+		ast_copy_string(cdr_copy->lastapp, cdr->appl, sizeof(cdr_copy->lastapp));
+		ast_copy_string(cdr_copy->lastdata, cdr->data, sizeof(cdr_copy->lastdata));
+		ast_copy_string(cdr_copy->dst, party_a->exten, sizeof(cdr_copy->dst));
+		ast_copy_string(cdr_copy->dcontext, party_a->context, sizeof(cdr_copy->dcontext));
+
+		/* Party B */
+		if (party_b) {
+			ast_copy_string(cdr_copy->dstchannel, party_b->name, sizeof(cdr_copy->dstchannel));
+			ast_copy_string(cdr_copy->peeraccount, party_b->accountcode, sizeof(cdr_copy->peeraccount));
+			if (!ast_strlen_zero(cdr->party_b.userfield)) {
+				snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", cdr->party_a.userfield, cdr->party_b.userfield);
+			}
+		}
+		if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(cdr->party_a.userfield)) {
+			ast_copy_string(cdr_copy->userfield, cdr->party_a.userfield, sizeof(cdr_copy->userfield));
+		}
+
+		/* Timestamps/durations */
+		cdr_copy->start = cdr->start;
+		cdr_copy->answer = cdr->answer;
+		cdr_copy->end = cdr->end;
+		cdr_copy->billsec = cdr_object_get_billsec(cdr);
+		cdr_copy->duration = cdr_object_get_duration(cdr);
+
+		/* Flags and IDs */
+		ast_copy_flags(cdr_copy, &cdr->flags, AST_FLAGS_ALL);
+		ast_copy_string(cdr_copy->linkedid, cdr->linkedid, sizeof(cdr_copy->linkedid));
+		cdr_copy->disposition = cdr->disposition;
+		cdr_copy->sequence = cdr->sequence;
+
+		/* Variables */
+		copy_variables(&cdr_copy->varshead, &cdr->party_a.variables);
+		AST_LIST_TRAVERSE(&cdr->party_b.variables, it_var, entries) {
+			int found = 0;
+			AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
+				if (!strcmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				AST_LIST_INSERT_TAIL(&cdr_copy->varshead, ast_var_assign(ast_var_name(it_var),
+						ast_var_value(it_var)), entries);
+			}
+		}
+
+		if (!pub_cdr) {
+			pub_cdr = cdr_copy;
+			cdr_prev = pub_cdr;
+		} else {
+			cdr_prev->next = cdr_copy;
+			cdr_prev = cdr_copy;
+		}
+		cdr = cdr->next;
+	}
+
+	return pub_cdr;
+}
+
+/*!
+ * \brief Dispatch a CDR.
+ * \param cdr The \ref cdr_object to dispatch
+ *
+ * This will create a \ref ast_cdr object and publish it to the various backends
+ */
+static void cdr_object_dispatch(struct cdr_object *cdr)
+{
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct ast_cdr *pub_cdr;
+
+	CDR_DEBUG(mod_cfg, "%p - Dispatching CDR for Party A %s, Party B %s\n", cdr,
+			cdr->party_a.snapshot->name,
+			cdr->party_b.snapshot ? cdr->party_b.snapshot->name : "<none>");
+	pub_cdr = cdr_object_create_public_records(cdr);
+	cdr_detach(pub_cdr);
+}
+
+/*!
+ * \brief Set the disposition on a \ref cdr_object based on a hangupcause code
+ * \param cdr The \ref cdr_object
+ * \param hangupcause The Asterisk hangup cause code
+ */
+static void cdr_object_set_disposition(struct cdr_object *cdr, int hangupcause)
+{
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	/* Change the disposition based on the hang up cause */
+	switch (hangupcause) {
+	case AST_CAUSE_BUSY:
+		cdr->disposition = AST_CDR_BUSY;
+		break;
+	case AST_CAUSE_CONGESTION:
+		if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) {
+			cdr->disposition = AST_CDR_FAILED;
+		} else {
+			cdr->disposition = AST_CDR_CONGESTION;
+		}
+		break;
+	case AST_CAUSE_NO_ROUTE_DESTINATION:
+	case AST_CAUSE_UNREGISTERED:
+		cdr->disposition = AST_CDR_FAILED;
+		break;
+	case AST_CAUSE_NORMAL_CLEARING:
+	case AST_CAUSE_NO_ANSWER:
+		cdr->disposition = AST_CDR_NOANSWER;
+		break;
+	default:
+		break;
+	}
+}
+
+/*!
+ * \brief Finalize a CDR.
+ *
+ * This function is safe to call multiple times. Note that you can call this
+ * explicitly before going to the finalized state if there's a chance the CDR
+ * will be re-activated, in which case the \ref cdr_object's end time should be
+ * cleared. This function is implicitly called when a CDR transitions to the
+ * finalized state and right before it is dispatched
+ *
+ * \param cdr_object The CDR to finalize
+ */
+static void cdr_object_finalize(struct cdr_object *cdr)
+{
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (!ast_tvzero(cdr->end)) {
+		return;
+	}
+	cdr->end = ast_tvnow();
+
+	if (cdr->disposition == AST_CDR_NULL) {
+		if (!ast_tvzero(cdr->answer)) {
+			cdr->disposition = AST_CDR_ANSWERED;
+		} else if (cdr->party_a.snapshot->hangupcause) {
+			cdr_object_set_disposition(cdr, cdr->party_a.snapshot->hangupcause);
+		} else if (cdr->party_b.snapshot && cdr->party_b.snapshot->hangupcause) {
+			cdr_object_set_disposition(cdr, cdr->party_b.snapshot->hangupcause);
+		} else {
+			cdr->disposition = AST_CDR_FAILED;
+		}
+	}
+
+	ast_debug(1, "Finalized CDR for %s - start %ld.%ld answer %ld.%ld end %ld.%ld dispo %s\n",
+			cdr->party_a.snapshot->name,
+			cdr->start.tv_sec,
+			cdr->start.tv_usec,
+			cdr->answer.tv_sec,
+			cdr->answer.tv_usec,
+			cdr->end.tv_sec,
+			cdr->end.tv_usec,
+			ast_cdr_disp2str(cdr->disposition));
+}
+
+/*!
+ * \brief Check to see if a CDR needs to move to the finalized state because
+ * its Party A hungup.
+ */
+static void cdr_object_check_party_a_hangup(struct cdr_object *cdr)
+{
+	if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)
+		&& cdr->fn_table != &finalized_state_fn_table) {
+		cdr_object_transition_state(cdr, &finalized_state_fn_table);
+	}
+}
+
+/*!
+ * \brief Check to see if a CDR needs to be answered based on its Party A.
+ * Note that this is safe to call as much as you want - we won't answer twice
+ */
+static void cdr_object_check_party_a_answer(struct cdr_object *cdr) {
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (cdr->party_a.snapshot->state == AST_STATE_UP && ast_tvzero(cdr->answer)) {
+		cdr->answer = ast_tvnow();
+		CDR_DEBUG(mod_cfg, "%p - Set answered time to %ld.%ld\n", cdr,
+			cdr->answer.tv_sec,
+			cdr->answer.tv_usec);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Set a variable on a CDR object
+ *
+ * \param headp The header pointer to the variable to set
+ * \param name The name of the variable
+ * \param value The value of the variable
+ *
+ * CDRs that are in a hungup state cannot have their variables set.
+ */
+static void set_variable(struct varshead *headp, const char *name, const char *value)
+{
+	struct ast_var_t *newvariable;
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
+		if (!strcasecmp(ast_var_name(newvariable), name)) {
+			AST_LIST_REMOVE_CURRENT(entries);
+			ast_var_delete(newvariable);
+			break;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	if (value) {
+		newvariable = ast_var_assign(name, value);
+		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+	}
+}
+
+/* \brief Set Caller ID information on a CDR */
+static void cdr_object_update_cid(struct cdr_object_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot)
+{
+	if (!old_snapshot->snapshot) {
+		set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid);
+		set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr);
+		set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr);
+		return;
+	}
+	if (!strcmp(old_snapshot->snapshot->caller_dnid, new_snapshot->caller_dnid)) {
+		set_variable(&old_snapshot->variables, "dnid", new_snapshot->caller_dnid);
+	}
+	if (!strcmp(old_snapshot->snapshot->caller_subaddr, new_snapshot->caller_subaddr)) {
+		set_variable(&old_snapshot->variables, "callingsubaddr", new_snapshot->caller_subaddr);
+	}
+	if (!strcmp(old_snapshot->snapshot->dialed_subaddr, new_snapshot->dialed_subaddr)) {
+		set_variable(&old_snapshot->variables, "calledsubaddr", new_snapshot->dialed_subaddr);
+	}
+}
+
+/*!
+ * \brief Swap an old \ref cdr_object_snapshot's \ref ast_channel_snapshot for
+ * a new \ref ast_channel_snapshot
+ * \param old_snapshot The old \ref cdr_object_snapshot
+ * \param new_snapshot The new \ref ast_channel_snapshot for old_snapshot
+ */
+static void cdr_object_swap_snapshot(struct cdr_object_snapshot *old_snapshot,
+		struct ast_channel_snapshot *new_snapshot)
+{
+	cdr_object_update_cid(old_snapshot, new_snapshot);
+	if (old_snapshot->snapshot) {
+		ao2_t_ref(old_snapshot->snapshot, -1, "Drop ref for swap");
+	}
+	ao2_t_ref(new_snapshot, +1, "Bump ref for swap");
+	old_snapshot->snapshot = new_snapshot;
+}
+
+/* BASE METHOD IMPLEMENTATIONS */
+
+static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	ast_assert(strcmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
+	cdr_object_swap_snapshot(&cdr->party_a, snapshot);
+
+	/* When Party A is originated to an application and the application exits, the stack
+	 * will attempt to clear the application and restore the dummy originate application
+	 * of "AppDialX". Prevent that, and any other application changes we might not want
+	 * here.
+	 */
+	if (!ast_strlen_zero(snapshot->appl) && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))) {
+		ast_string_field_set(cdr, appl, snapshot->appl);
+		ast_string_field_set(cdr, data, snapshot->data);
+	}
+
+	ast_string_field_set(cdr, linkedid, snapshot->linkedid);
+	cdr_object_check_party_a_answer(cdr);
+	cdr_object_check_party_a_hangup(cdr);
+
+	return 0;
+}
+
+static int base_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	/* In general, most things shouldn't get a bridge leave */
+	ast_assert(0);
+	return 1;
+}
+
+static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status)
+{
+	/* In general, most things shouldn't get a dial end. */
+	ast_assert(0);
+	return 0;
+}
+
+/* SINGLE STATE */
+
+static void single_state_init_function(struct cdr_object *cdr) {
+	cdr->start = ast_tvnow();
+	cdr_object_check_party_a_answer(cdr);
+}
+
+static void single_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	/* This should never happen! */
+	ast_assert(cdr->party_b.snapshot == NULL);
+	ast_assert(0);
+	return;
+}
+
+static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (caller && !strcmp(cdr->party_a.snapshot->name, caller->name)) {
+		cdr_object_swap_snapshot(&cdr->party_a, caller);
+		CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
+				cdr->party_a.snapshot->name);
+		cdr_object_swap_snapshot(&cdr->party_b, peer);
+		CDR_DEBUG(mod_cfg, "%p - Updated Party B %s snapshot\n", cdr,
+				cdr->party_b.snapshot->name);
+	} else if (!strcmp(cdr->party_a.snapshot->name, peer->name)) {
+		/* We're the entity being dialed, i.e., outbound origination */
+		cdr_object_swap_snapshot(&cdr->party_a, peer);
+		CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
+				cdr->party_a.snapshot->name);
+	}
+
+	cdr_object_transition_state(cdr, &dial_state_fn_table);
+	return 0;
+}
+
+/*!
+ * \brief Handle a comparison between our \ref cdr_object and a \ref cdr_object
+ * already in the bridge while in the Single state. The goal of this is to find
+ * a Party B for our CDR.
+ *
+ * \param cdr Our \ref cdr_object in the Single state
+ * \param cand_cdr The \ref cdr_object already in the Bridge state
+ *
+ * \retval 0 The cand_cdr had a Party A or Party B that we could use as our
+ * Party B
+ * \retval 1 No party in the cand_cdr could be used as our Party B
+ */
+static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
+		struct cdr_object *cand_cdr)
+{
+	struct cdr_object_snapshot *party_a;
+
+	/* Try the candidate CDR's Party A first */
+	party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
+	if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+		cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
+		if (!cand_cdr->party_b.snapshot) {
+			/* We just stole them - finalize their CDR. Note that this won't
+			 * transition their state, it just sets the end time and the
+			 * disposition - if we need to re-activate them later, we can.
+			 */
+			cdr_object_finalize(cand_cdr);
+		}
+		return 0;
+	}
+
+	/* Try their Party B */
+	if (!cand_cdr->party_b.snapshot) {
+		return 1;
+	}
+	party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
+	if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+		cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	struct ao2_iterator *it_cdrs;
+	struct cdr_object *cand_cdr_master;
+	char *bridge_id = ast_strdupa(bridge->uniqueid);
+	int success = 1;
+
+	ast_string_field_set(cdr, bridge, bridge->uniqueid);
+
+	/* Get parties in the bridge */
+	it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+			cdr_object_bridge_cmp_fn, bridge_id);
+	if (!it_cdrs) {
+		/* No one in the bridge yet! */
+		cdr_object_transition_state(cdr, &bridge_state_fn_table);
+		return 0;
+	}
+
+	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+		struct cdr_object *cand_cdr;
+
+		ao2_lock(cand_cdr_master);
+		for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
+			/* Skip any records that are not in a bridge or in this bridge.
+			 * I'm not sure how that would happen, but it pays to be careful. */
+			if (cand_cdr->fn_table != &bridge_state_fn_table ||
+					strcmp(cdr->bridge, cand_cdr->bridge)) {
+				continue;
+			}
+
+			if (single_state_bridge_enter_comparison(cdr, cand_cdr)) {
+				continue;
+			}
+			/* We successfully got a party B - break out */
+			success = 0;
+			break;
+		}
+		ao2_unlock(cand_cdr_master);
+		ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference");
+	}
+	ao2_iterator_destroy(it_cdrs);
+
+	/* We always transition state, even if we didn't get a peer */
+	cdr_object_transition_state(cdr, &bridge_state_fn_table);
+
+	/* Success implies that we have a Party B */
+	return success;
+}
+
+/* DIAL STATE */
+
+static void dial_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	ast_assert(snapshot != NULL);
+
+	if (!cdr->party_b.snapshot || strcmp(cdr->party_b.snapshot->name, snapshot->name)) {
+		return;
+	}
+	cdr_object_swap_snapshot(&cdr->party_b, snapshot);
+
+	/* If party B hangs up, finalize this CDR */
+	if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) {
+		cdr_object_transition_state(cdr, &finalized_state_fn_table);
+	}
+}
+
+static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
+	/* Don't process a begin dial here. A party A already in the dial state will
+	 * who receives a dial begin for something else will be handled by the
+	 * message router callback and will add a new CDR for the party A */
+	return 1;
+}
+
+/*! \internal \brief Convert a dial status to a CDR disposition */
+static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_status)
+{
+	RAII_VAR(struct module_config *, mod_cfg,
+		ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (!strcmp(dial_status, "ANSWER")) {
+		return AST_CDR_ANSWERED;
+	} else if (!strcmp(dial_status, "BUSY")) {
+		return AST_CDR_BUSY;
+	} else if (!strcmp(dial_status, "CANCEL") || !strcmp(dial_status, "NOANSWER")) {
+		return AST_CDR_NOANSWER;
+	} else if (!strcmp(dial_status, "CONGESTION")) {
+		if (!ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION)) {
+			return AST_CDR_FAILED;
+		} else {
+			return AST_CDR_CONGESTION;
+		}
+	} else if (!strcmp(dial_status, "FAILED")) {
+		return AST_CDR_FAILED;
+	}
+	return AST_CDR_FAILED;
+}
+
+static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status)
+{
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct ast_channel_snapshot *party_a;
+
+	if (caller) {
+		party_a = caller;
+	} else {
+		party_a = peer;
+	}
+	ast_assert(!strcmp(cdr->party_a.snapshot->name, party_a->name));
+	cdr_object_swap_snapshot(&cdr->party_a, party_a);
+
+	if (cdr->party_b.snapshot) {
+		if (strcmp(cdr->party_b.snapshot->name, peer->name)) {
+			/* Not the status for this CDR - defer back to the message router */
+			return 1;
+		}
+		cdr_object_swap_snapshot(&cdr->party_b, peer);
+	}
+
+	/* Set the disposition based on the dial string. */
+	cdr->disposition = dial_status_to_disposition(dial_status);
+	if (cdr->disposition == AST_CDR_ANSWERED) {
+		/* Switch to dial pending to wait and see what the caller does */
+		cdr_object_transition_state(cdr, &dialed_pending_state_fn_table);
+	} else {
+		cdr_object_transition_state(cdr, &finalized_state_fn_table);
+	}
+
+	return 0;
+}
+
+static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	struct ao2_iterator *it_cdrs;
+	char *bridge_id = ast_strdupa(bridge->uniqueid);
+	struct cdr_object *cand_cdr_master;
+	int success = 1;
+
+	ast_string_field_set(cdr, bridge, bridge->uniqueid);
+
+	/* Get parties in the bridge */
+	it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+			cdr_object_bridge_cmp_fn, bridge_id);
+	if (!it_cdrs) {
+		/* No one in the bridge yet! */
+		cdr_object_transition_state(cdr, &bridge_state_fn_table);
+		return 0;
+	}
+
+	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+		struct cdr_object *cand_cdr;
+
+		ao2_lock(cand_cdr_master);
+		for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
+			/* Skip any records that are not in a bridge or in this bridge.
+			 * I'm not sure how that would happen, but it pays to be careful. */
+			if (cand_cdr->fn_table != &bridge_state_fn_table ||
+					strcmp(cdr->bridge, cand_cdr->bridge)) {
+				continue;
+			}
+
+			/* Skip any records that aren't our Party B */
+			if (strcmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) {
+				continue;
+			}
+
+			cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
+			/* If they have a Party B, they joined up with someone else as their
+			 * Party A. Don't finalize them as they're active. Otherwise, we
+			 * have stolen them so they need to be finalized.
+			 */
+			if (!cand_cdr->party_b.snapshot) {
+				cdr_object_finalize(cand_cdr);
+			}
+			success = 0;
+			break;
+		}
+		ao2_unlock(cand_cdr_master);
+		ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference");
+	}
+	ao2_iterator_destroy(it_cdrs);
+
+	/* We always transition state, even if we didn't get a peer */
+	cdr_object_transition_state(cdr, &bridge_state_fn_table);
+
+	/* Success implies that we have a Party B */
+	return success;
+}
+
+/* DIALED PENDING STATE */
+
+static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	/* If we get a CEP change, we're executing dialplan. If we have a Party B
+	 * that means we need a new CDR; otherwise, switch us over to single.
+	 */
+	if (strcmp(snapshot->context, cdr->party_a.snapshot->context)
+		|| strcmp(snapshot->exten, cdr->party_a.snapshot->exten)
+		|| snapshot->priority != cdr->party_a.snapshot->priority
+		|| strcmp(snapshot->appl, cdr->party_a.snapshot->appl)) {
+		if (cdr->party_b.snapshot) {
+			cdr_object_transition_state(cdr, &finalized_state_fn_table);
+			cdr->fn_table->process_party_a(cdr, snapshot);
+			return 1;
+		} else {
+			cdr_object_transition_state(cdr, &single_state_fn_table);
+			cdr->fn_table->process_party_a(cdr, snapshot);
+			return 0;
+		}
+	}
+	base_process_party_a(cdr, snapshot);
+	return 0;
+}
+
+static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	cdr_object_transition_state(cdr, &dial_state_fn_table);
+	return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
+}
+
+static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
+	struct cdr_object *new_cdr;
+
+	cdr_object_transition_state(cdr, &finalized_state_fn_table);
+	new_cdr = cdr_object_create_and_append(cdr);
+	cdr_object_transition_state(cdr, &single_state_fn_table);
+	return new_cdr->fn_table->process_dial_begin(cdr, caller, peer);
+}
+
+/* BRIDGE STATE */
+
+static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	if (!cdr->party_b.snapshot || strcmp(cdr->party_b.snapshot->name, snapshot->name)) {
+		return;
+	}
+	cdr_object_swap_snapshot(&cdr->party_b, snapshot);
+
+	/* If party B hangs up, finalize this CDR */
+	if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_ZOMBIE)) {
+		cdr_object_transition_state(cdr, &finalized_state_fn_table);
+	}
+}
+
+static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	if (strcmp(cdr->bridge, bridge->uniqueid)) {
+		return 1;
+	}
+	if (strcmp(cdr->party_a.snapshot->name, channel->name)
+			&& cdr->party_b.snapshot
+			&& strcmp(cdr->party_b.snapshot->name, channel->name)) {
+		return 1;
+	}
+	cdr_object_transition_state(cdr, &finalized_state_fn_table);
+
+	return 0;
+}
+
+/* PENDING STATE */
+
+static void pending_state_init_function(struct cdr_object *cdr)
+{
+	ast_cdr_set_property(cdr->name, AST_CDR_FLAG_DISABLE);
+}
+
+static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	if (ast_test_flag(&snapshot->flags, AST_FLAG_ZOMBIE)) {
+		return 0;
+	}
+
+	/* Ignore if we don't get a CEP change */
+	if (!strcmp(snapshot->context, cdr->party_a.snapshot->context)
+		&& !strcmp(snapshot->exten, cdr->party_a.snapshot->exten)
+		&& snapshot->priority == cdr->party_a.snapshot->priority) {
+		return 0;
+	}
+
+	cdr_object_transition_state(cdr, &single_state_fn_table);
+	ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+	cdr->fn_table->process_party_a(cdr, snapshot);
+	return 0;
+}
+
+static int pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
+	cdr_object_transition_state(cdr, &single_state_fn_table);
+	ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+	return cdr->fn_table->process_dial_begin(cdr, caller, peer);
+}
+
+static int pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	cdr_object_transition_state(cdr, &single_state_fn_table);
+	ast_cdr_clear_property(cdr->name, AST_CDR_FLAG_DISABLE);
+	return cdr->fn_table->process_bridge_enter(cdr, bridge, channel);
+}
+
+/* FINALIZED STATE */
+
+static void finalized_state_init_function(struct cdr_object *cdr)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (!ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+		return;
+	}
+
+	cdr_object_finalize(cdr);
+}
+
+static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)) {
+		cdr_object_finalize(cdr);
+	}
+
+	/* Indicate that, if possible, we should get a new CDR */
+	return 1;
+}
+
+/* TOPIC ROUTER CALLBACKS */
+
+/*!
+ * \brief Handler for Stasis-Core dial messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message
+ */
+static void handle_dial_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	RAII_VAR(struct cdr_object *, cdr_caller, NULL, ao2_cleanup);
+	RAII_VAR(struct cdr_object *, cdr_peer, NULL, ao2_cleanup);
+	struct cdr_object *cdr;
+	struct ast_multi_channel_blob *payload = stasis_message_data(message);
+	struct ast_channel_snapshot *caller;
+	struct ast_channel_snapshot *peer;
+	struct cdr_object_snapshot *party_a;
+	struct cdr_object_snapshot *party_b;
+	struct cdr_object *it_cdr;
+	struct ast_json *dial_status_blob;
+	const char *dial_status = NULL;
+	int res = 1;
+
+	CDR_DEBUG(mod_cfg, "Dial message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+	ast_assert(payload != NULL);
+
+	caller = ast_multi_channel_blob_get_channel(payload, "caller");
+	peer = ast_multi_channel_blob_get_channel(payload, "peer");
+	if (!peer && !caller) {
+		return;
+	}
+	dial_status_blob = ast_json_object_get(ast_multi_channel_blob_get_json(payload), "dialstatus");
+	if (dial_status_blob) {
+		dial_status = ast_json_string_get(dial_status_blob);
+	}
+
+	/* Figure out who is running this show */
+	if (caller) {
+		cdr_caller = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY);
+	}
+	if (peer) {
+		cdr_peer = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY);
+	}
+	if (cdr_caller && cdr_peer) {
+		party_a = cdr_object_pick_party_a(&cdr_caller->party_a, &cdr_peer->party_a);
+		if (!strcmp(party_a->snapshot->name, cdr_caller->party_a.snapshot->name)) {
+			cdr = cdr_caller;
+			party_b = &cdr_peer->party_a;
+		} else {
+			cdr = cdr_peer;
+			party_b = &cdr_caller->party_a;
+		}
+	} else if (cdr_caller) {
+		cdr = cdr_caller;
+		party_a = &cdr_caller->party_a;
+		party_b = NULL;
+	} else if (cdr_peer) {
+		cdr = cdr_peer;
+		party_a = NULL;
+		party_b = &cdr_peer->party_a;
+	} else {
+		return;
+	}
+
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (ast_strlen_zero(dial_status)) {
+			if (!it_cdr->fn_table->process_dial_begin) {
+				continue;
+			}
+			CDR_DEBUG(mod_cfg, "%p - Processing Dial Begin message for channel %s, peer %s\n",
+					cdr,
+					party_a ? party_a->snapshot->name : "(none)",
+					party_b ? party_b->snapshot->name : "(none)");
+			res &= it_cdr->fn_table->process_dial_begin(it_cdr,
+					party_a ? party_a->snapshot : NULL,
+					party_b ? party_b->snapshot : NULL);
+		} else {
+			if (!it_cdr->fn_table->process_dial_end) {
+				continue;
+			}
+			CDR_DEBUG(mod_cfg, "%p - Processing Dial End message for channel %s, peer %s\n",
+					cdr,
+					party_a ? party_a->snapshot->name : "(none)",
+					party_b ? party_b->snapshot->name : "(none)");
+			it_cdr->fn_table->process_dial_end(it_cdr,
+					party_a ? party_a->snapshot : NULL,
+					party_b ? party_b->snapshot : NULL,
+					dial_status);
+		}
+	}
+
+	/* If no CDR handled a dial begin message, make a new one */
+	if (res && ast_strlen_zero(dial_status)) {
+		struct cdr_object *new_cdr;
+
+		new_cdr = cdr_object_create_and_append(cdr);
+		if (!new_cdr) {
+			return;
+		}
+		new_cdr->fn_table->process_dial_begin(new_cdr,
+				party_a ? party_a->snapshot : NULL,
+				party_b ? party_b->snapshot : NULL);
+	}
+	ao2_unlock(cdr);
+}
+
+static int cdr_object_finalize_party_b(void *obj, void *arg, int flags)
+{
+	struct cdr_object *cdr = obj;
+	struct ast_channel_snapshot *party_b = arg;
+	struct cdr_object *it_cdr;
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->party_b.snapshot && !strcmp(it_cdr->party_b.snapshot->name, party_b->name)) {
+			/* Don't transition to the finalized state - let the Party A do
+			 * that when its ready
+			 */
+			cdr_object_finalize(it_cdr);
+		}
+	}
+	return 0;
+}
+
+static int cdr_object_update_party_b(void *obj, void *arg, int flags)
+{
+	struct cdr_object *cdr = obj;
+	struct ast_channel_snapshot *party_b = arg;
+	struct cdr_object *it_cdr;
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (!it_cdr->fn_table->process_party_b) {
+			continue;
+		}
+		if (it_cdr->party_b.snapshot && !strcmp(it_cdr->party_b.snapshot->name, party_b->name)) {
+			it_cdr->fn_table->process_party_b(it_cdr, party_b);
+		}
+	}
+	return 0;
+}
+
+/*! \internal \brief Filter channel snapshots by technology */
+static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
+{
+	if (!strncmp(snapshot->name, "CBAnn", 5) ||
+		!strncmp(snapshot->name, "CBRec", 5)) {
+		return 1;
+	}
+	return 0;
+}
+
+/*! \internal \brief Filter a channel cache update */
+static int filter_channel_cache_message(struct ast_channel_snapshot *old_snapshot,
+		struct ast_channel_snapshot *new_snapshot)
+{
+	int ret = 0;
+
+	/* Drop cache updates from certain channel technologies */
+	if (old_snapshot) {
+		ret |= filter_channel_snapshot(old_snapshot);
+	}
+	if (new_snapshot) {
+		ret |= filter_channel_snapshot(new_snapshot);
+	}
+
+	return ret;
+}
+
+/*! \brief Determine if we need to add a new CDR based on snapshots */
+static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
+		struct ast_channel_snapshot *new_snapshot)
+{
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (!new_snapshot) {
+		return 0;
+	}
+
+	if (ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE)) {
+		return 0;
+	}
+
+	/* Auto-fall through will increment the priority but have no application */
+	if (ast_strlen_zero(new_snapshot->appl)) {
+		return 0;
+	}
+
+	if (old_snapshot && !strcmp(old_snapshot->context, new_snapshot->context)
+			&& !strcmp(old_snapshot->exten, new_snapshot->exten)
+			&& old_snapshot->priority == new_snapshot->priority
+			&& !(strcmp(old_snapshot->appl, new_snapshot->appl))) {
+		return 0;
+	}
+
+	return 1;
+}
+
+/*!
+ * \brief Handler for Stasis-Core channel cache update messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message
+ */
+static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct stasis_cache_update *update = stasis_message_data(message);
+	struct ast_channel_snapshot *old_snapshot;
+	struct ast_channel_snapshot *new_snapshot;
+	const char *name;
+	struct cdr_object *it_cdr;
+
+	ast_assert(update != NULL);
+	if (ast_channel_snapshot_type() != update->type) {
+		return;
+	}
+
+	old_snapshot = stasis_message_data(update->old_snapshot);
+	new_snapshot = stasis_message_data(update->new_snapshot);
+	name = new_snapshot ? new_snapshot->name : old_snapshot->name;
+
+	if (filter_channel_cache_message(old_snapshot, new_snapshot)) {
+		return;
+	}
+
+	CDR_DEBUG(mod_cfg, "Channel Update message for %s: %u.%08u\n",
+			name,
+			(unsigned int)stasis_message_timestamp(message)->tv_sec,
+			(unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+	if (new_snapshot && !old_snapshot) {
+		cdr = cdr_object_alloc(new_snapshot);
+		if (!cdr) {
+			return;
+		}
+		ao2_link(active_cdrs_by_channel, cdr);
+	}
+
+	/* Handle Party A */
+	if (!cdr) {
+		cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
+	}
+	if (!cdr) {
+		ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name);
+	} else {
+		ao2_lock(cdr);
+		if (new_snapshot) {
+			int all_reject = 1;
+			for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+				if (!it_cdr->fn_table->process_party_a) {
+					continue;
+				}
+				CDR_DEBUG(mod_cfg, "%p - Processing new channel snapshot %s\n", it_cdr, new_snapshot->name);
+				all_reject &= it_cdr->fn_table->process_party_a(it_cdr, new_snapshot);
+			}
+			if (all_reject && check_new_cdr_needed(old_snapshot, new_snapshot)) {
+				/* We're not hung up and we have a new snapshot - we need a new CDR */
+				struct cdr_object *new_cdr;
+				new_cdr = cdr_object_create_and_append(cdr);
+				new_cdr->fn_table->process_party_a(new_cdr, new_snapshot);
+			}
+		} else {
+			CDR_DEBUG(mod_cfg, "%p - Beginning finalize/dispatch for %s\n", cdr, old_snapshot->name);
+			for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+				cdr_object_finalize(it_cdr);
+			}
+			cdr_object_dispatch(cdr);
+			ao2_unlink(active_cdrs_by_channel, cdr);
+		}
+		ao2_unlock(cdr);
+	}
+
+	/* Handle Party B */
+	if (new_snapshot) {
+		ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_update_party_b,
+			new_snapshot);
+	} else {
+		ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_finalize_party_b,
+			old_snapshot);
+	}
+
+}
+
+struct bridge_leave_data {
+	struct ast_bridge_snapshot *bridge;
+	struct ast_channel_snapshot *channel;
 };
 
-static AST_RWLIST_HEAD_STATIC(be_list, ast_cdr_beitem);
+/*! \brief Callback used to notify CDRs of a Party B leaving the bridge */
+static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, int flags)
+{
+	struct cdr_object *cdr = obj;
+	struct bridge_leave_data *leave_data = arg;
+	struct cdr_object *it_cdr;
 
-struct ast_cdr_batch_item {
-	struct ast_cdr *cdr;
-	struct ast_cdr_batch_item *next;
+	if (strcmp(cdr->bridge, leave_data->bridge->uniqueid)) {
+		return 0;
+	}
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table != &bridge_state_fn_table) {
+			continue;
+		}
+		if (!it_cdr->party_b.snapshot) {
+			continue;
+		}
+		if (strcmp(it_cdr->party_b.snapshot->name, leave_data->channel->name)) {
+			continue;
+		}
+		if (!it_cdr->fn_table->process_bridge_leave(it_cdr, leave_data->bridge, leave_data->channel)) {
+			/* Update the end times for this CDR. We don't want to actually
+			 * finalize it, as the Party A will eventually need to leave, which
+			 * will switch the records to pending bridged.
+			 */
+			cdr_object_finalize(it_cdr);
+		}
+	}
+	return 0;
+}
+
+/*! \brief Filter bridge messages based on bridge technology */
+static int filter_bridge_messages(struct ast_bridge_snapshot *bridge)
+{
+	/* Ignore holding bridge technology messages. We treat this simply as an application
+	 * that a channel enters into.
+	 */
+	if (!strcmp(bridge->technology, "holding_bridge")) {
+		return 1;
+	}
+	return 0;
+}
+
+/*!
+ * \brief Handler for when a channel leaves a bridge
+ * \param bridge The \ref ast_bridge_snapshot representing the bridge
+ * \param channel The \ref ast_channel_snapshot representing the channel
+ */
+static void handle_bridge_leave_message(void *data, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *message)
+{
+	struct ast_bridge_blob *update = stasis_message_data(message);
+	struct ast_bridge_snapshot *bridge = update->bridge;
+	struct ast_channel_snapshot *channel = update->channel;
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+			ao2_cleanup);
+	struct cdr_object *it_cdr;
+	struct cdr_object *pending_cdr;
+	struct bridge_leave_data leave_data = {
+		.bridge = bridge,
+		.channel = channel,
+	};
+	int left_bridge = 0;
+
+	if (filter_bridge_messages(bridge)) {
+		return;
+	}
+
+	CDR_DEBUG(mod_cfg, "Bridge Leave message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+	if (!cdr) {
+		ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+		return;
+	}
+
+	/* Party A */
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (!it_cdr->fn_table->process_bridge_leave) {
+			continue;
+		}
+		CDR_DEBUG(mod_cfg, "%p - Processing Bridge Leave for %s\n",
+				it_cdr, channel->name);
+		if (!it_cdr->fn_table->process_bridge_leave(it_cdr, bridge, channel)) {
+			ast_string_field_set(it_cdr, bridge, "");
+			left_bridge = 1;
+		}
+	}
+	if (!left_bridge) {
+		ao2_unlock(cdr);
+		return;
+	}
+
+	ao2_unlink(active_cdrs_by_bridge, cdr);
+
+	/* Create a new pending record. If the channel decides to do something else,
+	 * the pending record will handle it - otherwise, if gets dropped.
+	 */
+	pending_cdr = cdr_object_create_and_append(cdr);
+	cdr_object_transition_state(pending_cdr, &bridged_pending_state_fn_table);
+	ao2_unlock(cdr);
+
+	/* Party B */
+	ao2_callback(active_cdrs_by_bridge, OBJ_NODATA,
+			cdr_object_party_b_left_bridge_cb,
+			&leave_data);
+}
+
+struct bridge_candidate {
+	struct cdr_object *cdr;					/*!< The actual CDR this candidate belongs to, either as A or B */
+	struct cdr_object_snapshot candidate;	/*!< The candidate for a new pairing */
 };
 
-static struct ast_cdr_batch {
-	int size;
-	struct ast_cdr_batch_item *head;
-	struct ast_cdr_batch_item *tail;
-} *batch = NULL;
+/*! \internal
+ * \brief Comparison function for \ref bridge_candidate objects
+ */
+static int bridge_candidate_cmp_fn(void *obj, void *arg, int flags)
+{
+	struct bridge_candidate *left = obj;
+	struct bridge_candidate *right = arg;
+	const char *match = (flags & OBJ_KEY) ? arg : right->candidate.snapshot->name;
+	return strcasecmp(left->candidate.snapshot->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
+}
+
+/*! \internal
+ * \brief Hash function for \ref bridge_candidate objects
+ */
+static int bridge_candidate_hash_fn(const void *obj, const int flags)
+{
+	const struct bridge_candidate *bc = obj;
+	const char *id = (flags & OBJ_KEY) ? obj : bc->candidate.snapshot->name;
+	return ast_str_case_hash(id);
+}
+
+/*! \brief \ref bridge_candidate Destructor */
+static void bridge_candidate_dtor(void *obj)
+{
+	struct bridge_candidate *bcand = obj;
+	ao2_cleanup(bcand->cdr);
+	ao2_cleanup(bcand->candidate.snapshot);
+	free_variables(&bcand->candidate.variables);
+}
 
+/*!
+ * \brief \ref bridge_candidate Constructor
+ * \param cdr The \ref cdr_object that is a candidate for being compared to in
+ *  a bridge operation
+ * \param candidate The \ref cdr_object_snapshot candidate snapshot in the CDR
+ *  that should be used during the operaton
+ */
+static struct bridge_candidate *bridge_candidate_alloc(struct cdr_object *cdr, struct cdr_object_snapshot *candidate)
+{
+	struct bridge_candidate *bcand;
 
-static int cdr_sequence =  0;
+	bcand = ao2_alloc(sizeof(*bcand), bridge_candidate_dtor);
+	if (!bcand) {
+		return NULL;
+	}
+	bcand->cdr = cdr;
+	ao2_ref(bcand->cdr, +1);
+	bcand->candidate.flags = candidate->flags;
+	strcpy(bcand->candidate.userfield, candidate->userfield);
+	bcand->candidate.snapshot = candidate->snapshot;
+	ao2_ref(bcand->candidate.snapshot, +1);
+	copy_variables(&bcand->candidate.variables, &candidate->variables);
 
-static int cdr_seq_inc(struct ast_cdr *cdr);
+	return bcand;
+}
 
-static struct ast_sched_context *sched;
-static int cdr_sched = -1;
-static pthread_t cdr_thread = AST_PTHREADT_NULL;
+/*!
+ * \internal \brief Build and add bridge candidates based on a CDR
+ * \param bridge_id The ID of the bridge we need candidates for
+ * \param candidates The container of \ref bridge_candidate objects
+ * \param cdr The \ref cdr_object that is our candidate
+ * \param party_a Non-zero if we should look at the Party A channel; 0 if Party B
+ */
+static void add_candidate_for_bridge(const char *bridge_id,
+		struct ao2_container *candidates,
+		struct cdr_object *cdr,
+		int party_a)
+{
+	struct cdr_object *it_cdr;
 
-static int enabled;
-static const int ENABLED_DEFAULT = 1;
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		struct cdr_object_snapshot *party_snapshot;
+		RAII_VAR(struct bridge_candidate *, bcand, NULL, ao2_cleanup);
 
-static int batchmode;
-static const int BATCHMODE_DEFAULT = 0;
+		party_snapshot = party_a ? &it_cdr->party_a : &it_cdr->party_b;
 
-static int unanswered;
-static const int UNANSWERED_DEFAULT = 0;
+		if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge_id, it_cdr->bridge)) {
+			continue;
+		}
 
-static int congestion;
-static const int CONGESTION_DEFAULT = 0;
+		if (!party_snapshot->snapshot) {
+			continue;
+		}
 
-static int batchsize;
-static const int BATCH_SIZE_DEFAULT = 100;
+		/* Don't add a party twice */
+		bcand = ao2_find(candidates, party_snapshot->snapshot->name, OBJ_KEY);
+		if (bcand) {
+			continue;
+		}
 
-static int batchtime;
-static const int BATCH_TIME_DEFAULT = 300;
+		bcand = bridge_candidate_alloc(it_cdr, party_snapshot);
+		if (bcand) {
+			ao2_link(candidates, bcand);
+		}
+	}
+}
 
-static int batchscheduleronly;
-static const int BATCH_SCHEDULER_ONLY_DEFAULT = 0;
+/*!
+ * \brief Create new \ref bridge_candidate objects for each party currently
+ * in a bridge
+ * \param bridge The \param ast_bridge_snapshot for the bridge we're processing
+ *
+ * Note that we use two passes here instead of one so that we only create a
+ * candidate for a party B if they are never a party A in the bridge. Otherwise,
+ * we don't care about them.
+ */
+static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snapshot *bridge)
+{
+	struct ao2_container *candidates = ao2_container_alloc(51, bridge_candidate_hash_fn, bridge_candidate_cmp_fn);
+	char *bridge_id = ast_strdupa(bridge->uniqueid);
+	struct ao2_iterator *it_cdrs;
+	struct cdr_object *cand_cdr_master;
 
-static int batchsafeshutdown;
-static const int BATCH_SAFE_SHUTDOWN_DEFAULT = 1;
+	if (!candidates) {
+		return NULL;
+	}
 
-AST_MUTEX_DEFINE_STATIC(cdr_sched_lock);
+	/* For each CDR that has a record in the bridge, get their Party A and
+	 * make them a candidate. Note that we do this in two passes as opposed to one so
+	 * that we give preference CDRs where the channel is Party A */
+	it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+			cdr_object_bridge_cmp_fn, bridge_id);
+	if (!it_cdrs) {
+		/* No one in the bridge yet! */
+		ao2_cleanup(candidates);
+		return NULL;
+	}
+	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+		SCOPED_AO2LOCK(lock, cand_cdr_master);
+		add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 1);
+	}
+	ao2_iterator_destroy(it_cdrs);
 
-AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
+	/* For each CDR that has a record in the bridge, get their Party B and
+	 * make them a candidate. */
+	it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+			cdr_object_bridge_cmp_fn, bridge_id);
+	if (!it_cdrs) {
+		/* Now it's just an error. */
+		ao2_cleanup(candidates);
+		return NULL;
+	}
+	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+		SCOPED_AO2LOCK(lock, cand_cdr_master);
+		add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 0);
+	}
+	ao2_iterator_destroy(it_cdrs);
 
-/* these are used to wake up the CDR thread when there's work to do */
-AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
-static ast_cond_t cdr_pending_cond;
+	return candidates;
+}
+
+/*!
+ * \internal \brief Create a new CDR, append it to an existing CDR, and update its snapshots
+ * \note The new CDR will be automatically transitioned to the bridge state
+ */
+static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
+		const char *bridge_id,
+		struct cdr_object_snapshot *party_b)
+{
+	struct cdr_object *new_cdr;
+
+	new_cdr = cdr_object_create_and_append(cdr);
+	cdr_object_snapshot_copy(&new_cdr->party_b, party_b);
+	cdr_object_check_party_a_answer(new_cdr);
+	ast_string_field_set(new_cdr, bridge, cdr->bridge);
+	cdr_object_transition_state(new_cdr, &bridge_state_fn_table);
+}
+
+/*!
+ * \brief Process a single \ref bridge_candidate. Note that this is called as
+ * part of an \ref ao2_callback on an \ref ao2_container of \ref bridge_candidate
+ * objects previously created by \ref create_candidates_for_bridge.
+ *
+ * \param obj The \ref bridge_candidate being processed
+ * \param arg The \ref cdr_object that is being compared against the candidates
+ *
+ * The purpose of this function is to create the necessary CDR entries as a
+ * result of \ref cdr_object having entered the same bridge as the CDR
+ * represented by \ref bridge_candidate.
+ */
+static int bridge_candidate_process(void *obj, void *arg, int flags)
+{
+	struct bridge_candidate *bcand = obj;
+	struct cdr_object *cdr = arg;
+	struct cdr_object_snapshot *party_a;
+
+	/* If the candidate is us or someone we've taken on, pass on by */
+	if (!strcmp(cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)
+		|| (cdr->party_b.snapshot && !(strcmp(cdr->party_b.snapshot->name, bcand->candidate.snapshot->name)))) {
+		return 0;
+	}
+
+	party_a = cdr_object_pick_party_a(&cdr->party_a, &bcand->candidate);
+	/* We're party A - make a new CDR, append it to us, and set the candidate as
+	 * Party B */
+	if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+		bridge_candidate_add_to_cdr(cdr, cdr->bridge, &bcand->candidate);
+		return 0;
+	}
+
+	/* We're Party B. Check if the candidate is the CDR's Party A. If so, find out if we
+	 * can add ourselves directly as the Party B, or if we need a new CDR. */
+	if (!strcmp(bcand->cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)) {
+		if (bcand->cdr->party_b.snapshot
+				&& strcmp(bcand->cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
+			bridge_candidate_add_to_cdr(bcand->cdr, cdr->bridge, &cdr->party_a);
+		} else {
+			cdr_object_snapshot_copy(&bcand->cdr->party_b, &cdr->party_a);
+			/* It's possible that this joined at one point and was never chosen
+			 * as party A. Clear their end time, as it would be set in such a
+			 * case.
+			 */
+			memset(&bcand->cdr->end, 0, sizeof(bcand->cdr->end));
+		}
+	} else {
+		/* We are Party B to a candidate CDR's Party B. Since a candidate
+		 * CDR will only have a Party B represented here if that channel
+		 * was never a Party A in the bridge, we have to go looking for
+		 * that channel's primary CDR record.
+		 */
+		struct cdr_object *b_party = ao2_find(active_cdrs_by_channel, bcand->candidate.snapshot->name, OBJ_KEY);
+		if (!b_party) {
+			/* Holy cow - no CDR? */
+			b_party = cdr_object_alloc(bcand->candidate.snapshot);
+			cdr_object_snapshot_copy(&b_party->party_a, &bcand->candidate);
+			cdr_object_snapshot_copy(&b_party->party_b, &cdr->party_a);
+			cdr_object_check_party_a_answer(b_party);
+			ast_string_field_set(b_party, bridge, cdr->bridge);
+			cdr_object_transition_state(b_party, &bridge_state_fn_table);
+			ao2_link(active_cdrs_by_channel, b_party);
+		} else {
+			bridge_candidate_add_to_cdr(b_party, cdr->bridge, &cdr->party_a);
+		}
+		ao2_link(active_cdrs_by_bridge, b_party);
+		ao2_ref(b_party, -1);
+	}
+
+	return 0;
+}
 
-int check_cdr_enabled(void)
+/*!
+ * \brief Handle creating bridge pairings for the \ref cdr_object that just
+ * entered a bridge
+ * \param cdr The \ref cdr_object that just entered the bridge
+ * \param bridge The \ref ast_bridge_snapshot representing the bridge it just entered
+ */
+static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge)
 {
-	return enabled;
+	RAII_VAR(struct ao2_container *, candidates,
+			create_candidates_for_bridge(bridge),
+			ao2_cleanup);
+
+	if (!candidates) {
+		return;
+	}
+
+	ao2_callback(candidates, OBJ_NODATA,
+			bridge_candidate_process,
+			cdr);
+
+	return;
 }
 
 /*!
- * \brief Register a CDR driver. Each registered CDR driver generates a CDR
- * \retval 0 on success.
- * \retval -1 on error
+ * \brief Handler for Stasis-Core bridge enter messages
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message The message - hopefully a bridge one!
  */
+static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub,
+		struct stasis_topic *topic, struct stasis_message *message)
+{
+	struct ast_bridge_blob *update = stasis_message_data(message);
+	struct ast_bridge_snapshot *bridge = update->bridge;
+	struct ast_channel_snapshot *channel = update->channel;
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+			ao2_cleanup);
+	RAII_VAR(struct module_config *, mod_cfg,
+			ao2_global_obj_ref(module_configs), ao2_cleanup);
+	int res = 1;
+	struct cdr_object *it_cdr;
+	struct cdr_object *handled_cdr = NULL;
+
+	if (filter_bridge_messages(bridge)) {
+		return;
+	}
+
+	CDR_DEBUG(mod_cfg, "Bridge Enter message: %u.%08u\n", (unsigned int)stasis_message_timestamp(message)->tv_sec, (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+	if (!cdr) {
+		ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+		return;
+	}
+
+	ao2_lock(cdr);
+
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table->process_party_a) {
+			CDR_DEBUG(mod_cfg, "%p - Updating Party A %s snapshot\n", it_cdr,
+					channel->name);
+			it_cdr->fn_table->process_party_a(it_cdr, channel);
+		}
+
+		/* Notify all states that they have entered a bridge */
+		if (it_cdr->fn_table->process_bridge_enter) {
+			CDR_DEBUG(mod_cfg, "%p - Processing bridge enter for %s\n", it_cdr,
+					channel->name);
+			res &= it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
+			if (!res && !handled_cdr) {
+				handled_cdr = it_cdr;
+			}
+		}
+	}
+
+	if (res) {
+		/* We didn't win on any - end this CDR. If someone else comes in later
+		 * that is Party B to this CDR, it can re-activate this CDR.
+		 */
+		cdr_object_finalize(cdr);
+	}
+
+	/* Create the new matchings, but only for either:
+	 *  * The first CDR in the chain that handled it. This avoids issues with
+	 *    forked CDRs.
+	 *  * If no one handled it, the last CDR in the chain. This would occur if
+	 *    a CDR joined a bridge and it wasn't Party A for anyone. We still need
+	 *    to make pairings with everyone in the bridge.
+	 */
+	if (!handled_cdr) {
+		handled_cdr = cdr->last;
+	}
+	handle_bridge_pairings(handled_cdr, bridge);
+
+	ao2_link(active_cdrs_by_bridge, cdr);
+	ao2_unlock(cdr);
+}
+
+struct ast_cdr_config *ast_cdr_get_config(void)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	ao2_ref(mod_cfg->general, +1);
+	return mod_cfg->general;
+}
+
+void ast_cdr_set_config(struct ast_cdr_config *config)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	ao2_cleanup(mod_cfg->general);
+	mod_cfg->general = config;
+	ao2_ref(mod_cfg->general, +1);
+}
+
+int ast_cdr_is_enabled(void)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED);
+}
+
 int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
 {
-	struct ast_cdr_beitem *i = NULL;
+	struct cdr_beitem *i = NULL;
 
 	if (!name)
 		return -1;
@@ -175,10 +2469,9 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
 	return 0;
 }
 
-/*! unregister a CDR driver */
 void ast_cdr_unregister(const char *name)
 {
-	struct ast_cdr_beitem *i = NULL;
+	struct cdr_beitem *i = NULL;
 
 	AST_RWLIST_WRLOCK(&be_list);
 	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
@@ -187,82 +2480,51 @@ void ast_cdr_unregister(const char *name)
 			break;
 		}
 	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-	AST_RWLIST_UNLOCK(&be_list);
-
-	if (i) {
-		ast_verb(2, "Unregistered '%s' CDR backend\n", name);
-		ast_free(i);
-	}
-}
-
-int ast_cdr_isset_unanswered(void)
-{
-	return unanswered;
-}
-
-int ast_cdr_isset_congestion(void)
-{
-	return congestion;
-}
-
-struct ast_cdr *ast_cdr_dup_unique(struct ast_cdr *cdr)
-{
-	struct ast_cdr *newcdr = ast_cdr_dup(cdr);
-	if (!newcdr)
-		return NULL;
-
-	cdr_seq_inc(newcdr);
-	return newcdr;
-}
-
-struct ast_cdr *ast_cdr_dup_unique_swap(struct ast_cdr *cdr)
-{
-	struct ast_cdr *newcdr = ast_cdr_dup(cdr);
-	if (!newcdr)
-		return NULL;
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&be_list);
 
-	cdr_seq_inc(cdr);
-	return newcdr;
+	if (i) {
+		ast_verb(2, "Unregistered '%s' CDR backend\n", name);
+		ast_free(i);
+	}
 }
 
-/*! Duplicate a CDR record
-	\returns Pointer to new CDR record
-*/
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
 {
 	struct ast_cdr *newcdr;
 
-	if (!cdr) /* don't die if we get a null cdr pointer */
+	if (!cdr) {
 		return NULL;
+	}
 	newcdr = ast_cdr_alloc();
-	if (!newcdr)
+	if (!newcdr) {
 		return NULL;
+	}
 
 	memcpy(newcdr, cdr, sizeof(*newcdr));
-	/* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
 	memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
-	ast_cdr_copy_vars(newcdr, cdr);
+	copy_variables(&newcdr->varshead, &cdr->varshead);
 	newcdr->next = NULL;
 
 	return newcdr;
 }
 
-static const char *ast_cdr_getvar_internal(struct ast_cdr *cdr, const char *name, int recur)
+static const char *cdr_format_var_internal(struct ast_cdr *cdr, const char *name)
 {
-	if (ast_strlen_zero(name))
+	struct ast_var_t *variables;
+	struct varshead *headp = &cdr->varshead;
+
+	if (ast_strlen_zero(name)) {
 		return NULL;
+	}
 
-	for (; cdr; cdr = recur ? cdr->next : NULL) {
-		struct ast_var_t *variables;
-		struct varshead *headp = &cdr->varshead;
-		AST_LIST_TRAVERSE(headp, variables, entries) {
-			if (!strcasecmp(name, ast_var_name(variables)))
-				return ast_var_value(variables);
+	AST_LIST_TRAVERSE(headp, variables, entries) {
+		if (!strcasecmp(name, ast_var_name(variables))) {
+			return ast_var_value(variables);
 		}
 	}
 
-	return NULL;
+	return '\0';
 }
 
 static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize)
@@ -279,45 +2541,43 @@ static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufs
 	}
 }
 
-/*! CDR channel variable retrieval */
-void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int recur, int raw)
+void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char *workspace, int workspacelen, int raw)
 {
 	const char *fmt = "%Y-%m-%d %T";
 	const char *varbuf;
 
-	if (!cdr)  /* don't die if the cdr is null */
+	if (!cdr) {
 		return;
+	}
 
 	*ret = NULL;
-	/* special vars (the ones from the struct ast_cdr when requested by name)
-	   I'd almost say we should convert all the stringed vals to vars */
 
-	if (!strcasecmp(name, "clid"))
+	if (!strcasecmp(name, "clid")) {
 		ast_copy_string(workspace, cdr->clid, workspacelen);
-	else if (!strcasecmp(name, "src"))
+	} else if (!strcasecmp(name, "src")) {
 		ast_copy_string(workspace, cdr->src, workspacelen);
-	else if (!strcasecmp(name, "dst"))
+	} else if (!strcasecmp(name, "dst")) {
 		ast_copy_string(workspace, cdr->dst, workspacelen);
-	else if (!strcasecmp(name, "dcontext"))
+	} else if (!strcasecmp(name, "dcontext")) {
 		ast_copy_string(workspace, cdr->dcontext, workspacelen);
-	else if (!strcasecmp(name, "channel"))
+	} else if (!strcasecmp(name, "channel")) {
 		ast_copy_string(workspace, cdr->channel, workspacelen);
-	else if (!strcasecmp(name, "dstchannel"))
+	} else if (!strcasecmp(name, "dstchannel")) {
 		ast_copy_string(workspace, cdr->dstchannel, workspacelen);
-	else if (!strcasecmp(name, "lastapp"))
+	} else if (!strcasecmp(name, "lastapp")) {
 		ast_copy_string(workspace, cdr->lastapp, workspacelen);
-	else if (!strcasecmp(name, "lastdata"))
+	} else if (!strcasecmp(name, "lastdata")) {
 		ast_copy_string(workspace, cdr->lastdata, workspacelen);
-	else if (!strcasecmp(name, "start"))
+	} else if (!strcasecmp(name, "start")) {
 		cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen);
-	else if (!strcasecmp(name, "answer"))
+	} else if (!strcasecmp(name, "answer")) {
 		cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen);
-	else if (!strcasecmp(name, "end"))
+	} else if (!strcasecmp(name, "end")) {
 		cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen);
-	else if (!strcasecmp(name, "duration")) {
+	} else if (!strcasecmp(name, "duration")) {
 		snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
 	} else if (!strcasecmp(name, "billsec")) {
-		snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000);	
+		snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000);
 	} else if (!strcasecmp(name, "disposition")) {
 		if (raw) {
 			snprintf(workspace, workspacelen, "%ld", cdr->disposition);
@@ -328,683 +2588,288 @@ void ast_cdr_getvar(struct ast_cdr *cdr, const char *name, char **ret, char *wor
 		if (raw) {
 			snprintf(workspace, workspacelen, "%ld", cdr->amaflags);
 		} else {
-			ast_copy_string(workspace, ast_cdr_flags2str(cdr->amaflags), workspacelen);
+			ast_copy_string(workspace, ast_channel_amaflags2string(cdr->amaflags), workspacelen);
 		}
-	} else if (!strcasecmp(name, "accountcode"))
+	} else if (!strcasecmp(name, "accountcode")) {
 		ast_copy_string(workspace, cdr->accountcode, workspacelen);
-	else if (!strcasecmp(name, "peeraccount"))
+	} else if (!strcasecmp(name, "peeraccount")) {
 		ast_copy_string(workspace, cdr->peeraccount, workspacelen);
-	else if (!strcasecmp(name, "uniqueid"))
+	} else if (!strcasecmp(name, "uniqueid")) {
 		ast_copy_string(workspace, cdr->uniqueid, workspacelen);
-	else if (!strcasecmp(name, "linkedid"))
+	} else if (!strcasecmp(name, "linkedid")) {
 		ast_copy_string(workspace, cdr->linkedid, workspacelen);
-	else if (!strcasecmp(name, "userfield"))
+	} else if (!strcasecmp(name, "userfield")) {
 		ast_copy_string(workspace, cdr->userfield, workspacelen);
-	else if (!strcasecmp(name, "sequence"))
+	} else if (!strcasecmp(name, "sequence")) {
 		snprintf(workspace, workspacelen, "%d", cdr->sequence);
-	else if ((varbuf = ast_cdr_getvar_internal(cdr, name, recur)))
+	} else if ((varbuf = cdr_format_var_internal(cdr, name))) {
 		ast_copy_string(workspace, varbuf, workspacelen);
-	else
+	} else {
 		workspace[0] = '\0';
+	}
 
-	if (!ast_strlen_zero(workspace))
+	if (!ast_strlen_zero(workspace)) {
 		*ret = workspace;
+	}
+}
+
+/*
+ * \internal
+ * \brief Callback that finds all CDRs that reference a particular channel
+ */
+static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
+{
+	struct cdr_object *cdr = obj;
+	const char *name = arg;
+	if (!(flags & OBJ_KEY)) {
+		return 0;
+	}
+	if (!strcasecmp(cdr->party_a.snapshot->name, name) ||
+			(cdr->party_b.snapshot && !strcasecmp(cdr->party_b.snapshot->name, name))) {
+		return CMP_MATCH;
+	}
+	return 0;
 }
 
-/* readonly cdr variables */
+/* Read Only CDR variables */
 static const char * const cdr_readonly_vars[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
 						  "lastapp", "lastdata", "start", "answer", "end", "duration",
 						  "billsec", "disposition", "amaflags", "accountcode", "uniqueid", "linkedid",
 						  "userfield", "sequence", NULL };
-/*! Set a CDR channel variable
-	\note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
-*/
-int ast_cdr_setvar(struct ast_cdr *cdr, const char *name, const char *value, int recur)
+
+int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
 {
-	struct ast_var_t *newvariable;
-	struct varshead *headp;
+	struct cdr_object *cdr;
+	struct cdr_object *it_cdr;
+	struct ao2_iterator *it_cdrs;
+	char *arg = ast_strdupa(channel_name);
 	int x;
 
 	for (x = 0; cdr_readonly_vars[x]; x++) {
 		if (!strcasecmp(name, cdr_readonly_vars[x])) {
-			ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!.\n", name);
+			ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!\n", name);
 			return -1;
 		}
 	}
 
-	if (!cdr) {
-		ast_log(LOG_ERROR, "Attempt to set a variable on a nonexistent CDR record.\n");
+	it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE | OBJ_KEY, cdr_object_select_all_by_channel_cb, arg);
+	if (!it_cdrs) {
+		ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
 		return -1;
 	}
 
-	for (; cdr; cdr = recur ? cdr->next : NULL) {
-		if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			continue;
-		headp = &cdr->varshead;
-		AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
-			if (!strcasecmp(ast_var_name(newvariable), name)) {
-				/* there is already such a variable, delete it */
-				AST_LIST_REMOVE_CURRENT(entries);
-				ast_var_delete(newvariable);
-				break;
-			}
-		}
-		AST_LIST_TRAVERSE_SAFE_END;
-
-		if (value) {
-			newvariable = ast_var_assign(name, value);
-			AST_LIST_INSERT_HEAD(headp, newvariable, entries);
-		}
-	}
-
-	return 0;
-}
-
-int ast_cdr_copy_vars(struct ast_cdr *to_cdr, struct ast_cdr *from_cdr)
-{
-	struct ast_var_t *variables, *newvariable = NULL;
-	struct varshead *headpa, *headpb;
-	const char *var, *val;
-	int x = 0;
-
-	if (!to_cdr || !from_cdr) /* don't die if one of the pointers is null */
-		return 0;
-
-	headpa = &from_cdr->varshead;
-	headpb = &to_cdr->varshead;
-
-	AST_LIST_TRAVERSE(headpa,variables,entries) {
-		if (variables &&
-		    (var = ast_var_name(variables)) && (val = ast_var_value(variables)) &&
-		    !ast_strlen_zero(var) && !ast_strlen_zero(val)) {
-			newvariable = ast_var_assign(var, val);
-			AST_LIST_INSERT_HEAD(headpb, newvariable, entries);
-			x++;
-		}
-	}
-
-	return x;
-}
-
-int ast_cdr_serialize_variables(struct ast_cdr *cdr, struct ast_str **buf, char delim, char sep, int recur)
-{
-	struct ast_var_t *variables;
-	const char *var;
-	char *tmp;
-	char workspace[256];
-	int total = 0, x = 0, i;
-
-	ast_str_reset(*buf);
-
-	for (; cdr; cdr = recur ? cdr->next : NULL) {
-		if (++x > 1)
-			ast_str_append(buf, 0, "\n");
-
-		AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) {
-			if (!(var = ast_var_name(variables))) {
+	while ((cdr = ao2_iterator_next(it_cdrs))) {
+		ao2_lock(cdr);
+		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+			struct varshead *headp = NULL;
+			if (it_cdr->fn_table == &finalized_state_fn_table) {
 				continue;
 			}
-
-			if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variables), ""), sep) < 0) {
-				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
-				break;
+			if (!strcmp(channel_name, it_cdr->party_a.snapshot->name)) {
+				headp = &it_cdr->party_a.variables;
+			} else if (it_cdr->party_b.snapshot && !strcmp(channel_name, it_cdr->party_b.snapshot->name)) {
+				headp = &it_cdr->party_b.variables;
 			}
-
-			total++;
-		}
-
-		for (i = 0; cdr_readonly_vars[i]; i++) {
-			workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
-			ast_cdr_getvar(cdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
-			if (!tmp)
-				continue;
-
-			if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, tmp, sep) < 0) {
-				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
-				break;
-			} else
-				total++;
-		}
-	}
-
-	return total;
-}
-
-
-void ast_cdr_free_vars(struct ast_cdr *cdr, int recur)
-{
-
-	/* clear variables */
-	for (; cdr; cdr = recur ? cdr->next : NULL) {
-		struct ast_var_t *vardata;
-		struct varshead *headp = &cdr->varshead;
-		while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries)))
-			ast_var_delete(vardata);
-	}
-}
-
-/*! \brief  print a warning if cdr already posted */
-static void check_post(struct ast_cdr *cdr)
-{
-	if (!cdr)
-		return;
-	if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED))
-		ast_log(LOG_NOTICE, "CDR on channel '%s' already posted\n", S_OR(cdr->channel, "<unknown>"));
-}
-
-void ast_cdr_free(struct ast_cdr *cdr)
-{
-
-	while (cdr) {
-		struct ast_cdr *next = cdr->next;
-
-		ast_cdr_free_vars(cdr, 0);
-		ast_free(cdr);
-		cdr = next;
-	}
-}
-
-/*! \brief the same as a cdr_free call, only with no checks; just get rid of it */
-void ast_cdr_discard(struct ast_cdr *cdr)
-{
-	while (cdr) {
-		struct ast_cdr *next = cdr->next;
-
-		ast_cdr_free_vars(cdr, 0);
-		ast_free(cdr);
-		cdr = next;
-	}
-}
-
-struct ast_cdr *ast_cdr_alloc(void)
-{
-	struct ast_cdr *x;
-	x = ast_calloc(1, sizeof(*x));
-	if (!x)
-		ast_log(LOG_ERROR,"Allocation Failure for a CDR!\n");
-	return x;
-}
-
-static void cdr_merge_vars(struct ast_cdr *to, struct ast_cdr *from)
-{
-	struct ast_var_t *variablesfrom,*variablesto;
-	struct varshead *headpfrom = &to->varshead;
-	struct varshead *headpto = &from->varshead;
-	AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom, variablesfrom, entries) {
-		/* for every var in from, stick it in to */
-		const char *fromvarname, *fromvarval;
-		const char *tovarname = NULL, *tovarval = NULL;
-		fromvarname = ast_var_name(variablesfrom);
-		fromvarval = ast_var_value(variablesfrom);
-		tovarname = 0;
-
-		/* now, quick see if that var is in the 'to' cdr already */
-		AST_LIST_TRAVERSE(headpto, variablesto, entries) {
-
-			/* now, quick see if that var is in the 'to' cdr already */
-			if ( strcasecmp(fromvarname, ast_var_name(variablesto)) == 0 ) {
-				tovarname = ast_var_name(variablesto);
-				tovarval = ast_var_value(variablesto);
-				break;
+			if (headp) {
+				set_variable(headp, name, value);
 			}
 		}
-		if (tovarname && strcasecmp(fromvarval,tovarval) != 0) {  /* this message here to see how irritating the userbase finds it */
-			ast_log(LOG_NOTICE, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname, fromvarval, tovarval);
-			continue;
-		} else if (tovarname && strcasecmp(fromvarval,tovarval) == 0) /* if they are the same, the job is done */
-			continue;
-
-		/* rip this var out of the from cdr, and stick it in the to cdr */
-		AST_LIST_MOVE_CURRENT(headpto, entries);
+		ao2_unlock(cdr);
+		ao2_ref(cdr, -1);
 	}
-	AST_LIST_TRAVERSE_SAFE_END;
+	ao2_iterator_destroy(it_cdrs);
+
+	return 0;
 }
 
-void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from)
+/*!
+ * \brief Format a variable on a \ref cdr_object
+ */
+static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char *value, size_t length)
 {
-	struct ast_cdr *zcdr;
-	struct ast_cdr *lto = NULL;
-	struct ast_cdr *lfrom = NULL;
-	int discard_from = 0;
-
-	if (!to || !from)
-		return;
+	struct ast_var_t *variable;
 
-	/* don't merge into locked CDR's -- it's bad business */
-	if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
-		zcdr = to; /* safety valve? */
-		while (to->next) {
-			lto = to;
-			to = to->next;
-		}
-
-		if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
-			ast_log(LOG_WARNING, "Merging into locked CDR... no choice.\n");
-			to = zcdr; /* safety-- if all there are is locked CDR's, then.... ?? */
-			lto = NULL;
+	AST_LIST_TRAVERSE(&cdr->party_a.variables, variable, entries) {
+		if (!strcasecmp(name, ast_var_name(variable))) {
+			ast_copy_string(value, ast_var_value(variable), length);
+			return;
 		}
 	}
 
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) {
-		struct ast_cdr *llfrom = NULL;
-		discard_from = 1;
-		if (lto) {
-			/* insert the from stuff after lto */
-			lto->next = from;
-			lfrom = from;
-			while (lfrom && lfrom->next) {
-				if (!lfrom->next->next)
-					llfrom = lfrom;
-				lfrom = lfrom->next;
-			}
-			/* rip off the last entry and put a copy of the to at the end */
-			if (llfrom) {
-				llfrom->next = to;
-			}
-			from = lfrom;
-		} else {
-			/* save copy of the current *to cdr */
-			struct ast_cdr tcdr;
-			memcpy(&tcdr, to, sizeof(tcdr));
-			/* copy in the locked from cdr */
-			memcpy(to, from, sizeof(*to));
-			lfrom = from;
-			while (lfrom && lfrom->next) {
-				if (!lfrom->next->next)
-					llfrom = lfrom;
-				lfrom = lfrom->next;
-			}
-			from->next = NULL;
-			/* rip off the last entry and put a copy of the to at the end */
-			if (llfrom == from) {
-				to = to->next = ast_cdr_dup(&tcdr);
-			} else if (llfrom) {
-				to = llfrom->next = ast_cdr_dup(&tcdr);
-			}
-			from = lfrom;
-		}
-	}
+	*value = '\0';
+}
 
-	if (!ast_tvzero(from->start)) {
-		if (!ast_tvzero(to->start)) {
-			if (ast_tvcmp(to->start, from->start) > 0 ) {
-				to->start = from->start; /* use the earliest time */
-				from->start = ast_tv(0,0); /* we actively "steal" these values */
-			}
-			/* else nothing to do */
-		} else {
-			to->start = from->start;
-			from->start = ast_tv(0,0); /* we actively "steal" these values */
-		}
-	}
-	if (!ast_tvzero(from->answer)) {
-		if (!ast_tvzero(to->answer)) {
-			if (ast_tvcmp(to->answer, from->answer) > 0 ) {
-				to->answer = from->answer; /* use the earliest time */
-				from->answer = ast_tv(0,0); /* we actively "steal" these values */
-			}
-			/* we got the earliest answer time, so we'll settle for that? */
+/*!
+ * \brief Format one of the standard properties on a \ref cdr_object
+ */
+static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char *value, size_t length)
+{
+	struct ast_channel_snapshot *party_a = cdr_obj->party_a.snapshot;
+	struct ast_channel_snapshot *party_b = cdr_obj->party_b.snapshot;
+
+	if (!strcasecmp(name, "clid")) {
+		ast_callerid_merge(value, length, party_a->caller_name, party_a->caller_number, "");
+	} else if (!strcasecmp(name, "src")) {
+		ast_copy_string(value, party_a->caller_number, length);
+	} else if (!strcasecmp(name, "dst")) {
+		ast_copy_string(value, party_a->exten, length);
+	} else if (!strcasecmp(name, "dcontext")) {
+		ast_copy_string(value, party_a->context, length);
+	} else if (!strcasecmp(name, "channel")) {
+		ast_copy_string(value, party_a->name, length);
+	} else if (!strcasecmp(name, "dstchannel")) {
+		if (party_b) {
+			ast_copy_string(value, party_b->name, length);
 		} else {
-			to->answer = from->answer;
-			from->answer = ast_tv(0,0); /* we actively "steal" these values */
+			ast_copy_string(value, "", length);
 		}
-	}
-	if (!ast_tvzero(from->end)) {
-		if (!ast_tvzero(to->end)) {
-			if (ast_tvcmp(to->end, from->end) < 0 ) {
-				to->end = from->end; /* use the latest time */
-				from->end = ast_tv(0,0); /* we actively "steal" these values */
-				to->duration = to->end.tv_sec - to->start.tv_sec;  /* don't forget to update the duration, billsec, when we set end */
-				to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
-			}
-			/* else, nothing to do */
+	} else if (!strcasecmp(name, "lastapp")) {
+		ast_copy_string(value, party_a->appl, length);
+	} else if (!strcasecmp(name, "lastdata")) {
+		ast_copy_string(value, party_a->data, length);
+	} else if (!strcasecmp(name, "start")) {
+		cdr_get_tv(cdr_obj->start, NULL, value, length);
+	} else if (!strcasecmp(name, "answer")) {
+		cdr_get_tv(cdr_obj->answer, NULL, value, length);
+	} else if (!strcasecmp(name, "end")) {
+		cdr_get_tv(cdr_obj->end, NULL, value, length);
+	} else if (!strcasecmp(name, "duration")) {
+		snprintf(value, length, "%ld", cdr_object_get_duration(cdr_obj));
+	} else if (!strcasecmp(name, "billsec")) {
+		snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
+	} else if (!strcasecmp(name, "disposition")) {
+		snprintf(value, length, "%d", cdr_obj->disposition);
+	} else if (!strcasecmp(name, "amaflags")) {
+		snprintf(value, length, "%d", party_a->amaflags);
+	} else if (!strcasecmp(name, "accountcode")) {
+		ast_copy_string(value, party_a->accountcode, length);
+	} else if (!strcasecmp(name, "peeraccount")) {
+		if (party_b) {
+			ast_copy_string(value, party_b->accountcode, length);
 		} else {
-			to->end = from->end;
-			from->end = ast_tv(0,0); /* we actively "steal" these values */
-			to->duration = to->end.tv_sec - to->start.tv_sec;
-			to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
-		}
-	}
-	if (to->disposition < from->disposition) {
-		to->disposition = from->disposition;
-		from->disposition = AST_CDR_NOANSWER;
-	}
-	if (ast_strlen_zero(to->lastapp) && !ast_strlen_zero(from->lastapp)) {
-		ast_copy_string(to->lastapp, from->lastapp, sizeof(to->lastapp));
-		from->lastapp[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->lastdata) && !ast_strlen_zero(from->lastdata)) {
-		ast_copy_string(to->lastdata, from->lastdata, sizeof(to->lastdata));
-		from->lastdata[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->dcontext) && !ast_strlen_zero(from->dcontext)) {
-		ast_copy_string(to->dcontext, from->dcontext, sizeof(to->dcontext));
-		from->dcontext[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->dstchannel) && !ast_strlen_zero(from->dstchannel)) {
-		ast_copy_string(to->dstchannel, from->dstchannel, sizeof(to->dstchannel));
-		from->dstchannel[0] = 0; /* theft */
-	}
-	if (!ast_strlen_zero(from->channel) && (ast_strlen_zero(to->channel) || !strncasecmp(from->channel, "Agent/", 6))) {
-		ast_copy_string(to->channel, from->channel, sizeof(to->channel));
-		from->channel[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->src) && !ast_strlen_zero(from->src)) {
-		ast_copy_string(to->src, from->src, sizeof(to->src));
-		from->src[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->clid) && !ast_strlen_zero(from->clid)) {
-		ast_copy_string(to->clid, from->clid, sizeof(to->clid));
-		from->clid[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->dst) && !ast_strlen_zero(from->dst)) {
-		ast_copy_string(to->dst, from->dst, sizeof(to->dst));
-		from->dst[0] = 0; /* theft */
-	}
-	if (!to->amaflags)
-		to->amaflags = AST_CDR_DOCUMENTATION;
-	if (!from->amaflags)
-		from->amaflags = AST_CDR_DOCUMENTATION; /* make sure both amaflags are set to something (DOC is default) */
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (to->amaflags == AST_CDR_DOCUMENTATION && from->amaflags != AST_CDR_DOCUMENTATION)) {
-		to->amaflags = from->amaflags;
-	}
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->accountcode) && !ast_strlen_zero(from->accountcode))) {
-		ast_copy_string(to->accountcode, from->accountcode, sizeof(to->accountcode));
-	}
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->peeraccount) && !ast_strlen_zero(from->peeraccount))) {
-		ast_copy_string(to->peeraccount, from->peeraccount, sizeof(to->peeraccount));
-	}
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->userfield) && !ast_strlen_zero(from->userfield))) {
-		ast_copy_string(to->userfield, from->userfield, sizeof(to->userfield));
-	}
-	/* flags, varsead, ? */
-	cdr_merge_vars(from, to);
-
-	if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS))
-		ast_set_flag(to, AST_CDR_FLAG_KEEP_VARS);
-	if (ast_test_flag(from, AST_CDR_FLAG_POSTED))
-		ast_set_flag(to, AST_CDR_FLAG_POSTED);
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED))
-		ast_set_flag(to, AST_CDR_FLAG_LOCKED);
-	if (ast_test_flag(from, AST_CDR_FLAG_CHILD))
-		ast_set_flag(to, AST_CDR_FLAG_CHILD);
-	if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED))
-		ast_set_flag(to, AST_CDR_FLAG_POST_DISABLED);
-
-	/* last, but not least, we need to merge any forked CDRs to the 'to' cdr */
-	while (from->next) {
-		/* just rip 'em off the 'from' and insert them on the 'to' */
-		zcdr = from->next;
-		from->next = zcdr->next;
-		zcdr->next = NULL;
-		/* zcdr is now ripped from the current list; */
-		ast_cdr_append(to, zcdr);
-	}
-	if (discard_from)
-		ast_cdr_discard(from);
-}
-
-void ast_cdr_start(struct ast_cdr *cdr)
-{
-	for (; cdr; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			check_post(cdr);
-			cdr->start = ast_tvnow();
-		}
-	}
-}
-
-void ast_cdr_answer(struct ast_cdr *cdr)
-{
-
-	for (; cdr; cdr = cdr->next) {
-		if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED))
-			continue;
-		if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			continue;
-		check_post(cdr);
-		if (cdr->disposition < AST_CDR_ANSWERED)
-			cdr->disposition = AST_CDR_ANSWERED;
-		if (ast_tvzero(cdr->answer))
-			cdr->answer = ast_tvnow();
-	}
-}
-
-void ast_cdr_busy(struct ast_cdr *cdr)
-{
-
-	for (; cdr; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			check_post(cdr);
-			cdr->disposition = AST_CDR_BUSY;
-		}
-	}
-}
-
-void ast_cdr_failed(struct ast_cdr *cdr)
-{
-	for (; cdr; cdr = cdr->next) {
-		check_post(cdr);
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			check_post(cdr);
-			if (cdr->disposition < AST_CDR_FAILED)
-				cdr->disposition = AST_CDR_FAILED;
+			ast_copy_string(value, "", length);
 		}
+	} else if (!strcasecmp(name, "uniqueid")) {
+		ast_copy_string(value, party_a->uniqueid, length);
+	} else if (!strcasecmp(name, "linkedid")) {
+		ast_copy_string(value, cdr_obj->linkedid, length);
+	} else if (!strcasecmp(name, "userfield")) {
+		ast_copy_string(value, cdr_obj->party_a.userfield, length);
+	} else if (!strcasecmp(name, "sequence")) {
+		snprintf(value, length, "%d", cdr_obj->sequence);
+	} else {
+		return 1;
 	}
-}
 
-void ast_cdr_noanswer(struct ast_cdr *cdr)
-{
-	while (cdr) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			check_post(cdr);
-			cdr->disposition = AST_CDR_NOANSWER;
-		}
-		cdr = cdr->next;
-	}
+	return 0;
 }
 
-void ast_cdr_congestion(struct ast_cdr *cdr)
+int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
 {
-	char *chan;
+	RAII_VAR(struct cdr_object *, cdr,
+		ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+		ao2_cleanup);
+	struct cdr_object *cdr_obj;
 
-	/* if congestion log is disabled, pass the buck to ast_cdr_failed */
-	if (!congestion) {
-		ast_cdr_failed(cdr);
+	if (!cdr) {
+		ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+		return 1;
 	}
 
-	while (cdr && congestion) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			chan = !ast_strlen_zero(cdr->channel) ? cdr->channel : "<unknown>";
-
-			if (ast_test_flag(cdr, AST_CDR_FLAG_POSTED)) {
-				ast_log(LOG_WARNING, "CDR on channel '%s' already posted\n", chan);
-			}
-
-			if (cdr->disposition < AST_CDR_CONGESTION) {
-				cdr->disposition = AST_CDR_CONGESTION;
-			}
-		}
-		cdr = cdr->next;
+	if (ast_strlen_zero(name)) {
+		return 1;
 	}
-}
 
-/* everywhere ast_cdr_disposition is called, it will call ast_cdr_failed()
-   if ast_cdr_disposition returns a non-zero value */
+	ao2_lock(cdr);
 
-int ast_cdr_disposition(struct ast_cdr *cdr, int cause)
-{
-	int res = 0;
+	cdr_obj = cdr->last;
 
-	for (; cdr; cdr = cdr->next) {
-		switch (cause) {  /* handle all the non failure, busy cases, return 0 not to set disposition,
-							return -1 to set disposition to FAILED */
-		case AST_CAUSE_BUSY:
-			ast_cdr_busy(cdr);
-			break;
-		case AST_CAUSE_NO_ANSWER:
-			ast_cdr_noanswer(cdr);
-			break;
-		case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION:
-			ast_cdr_congestion(cdr);
-			break;
-		case AST_CAUSE_NORMAL:
-			break;
-		default:
-			res = -1;
-		}
+	if (cdr_object_format_property(cdr_obj, name, value, length)) {
+		/* Property failed; attempt variable */
+		cdr_object_format_var_internal(cdr_obj, name, value, length);
 	}
-	return res;
-}
+	ao2_unlock(cdr);
 
-void ast_cdr_setdestchan(struct ast_cdr *cdr, const char *chann)
-{
-	for (; cdr; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			check_post(cdr);
-			ast_copy_string(cdr->dstchannel, chann, sizeof(cdr->dstchannel));
-		}
-	}
+	return 0;
 }
 
-void ast_cdr_setapp(struct ast_cdr *cdr, const char *app, const char *data)
+int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep)
 {
+	RAII_VAR(struct cdr_object *, cdr,
+		ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+		ao2_cleanup);
+	struct cdr_object *it_cdr;
+	struct ast_var_t *variable;
+	const char *var;
+	RAII_VAR(char *, workspace, ast_malloc(256), ast_free);
+	int total = 0, x = 0, i;
 
-	for (; cdr; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			check_post(cdr);
-			ast_copy_string(cdr->lastapp, S_OR(app, ""), sizeof(cdr->lastapp));
-			ast_copy_string(cdr->lastdata, S_OR(data, ""), sizeof(cdr->lastdata));
-		}
+	if (!workspace) {
+		return 1;
 	}
-}
-
-void ast_cdr_setanswer(struct ast_cdr *cdr, struct timeval t)
-{
 
-	for (; cdr; cdr = cdr->next) {
-		if (ast_test_flag(cdr, AST_CDR_FLAG_ANSLOCKED))
-			continue;
-		if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			continue;
-		check_post(cdr);
-		cdr->answer = t;
+	if (!cdr) {
+		ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+		return 1;
 	}
-}
 
-void ast_cdr_setdisposition(struct ast_cdr *cdr, long int disposition)
-{
+	ast_str_reset(*buf);
 
-	for (; cdr; cdr = cdr->next) {
-		if (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			continue;
-		check_post(cdr);
-		cdr->disposition = disposition;
-	}
-}
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (++x > 1)
+			ast_str_append(buf, 0, "\n");
 
-/* set cid info for one record */
-static void set_one_cid(struct ast_cdr *cdr, struct ast_channel *c)
-{
-	const char *num;
+		AST_LIST_TRAVERSE(&it_cdr->party_a.variables, variable, entries) {
+			if (!(var = ast_var_name(variable))) {
+				continue;
+			}
 
-	if (!cdr) {
-		return;
-	}
+			if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variable), ""), sep) < 0) {
+				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
+				break;
+			}
 
-	/* Grab source from ANI or normal Caller*ID */
-	num = S_COR(ast_channel_caller(c)->ani.number.valid, ast_channel_caller(c)->ani.number.str,
-		S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL));
-	ast_callerid_merge(cdr->clid, sizeof(cdr->clid),
-		S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, NULL), num, "");
-	ast_copy_string(cdr->src, S_OR(num, ""), sizeof(cdr->src));
-	ast_cdr_setvar(cdr, "dnid", S_OR(ast_channel_dialed(c)->number.str, ""), 0);
+			total++;
+		}
 
-	if (ast_channel_caller(c)->id.subaddress.valid) {
-		ast_cdr_setvar(cdr, "callingsubaddr", S_OR(ast_channel_caller(c)->id.subaddress.str, ""), 0);
-	}
-	if (ast_channel_dialed(c)->subaddress.valid) {
-		ast_cdr_setvar(cdr, "calledsubaddr", S_OR(ast_channel_dialed(c)->subaddress.str, ""), 0);
-	}
-}
+		for (i = 0; cdr_readonly_vars[i]; i++) {
+			/* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
+			workspace[0] = 0;
+			cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace));
 
-int ast_cdr_setcid(struct ast_cdr *cdr, struct ast_channel *c)
-{
-	for (; cdr; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			set_one_cid(cdr, c);
+			if (!ast_strlen_zero(workspace)
+				&& ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, workspace, sep) < 0) {
+				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
+				break;
+			}
+			total++;
+		}
 	}
-	return 0;
-}
 
-static int cdr_seq_inc(struct ast_cdr *cdr)
-{
-	return (cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1));
+	return total;
 }
 
-int ast_cdr_init(struct ast_cdr *cdr, struct ast_channel *c)
+void ast_cdr_free(struct ast_cdr *cdr)
 {
-	for ( ; cdr ; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			ast_copy_string(cdr->channel, ast_channel_name(c), sizeof(cdr->channel));
-			set_one_cid(cdr, c);
-			cdr_seq_inc(cdr);
+	while (cdr) {
+		struct ast_cdr *next = cdr->next;
 
-			cdr->disposition = (ast_channel_state(c) == AST_STATE_UP) ?  AST_CDR_ANSWERED : AST_CDR_NOANSWER;
-			cdr->amaflags = ast_channel_amaflags(c) ? ast_channel_amaflags(c) :  ast_default_amaflags;
-			ast_copy_string(cdr->accountcode, ast_channel_accountcode(c), sizeof(cdr->accountcode));
-			ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(c), sizeof(cdr->peeraccount));
-			/* Destination information */
-			ast_copy_string(cdr->dst, S_OR(ast_channel_macroexten(c),ast_channel_exten(c)), sizeof(cdr->dst));
-			ast_copy_string(cdr->dcontext, S_OR(ast_channel_macrocontext(c),ast_channel_context(c)), sizeof(cdr->dcontext));
-			/* Unique call identifier */
-			ast_copy_string(cdr->uniqueid, ast_channel_uniqueid(c), sizeof(cdr->uniqueid));
-			/* Linked call identifier */
-			ast_copy_string(cdr->linkedid, ast_channel_linkedid(c), sizeof(cdr->linkedid));
-		}
+		free_variables(&cdr->varshead);
+		ast_free(cdr);
+		cdr = next;
 	}
-	return 0;
 }
 
-/* Three routines were "fixed" via 10668, and later shown that
-   users were depending on this behavior. ast_cdr_end,
-   ast_cdr_setvar and ast_cdr_answer are the three routines.
-   While most of the other routines would not touch
-   LOCKED cdr's, these three routines were designed to
-   operate on locked CDR's as a matter of course.
-   I now appreciate how this plays with the ForkCDR app,
-   which forms these cdr chains in the first place.
-   cdr_end is pretty key: all cdrs created are closed
-   together. They only vary by start time. Arithmetically,
-   users can calculate the subintervals they wish to track. */
-
-void ast_cdr_end(struct ast_cdr *cdr)
+struct ast_cdr *ast_cdr_alloc(void)
 {
-	for ( ; cdr ; cdr = cdr->next) {
-		if (ast_test_flag(cdr, AST_CDR_FLAG_DONT_TOUCH) && ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			continue;
-		check_post(cdr);
-		if (ast_tvzero(cdr->end))
-			cdr->end = ast_tvnow();
-		if (ast_tvzero(cdr->start)) {
-			ast_log(LOG_WARNING, "CDR on channel '%s' has not started\n", S_OR(cdr->channel, "<unknown>"));
-			cdr->disposition = AST_CDR_FAILED;
-		} else
-			cdr->duration = cdr->end.tv_sec - cdr->start.tv_sec;
-		if (ast_tvzero(cdr->answer)) {
-			if (cdr->disposition == AST_CDR_ANSWERED) {
-				ast_log(LOG_WARNING, "CDR on channel '%s' has no answer time but is 'ANSWERED'\n", S_OR(cdr->channel, "<unknown>"));
-				cdr->disposition = AST_CDR_FAILED;
-			}
-		} else {
-			cdr->billsec = cdr->end.tv_sec - cdr->answer.tv_sec;
-			if (ast_test_flag(&ast_options, AST_OPT_FLAG_INITIATED_SECONDS))
-				cdr->billsec += cdr->end.tv_usec > cdr->answer.tv_usec ? 1 : 0;
-		}
-	}
+	struct ast_cdr *x;
+
+	x = ast_calloc(1, sizeof(*x));
+	return x;
 }
 
-char *ast_cdr_disp2str(int disposition)
+const char *ast_cdr_disp2str(int disposition)
 {
 	switch (disposition) {
 	case AST_CDR_NULL:
@@ -1023,272 +2888,247 @@ char *ast_cdr_disp2str(int disposition)
 	return "UNKNOWN";
 }
 
-/*! Converts AMA flag to printable string 
- *
- * \param flag, flags
- */
-char *ast_cdr_flags2str(int flag)
-{
-	switch (flag) {
-	case AST_CDR_OMIT:
-		return "OMIT";
-	case AST_CDR_BILLING:
-		return "BILLING";
-	case AST_CDR_DOCUMENTATION:
-		return "DOCUMENTATION";
-	}
-	return "Unknown";
-}
+struct party_b_userfield_update {
+	const char *channel_name;
+	const char *userfield;
+};
 
-int ast_cdr_setaccount(struct ast_channel *chan, const char *account)
+/*! \brief Callback used to update the userfield on Party B on all CDRs */
+static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flags)
 {
-	struct ast_cdr *cdr = ast_channel_cdr(chan);
-	const char *old_acct = "";
-
-	if (!ast_strlen_zero(ast_channel_accountcode(chan))) {
-		old_acct = ast_strdupa(ast_channel_accountcode(chan));
-	}
-
-	ast_channel_accountcode_set(chan, account);
-	for ( ; cdr ; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			ast_copy_string(cdr->accountcode, ast_channel_accountcode(chan), sizeof(cdr->accountcode));
+	struct cdr_object *cdr = obj;
+	struct party_b_userfield_update *info = arg;
+	struct cdr_object *it_cdr;
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table == &finalized_state_fn_table) {
+			continue;
+		}
+		if (it_cdr->party_b.snapshot
+			&& !strcmp(it_cdr->party_b.snapshot->name, info->channel_name)) {
+			strcpy(it_cdr->party_b.userfield, info->userfield);
 		}
 	}
-	/*** DOCUMENTATION
-		<managerEventInstance>
-			<synopsis>Raised when a CDR's AccountCode is changed.</synopsis>
-		</managerEventInstance>
-	***/
-	ast_manager_event(chan, EVENT_FLAG_CALL, "NewAccountCode",
-			"Channel: %s\r\n"
-			"Uniqueid: %s\r\n"
-			"AccountCode: %s\r\n"
-			"OldAccountCode: %s\r\n",
-			ast_channel_name(chan), ast_channel_uniqueid(chan), ast_channel_accountcode(chan), old_acct);
-
 	return 0;
 }
 
-int ast_cdr_setpeeraccount(struct ast_channel *chan, const char *account)
+void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
 {
-	struct ast_cdr *cdr = ast_channel_cdr(chan);
-	const char *old_acct = "";
-
-	if (!ast_strlen_zero(ast_channel_peeraccount(chan))) {
-		old_acct = ast_strdupa(ast_channel_peeraccount(chan));
-	}
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+			ao2_cleanup);
+	struct party_b_userfield_update party_b_info = {
+			.channel_name = channel_name,
+			.userfield = userfield,
+	};
+	struct cdr_object *it_cdr;
 
-	ast_channel_peeraccount_set(chan, account);
-	for ( ; cdr ; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(chan), sizeof(cdr->peeraccount));
+	/* Handle Party A */
+	if (cdr) {
+		ao2_lock(cdr);
+		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+			if (it_cdr->fn_table == &finalized_state_fn_table) {
+				continue;
+			}
+			strcpy(it_cdr->party_a.userfield, userfield);
 		}
+		ao2_unlock(cdr);
 	}
-	/*** DOCUMENTATION
-		<managerEventInstance>
-			<synopsis>Raised when a CDR's PeerAccount is changed.</synopsis>
-		</managerEventInstance>
-	***/
-	ast_manager_event(chan, EVENT_FLAG_CALL, "NewPeerAccount",
-			"Channel: %s\r\n"
-			"Uniqueid: %s\r\n"
-			"PeerAccount: %s\r\n"
-			"OldPeerAccount: %s\r\n",
-			ast_channel_name(chan), ast_channel_uniqueid(chan), ast_channel_peeraccount(chan), old_acct);
 
-	return 0;
+	/* Handle Party B */
+	ao2_callback(active_cdrs_by_channel, OBJ_NODATA,
+			cdr_object_update_party_b_userfield_cb,
+			&party_b_info);
+
 }
 
-int ast_cdr_setamaflags(struct ast_channel *chan, const char *flag)
+static void post_cdr(struct ast_cdr *cdr)
 {
-	struct ast_cdr *cdr;
-	int newflag = ast_cdr_amaflags2int(flag);
-	if (newflag) {
-		for (cdr = ast_channel_cdr(chan); cdr; cdr = cdr->next) {
-			if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-				cdr->amaflags = newflag;
-			}
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct cdr_beitem *i;
+
+	for (; cdr ; cdr = cdr->next) {
+		/* For people, who don't want to see unanswered single-channel events */
+		if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
+				cdr->disposition < AST_CDR_ANSWERED &&
+				(ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
+			continue;
 		}
-	}
 
-	return 0;
+		if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) {
+			continue;
+		}
+		AST_RWLIST_RDLOCK(&be_list);
+		AST_RWLIST_TRAVERSE(&be_list, i, list) {
+			i->be(cdr);
+		}
+		AST_RWLIST_UNLOCK(&be_list);
+	}
 }
 
-int ast_cdr_setuserfield(struct ast_channel *chan, const char *userfield)
+int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
 {
-	struct ast_cdr *cdr = ast_channel_cdr(chan);
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+			ao2_cleanup);
+	struct cdr_object *it_cdr;
+
+	if (!cdr) {
+		return -1;
+	}
 
-	for ( ; cdr ; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			ast_copy_string(cdr->userfield, userfield, sizeof(cdr->userfield));
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table == &finalized_state_fn_table) {
+			continue;
+		}
+		ast_set_flag(&it_cdr->flags, option);
 	}
+	ao2_unlock(cdr);
 
 	return 0;
 }
 
-int ast_cdr_appenduserfield(struct ast_channel *chan, const char *userfield)
+int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
 {
-	struct ast_cdr *cdr = ast_channel_cdr(chan);
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+			ao2_cleanup);
+	struct cdr_object *it_cdr;
 
-	for ( ; cdr ; cdr = cdr->next) {
-		int len = strlen(cdr->userfield);
+	if (!cdr) {
+		return -1;
+	}
 
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED))
-			ast_copy_string(cdr->userfield + len, userfield, sizeof(cdr->userfield) - len);
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table == &finalized_state_fn_table) {
+			continue;
+		}
+		ast_clear_flag(&it_cdr->flags, option);
 	}
+	ao2_unlock(cdr);
 
 	return 0;
 }
 
-int ast_cdr_update(struct ast_channel *c)
+int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
 {
-	struct ast_cdr *cdr = ast_channel_cdr(c);
-
-	for ( ; cdr ; cdr = cdr->next) {
-		if (!ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			set_one_cid(cdr, c);
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+			ao2_cleanup);
+	struct ast_var_t *vardata;
+	struct cdr_object *it_cdr;
 
-			/* Copy account code et-al */
-			ast_copy_string(cdr->accountcode, ast_channel_accountcode(c), sizeof(cdr->accountcode));
-			ast_copy_string(cdr->peeraccount, ast_channel_peeraccount(c), sizeof(cdr->peeraccount));
-			ast_copy_string(cdr->linkedid, ast_channel_linkedid(c), sizeof(cdr->linkedid));
+	if (!cdr) {
+		return -1;
+	}
 
-			/* Destination information */ /* XXX privilege macro* ? */
-			ast_copy_string(cdr->dst, S_OR(ast_channel_macroexten(c), ast_channel_exten(c)), sizeof(cdr->dst));
-			ast_copy_string(cdr->dcontext, S_OR(ast_channel_macrocontext(c), ast_channel_context(c)), sizeof(cdr->dcontext));
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		/* clear variables */
+		if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+			while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
+				ast_var_delete(vardata);
+			}
+			if (cdr->party_b.snapshot) {
+				while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_b.variables, entries))) {
+					ast_var_delete(vardata);
+				}
+			}
 		}
+
+		/* Reset to initial state */
+		memset(&it_cdr->start, 0, sizeof(it_cdr->start));
+		memset(&it_cdr->end, 0, sizeof(it_cdr->end));
+		memset(&it_cdr->answer, 0, sizeof(it_cdr->answer));
+		it_cdr->start = ast_tvnow();
+		cdr_object_check_party_a_answer(it_cdr);
 	}
+	ao2_unlock(cdr);
 
 	return 0;
 }
 
-int ast_cdr_amaflags2int(const char *flag)
+int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
 {
-	if (!strcasecmp(flag, "default"))
-		return 0;
-	if (!strcasecmp(flag, "omit"))
-		return AST_CDR_OMIT;
-	if (!strcasecmp(flag, "billing"))
-		return AST_CDR_BILLING;
-	if (!strcasecmp(flag, "documentation"))
-		return AST_CDR_DOCUMENTATION;
-	return -1;
-}
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+			ao2_cleanup);
+	struct cdr_object *new_cdr;
+	struct cdr_object *it_cdr;
+	struct cdr_object *cdr_obj;
 
-static void post_cdr(struct ast_cdr *cdr)
-{
-	struct ast_cdr_beitem *i;
+	if (!cdr) {
+		return -1;
+	}
 
-	for ( ; cdr ; cdr = cdr->next) {
-		if (!unanswered && cdr->disposition < AST_CDR_ANSWERED && (ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
-			/* For people, who don't want to see unanswered single-channel events */
-			ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
-			continue;
+	{
+		SCOPED_AO2LOCK(lock, cdr);
+		cdr_obj = cdr->last;
+		if (cdr_obj->fn_table == &finalized_state_fn_table) {
+			/* If the last CDR in the chain is finalized, don't allow a fork -
+			 * things are already dying at this point
+			 */
+			ast_log(AST_LOG_ERROR, "FARK\n");
+			return -1;
 		}
 
-		/* don't post CDRs that are for dialed channels unless those
-		 * channels were originated from asterisk (pbx_spool, manager,
-		 * cli) */
-		if (ast_test_flag(cdr, AST_CDR_FLAG_DIALED) && !ast_test_flag(cdr, AST_CDR_FLAG_ORIGINATED)) {
-			ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
-			continue;
+		/* Copy over the basic CDR information. The Party A information is
+		 * copied over automatically as part of the append
+		 */
+		ast_debug(1, "Forking CDR for channel %s\n", cdr->party_a.snapshot->name);
+		new_cdr = cdr_object_create_and_append(cdr);
+		if (!new_cdr) {
+			return -1;
 		}
-
-		check_post(cdr);
-		ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
-		if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED))
-			continue;
-		AST_RWLIST_RDLOCK(&be_list);
-		AST_RWLIST_TRAVERSE(&be_list, i, list) {
-			i->be(cdr);
+		new_cdr->fn_table = cdr_obj->fn_table;
+		ast_string_field_set(new_cdr, bridge, cdr->bridge);
+		new_cdr->flags = cdr->flags;
+
+		/* If there's a Party B, copy it over as well */
+		if (cdr_obj->party_b.snapshot) {
+			new_cdr->party_b.snapshot = cdr_obj->party_b.snapshot;
+			ao2_ref(new_cdr->party_b.snapshot, +1);
+			strcpy(new_cdr->party_b.userfield, cdr_obj->party_b.userfield);
+			new_cdr->party_b.flags = cdr_obj->party_b.flags;
+			if (ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+				copy_variables(&new_cdr->party_b.variables, &cdr_obj->party_b.variables);
+			}
 		}
-		AST_RWLIST_UNLOCK(&be_list);
-	}
-}
+		new_cdr->start = cdr_obj->start;
+		new_cdr->answer = cdr_obj->answer;
 
-void ast_cdr_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
-{
-	struct ast_cdr *duplicate;
-	struct ast_flags flags = { 0 };
+		/* Modify the times based on the flags passed in */
+		if (ast_test_flag(options, AST_CDR_FLAG_SET_ANSWER)
+				&& new_cdr->party_a.snapshot->state == AST_STATE_UP) {
+			new_cdr->answer = ast_tvnow();
+		}
+		if (ast_test_flag(options, AST_CDR_FLAG_RESET)) {
+			new_cdr->answer = ast_tvnow();
+			new_cdr->start = ast_tvnow();
+		}
 
-	if (_flags)
-		ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
+		/* Create and append, by default, copies over the variables */
+		if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
+			free_variables(&new_cdr->party_a.variables);
+		}
 
-	for ( ; cdr ; cdr = cdr->next) {
-		/* Detach if post is requested */
-		if (ast_test_flag(&flags, AST_CDR_FLAG_LOCKED) || !ast_test_flag(cdr, AST_CDR_FLAG_LOCKED)) {
-			if (ast_test_flag(&flags, AST_CDR_FLAG_POSTED)) {
-				ast_cdr_end(cdr);
-				if ((duplicate = ast_cdr_dup_unique_swap(cdr))) {
-					ast_cdr_detach(duplicate);
+		/* Finalize any current CDRs */
+		if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) {
+			for (it_cdr = cdr; it_cdr != new_cdr; it_cdr = it_cdr->next) {
+				if (it_cdr->fn_table == &finalized_state_fn_table) {
+					continue;
 				}
-				ast_set_flag(cdr, AST_CDR_FLAG_POSTED);
-			}
-
-			/* enable CDR only */
-			if (ast_test_flag(&flags, AST_CDR_FLAG_POST_ENABLE)) {
-				ast_clear_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
-				continue;
-			}
-
-			/* clear variables */
-			if (!ast_test_flag(&flags, AST_CDR_FLAG_KEEP_VARS)) {
-				ast_cdr_free_vars(cdr, 0);
+				/* Force finalization on the CDR. This will bypass any checks for
+				 * end before 'h' extension.
+				 */
+				cdr_object_finalize(it_cdr);
+				cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
 			}
-
-			/* Reset to initial state */
-			ast_clear_flag(cdr, AST_FLAGS_ALL);
-			memset(&cdr->start, 0, sizeof(cdr->start));
-			memset(&cdr->end, 0, sizeof(cdr->end));
-			memset(&cdr->answer, 0, sizeof(cdr->answer));
-			cdr->billsec = 0;
-			cdr->duration = 0;
-			ast_cdr_start(cdr);
-			cdr->disposition = AST_CDR_NOANSWER;
 		}
 	}
-}
-
-void ast_cdr_specialized_reset(struct ast_cdr *cdr, struct ast_flags *_flags)
-{
-	struct ast_flags flags = { 0 };
-
-	if (_flags)
-		ast_copy_flags(&flags, _flags, AST_FLAGS_ALL);
-
-	/* Reset to initial state */
-	if (ast_test_flag(cdr, AST_CDR_FLAG_POST_DISABLED)) { /* But do NOT lose the NoCDR() setting */
-		ast_clear_flag(cdr, AST_FLAGS_ALL);
-		ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
-	} else {
-		ast_clear_flag(cdr, AST_FLAGS_ALL);
-	}
-
-	memset(&cdr->start, 0, sizeof(cdr->start));
-	memset(&cdr->end, 0, sizeof(cdr->end));
-	memset(&cdr->answer, 0, sizeof(cdr->answer));
-	cdr->billsec = 0;
-	cdr->duration = 0;
-	ast_cdr_start(cdr);
-	cdr->disposition = AST_CDR_NULL;
-}
-
-struct ast_cdr *ast_cdr_append(struct ast_cdr *cdr, struct ast_cdr *newcdr)
-{
-	struct ast_cdr *ret;
-
-	if (cdr) {
-		ret = cdr;
-
-		while (cdr->next)
-			cdr = cdr->next;
-		cdr->next = newcdr;
-	} else {
-		ret = newcdr;
-	}
 
-	return ret;
+	return 0;
 }
 
 /*! \note Don't call without cdr_batch_lock */
@@ -1313,8 +3153,8 @@ static int init_batch(void)
 
 static void *do_batch_backend_process(void *data)
 {
-	struct ast_cdr_batch_item *processeditem;
-	struct ast_cdr_batch_item *batchitem = data;
+	struct cdr_batch_item *processeditem;
+	struct cdr_batch_item *batchitem = data;
 
 	/* Push each CDR into storage mechanism(s) and free all the memory */
 	while (batchitem) {
@@ -1328,14 +3168,16 @@ static void *do_batch_backend_process(void *data)
 	return NULL;
 }
 
-void ast_cdr_submit_batch(int do_shutdown)
+static void cdr_submit_batch(int do_shutdown)
 {
-	struct ast_cdr_batch_item *oldbatchitems = NULL;
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct cdr_batch_item *oldbatchitems = NULL;
 	pthread_t batch_post_thread = AST_PTHREADT_NULL;
 
 	/* if there's no batch, or no CDRs in the batch, then there's nothing to do */
-	if (!batch || !batch->head)
+	if (!batch || !batch->head) {
 		return;
+	}
 
 	/* move the old CDRs aside, and prepare a new CDR batch */
 	ast_mutex_lock(&cdr_batch_lock);
@@ -1345,7 +3187,7 @@ void ast_cdr_submit_batch(int do_shutdown)
 
 	/* if configured, spawn a new thread to post these CDRs,
 	   also try to save as much as possible if we are shutting down safely */
-	if (batchscheduleronly || do_shutdown) {
+	if (ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) || do_shutdown) {
 		ast_debug(1, "CDR single-threaded batch processing begins now\n");
 		do_batch_backend_process(oldbatchitems);
 	} else {
@@ -1360,10 +3202,12 @@ void ast_cdr_submit_batch(int do_shutdown)
 
 static int submit_scheduled_batch(const void *data)
 {
-	ast_cdr_submit_batch(0);
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	cdr_submit_batch(0);
 	/* manually reschedule from this point in time */
+
 	ast_mutex_lock(&cdr_sched_lock);
-	cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
+	cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.size * 1000, submit_scheduled_batch, NULL);
 	ast_mutex_unlock(&cdr_sched_lock);
 	/* returning zero so the scheduler does not automatically reschedule */
 	return 0;
@@ -1386,25 +3230,26 @@ static void submit_unscheduled_batch(void)
 	ast_mutex_unlock(&cdr_pending_lock);
 }
 
-void ast_cdr_detach(struct ast_cdr *cdr)
+static void cdr_detach(struct ast_cdr *cdr)
 {
-	struct ast_cdr_batch_item *newtail;
+	struct cdr_batch_item *newtail;
 	int curr;
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 	int submit_batch = 0;
 
-	if (!cdr)
+	if (!cdr) {
 		return;
+	}
 
 	/* maybe they disabled CDR stuff completely, so just drop it */
-	if (!enabled) {
+	if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
 		ast_debug(1, "Dropping CDR !\n");
-		ast_set_flag(cdr, AST_CDR_FLAG_POST_DISABLED);
 		ast_cdr_free(cdr);
 		return;
 	}
 
 	/* post stuff immediately if we are not in batch mode, this is legacy behaviour */
-	if (!batchmode) {
+	if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
 		post_cdr(cdr);
 		ast_cdr_free(cdr);
 		return;
@@ -1436,7 +3281,7 @@ void ast_cdr_detach(struct ast_cdr *cdr)
 	curr = batch->size++;
 
 	/* if we have enough stuff to post, then do it */
-	if (curr >= (batchsize - 1)) {
+	if (curr >= (mod_cfg->general->batch_settings.size - 1)) {
 		submit_batch = 1;
 	}
 	ast_mutex_unlock(&cdr_batch_lock);
@@ -1473,11 +3318,40 @@ static void *do_cdr(void *data)
 	return NULL;
 }
 
+static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "cdr set debug [on|off]";
+		e->usage = "Enable or disable extra debugging in the CDR Engine";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!strcmp(a->argv[3], "on") && !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
+		ast_set_flag(&mod_cfg->general->settings, CDR_DEBUG);
+		ast_cli(a->fd, "CDR debugging enabled\n");
+	} else if (!strcmp(a->argv[3], "off") && ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
+		ast_clear_flag(&mod_cfg->general->settings, CDR_DEBUG);
+		ast_cli(a->fd, "CDR debugging disabled\n");
+	}
+
+	return CLI_SUCCESS;
+}
+
 static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct ast_cdr_beitem *beitem=NULL;
-	int cnt=0;
-	long nextbatchtime=0;
+	struct cdr_beitem *beitem = NULL;
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	int cnt = 0;
+	long nextbatchtime = 0;
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -1496,23 +3370,23 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
 	ast_cli(a->fd, "\n");
 	ast_cli(a->fd, "Call Detail Record (CDR) settings\n");
 	ast_cli(a->fd, "----------------------------------\n");
-	ast_cli(a->fd, "  Logging:                    %s\n", enabled ? "Enabled" : "Disabled");
-	ast_cli(a->fd, "  Mode:                       %s\n", batchmode ? "Batch" : "Simple");
-	if (enabled) {
-		ast_cli(a->fd, "  Log unanswered calls:       %s\n", unanswered ? "Yes" : "No");
-		ast_cli(a->fd, "  Log congestion:             %s\n\n", congestion ? "Yes" : "No");
-		if (batchmode) {
+	ast_cli(a->fd, "  Logging:                    %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) ? "Enabled" : "Disabled");
+	ast_cli(a->fd, "  Mode:                       %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE) ? "Batch" : "Simple");
+	if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+		ast_cli(a->fd, "  Log unanswered calls:       %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) ? "Yes" : "No");
+		ast_cli(a->fd, "  Log congestion:             %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION) ? "Yes" : "No");
+		if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
 			ast_cli(a->fd, "* Batch Mode Settings\n");
 			ast_cli(a->fd, "  -------------------\n");
 			if (batch)
 				cnt = batch->size;
 			if (cdr_sched > -1)
 				nextbatchtime = ast_sched_when(sched, cdr_sched);
-			ast_cli(a->fd, "  Safe shutdown:              %s\n", batchsafeshutdown ? "Enabled" : "Disabled");
-			ast_cli(a->fd, "  Threading model:            %s\n", batchscheduleronly ? "Scheduler only" : "Scheduler plus separate threads");
+			ast_cli(a->fd, "  Safe shutdown:              %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN) ? "Enabled" : "Disabled");
+			ast_cli(a->fd, "  Threading model:            %s\n", ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY) ? "Scheduler only" : "Scheduler plus separate threads");
 			ast_cli(a->fd, "  Current batch size:         %d record%s\n", cnt, ESS(cnt));
-			ast_cli(a->fd, "  Maximum batch size:         %d record%s\n", batchsize, ESS(batchsize));
-			ast_cli(a->fd, "  Maximum batch time:         %d second%s\n", batchtime, ESS(batchtime));
+			ast_cli(a->fd, "  Maximum batch size:         %d record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
+			ast_cli(a->fd, "  Maximum batch time:         %d second%s\n", mod_cfg->general->batch_settings.time, ESS(mod_cfg->general->batch_settings.time));
 			ast_cli(a->fd, "  Next batch processing time: %ld second%s\n\n", nextbatchtime, ESS(nextbatchtime));
 		}
 		ast_cli(a->fd, "* Registered Backends\n");
@@ -1555,148 +3429,163 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_
 
 static struct ast_cli_entry cli_submit = AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data");
 static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CDR status");
+static struct ast_cli_entry cli_debug = AST_CLI_DEFINE(handle_cli_debug, "Enable debugging");
 
-static void do_reload(int reload)
+
+/*!
+ * \brief This dispatches *all* \ref cdr_objects. It should only be used during
+ * shutdown, so that we get billing records for everything that we can.
+ */
+static int cdr_object_dispatch_all_cb(void *obj, void *arg, int flags)
 {
-	struct ast_config *config;
-	struct ast_variable *v;
-	int cfg_size;
-	int cfg_time;
-	int was_enabled;
-	int was_batchmode;
-	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+	struct cdr_object *cdr = obj;
+	struct cdr_object *it_cdr;
 
-	if ((config = ast_config_load2("cdr.conf", "cdr", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
-		return;
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
 	}
+	cdr_object_dispatch(cdr);
 
-	ast_mutex_lock(&cdr_batch_lock);
-
-	was_enabled = enabled;
-	was_batchmode = batchmode;
-
-	batchsize = BATCH_SIZE_DEFAULT;
-	batchtime = BATCH_TIME_DEFAULT;
-	batchscheduleronly = BATCH_SCHEDULER_ONLY_DEFAULT;
-	batchsafeshutdown = BATCH_SAFE_SHUTDOWN_DEFAULT;
-	enabled = ENABLED_DEFAULT;
-	batchmode = BATCHMODE_DEFAULT;
-	unanswered = UNANSWERED_DEFAULT;
-	congestion = CONGESTION_DEFAULT;
+	return 0;
+}
 
-	if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
-		ast_mutex_unlock(&cdr_batch_lock);
+static void finalize_batch_mode(void)
+{
+	if (cdr_thread == AST_PTHREADT_NULL) {
 		return;
 	}
+	/* wake up the thread so it will exit */
+	pthread_cancel(cdr_thread);
+	pthread_kill(cdr_thread, SIGURG);
+	pthread_join(cdr_thread, NULL);
+	cdr_thread = AST_PTHREADT_NULL;
+	ast_cond_destroy(&cdr_pending_cond);
+	ast_cli_unregister(&cli_submit);
+	ast_cdr_engine_term();
+}
 
-	/* don't run the next scheduled CDR posting while reloading */
-	ast_mutex_lock(&cdr_sched_lock);
-	AST_SCHED_DEL(sched, cdr_sched);
-	ast_mutex_unlock(&cdr_sched_lock);
+static int process_config(int reload)
+{
+	RAII_VAR(struct module_config *, mod_cfg, module_config_alloc(), ao2_cleanup);
 
-	for (v = ast_variable_browse(config, "general"); v; v = v->next) {
-		if (!strcasecmp(v->name, "enable")) {
-			enabled = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "unanswered")) {
-			unanswered = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "congestion")) {
-			congestion = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "batch")) {
-			batchmode = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "scheduleronly")) {
-			batchscheduleronly = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "safeshutdown")) {
-			batchsafeshutdown = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "size")) {
-			if (sscanf(v->value, "%30d", &cfg_size) < 1) {
-				ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
-			} else if (cfg_size < 0) {
-				ast_log(LOG_WARNING, "Invalid maximum batch size '%d' specified, using default\n", cfg_size);
-			} else {
-				batchsize = cfg_size;
-			}
-		} else if (!strcasecmp(v->name, "time")) {
-			if (sscanf(v->value, "%30d", &cfg_time) < 1) {
-				ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", v->value);
-			} else if (cfg_time < 0) {
-				ast_log(LOG_WARNING, "Invalid maximum batch time '%d' specified, using default\n", cfg_time);
-			} else {
-				batchtime = cfg_time;
-			}
-		} else if (!strcasecmp(v->name, "endbeforehexten")) {
-			ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN);
-		} else if (!strcasecmp(v->name, "initiatedseconds")) {
-			ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INITIATED_SECONDS);
+	if (!reload) {
+		if (aco_info_init(&cfg_info)) {
+			return 1;
 		}
-	}
-
-	if (enabled && !batchmode) {
-		ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
-	} else if (enabled && batchmode) {
-		ast_mutex_lock(&cdr_sched_lock);
-		cdr_sched = ast_sched_add(sched, batchtime * 1000, submit_scheduled_batch, NULL);
-		ast_mutex_unlock(&cdr_sched_lock);
-		ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize, batchtime);
-	} else {
-		ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
-	}
 
-	/* if this reload enabled the CDR batch mode, create the background thread
-	   if it does not exist */
-	if (enabled && batchmode && (!was_enabled || !was_batchmode) && (cdr_thread == AST_PTHREADT_NULL)) {
-		ast_cond_init(&cdr_pending_cond, NULL);
-		if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) {
-			ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
-			ast_mutex_lock(&cdr_sched_lock);
-			AST_SCHED_DEL(sched, cdr_sched);
-			ast_mutex_unlock(&cdr_sched_lock);
-		} else {
-			ast_cli_register(&cli_submit);
-			ast_register_atexit(ast_cdr_engine_term);
-		}
-	/* if this reload disabled the CDR and/or batch mode and there is a background thread,
-	   kill it */
-	} else if (((!enabled && was_enabled) || (!batchmode && was_batchmode)) && (cdr_thread != AST_PTHREADT_NULL)) {
-		/* wake up the thread so it will exit */
-		pthread_cancel(cdr_thread);
-		pthread_kill(cdr_thread, SIGURG);
-		pthread_join(cdr_thread, NULL);
-		cdr_thread = AST_PTHREADT_NULL;
-		ast_cond_destroy(&cdr_pending_cond);
-		ast_cli_unregister(&cli_submit);
-		ast_unregister_atexit(ast_cdr_engine_term);
-		/* if leaving batch mode, then post the CDRs in the batch,
-		   and don't reschedule, since we are stopping CDR logging */
-		if (!batchmode && was_batchmode) {
-			ast_cdr_engine_term();
+		aco_option_register(&cfg_info, "enable", ACO_EXACT, general_options, DEFAULT_ENABLED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_ENABLED);
+		aco_option_register(&cfg_info, "debug", ACO_EXACT, general_options, 0, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_DEBUG);
+		aco_option_register(&cfg_info, "unanswered", ACO_EXACT, general_options, DEFAULT_UNANSWERED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_UNANSWERED);
+		aco_option_register(&cfg_info, "congestion", ACO_EXACT, general_options, 0, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_CONGESTION);
+		aco_option_register(&cfg_info, "batch", ACO_EXACT, general_options, DEFAULT_BATCHMODE, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_BATCHMODE);
+		aco_option_register(&cfg_info, "endbeforehexten", ACO_EXACT, general_options, DEFAULT_END_BEFORE_H_EXTEN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_END_BEFORE_H_EXTEN);
+		aco_option_register(&cfg_info, "initiatedseconds", ACO_EXACT, general_options, DEFAULT_INITIATED_SECONDS, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_INITIATED_SECONDS);
+		aco_option_register(&cfg_info, "scheduleronly", ACO_EXACT, general_options, DEFAULT_BATCH_SCHEDULER_ONLY, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SCHEDULER_ONLY);
+		aco_option_register(&cfg_info, "safeshutdown", ACO_EXACT, general_options, DEFAULT_BATCH_SAFE_SHUTDOWN, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, batch_settings.settings), BATCH_MODE_SAFE_SHUTDOWN);
+		aco_option_register(&cfg_info, "size", ACO_EXACT, general_options, DEFAULT_BATCH_SIZE, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.size), 0, MAX_BATCH_SIZE);
+		aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 0, MAX_BATCH_TIME);
+	}
+
+	if (aco_process_config(&cfg_info, reload)) {
+		if (!mod_cfg) {
+			return 1;
+		}
+		/* If we couldn't process the configuration and this wasn't a reload,
+		 * create a default config
+		 */
+		if (!reload && !(aco_set_defaults(&general_option, "general", mod_cfg->general))) {
+			ast_log(LOG_NOTICE, "Failed to process CDR configuration; using defaults\n");
+			ao2_global_obj_replace(module_configs, mod_cfg);
+			return 0;
 		}
+		return 1;
 	}
 
-	ast_mutex_unlock(&cdr_batch_lock);
-	ast_config_destroy(config);
+	if (reload) {
+		manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: CDR\r\nMessage: CDR subsystem reload requested\r\n");
+	}
+	return 0;
 }
 
 static void cdr_engine_shutdown(void)
 {
-	if (cdr_thread != AST_PTHREADT_NULL) {
-		/* wake up the thread so it will exit */
-		pthread_cancel(cdr_thread);
-		pthread_kill(cdr_thread, SIGURG);
-		pthread_join(cdr_thread, NULL);
-		cdr_thread = AST_PTHREADT_NULL;
-		ast_cond_destroy(&cdr_pending_cond);
-	}
-	ast_cli_unregister(&cli_submit);
-
+	ao2_callback(active_cdrs_by_channel, OBJ_NODATA, cdr_object_dispatch_all_cb,
+		NULL);
+	finalize_batch_mode();
+	aco_info_destroy(&cfg_info);
 	ast_cli_unregister(&cli_status);
+	ast_cli_unregister(&cli_debug);
 	ast_sched_context_destroy(sched);
 	sched = NULL;
 	ast_free(batch);
 	batch = NULL;
+
+	ao2_ref(active_cdrs_by_channel, -1);
+	ao2_ref(active_cdrs_by_bridge, -1);
+}
+
+static void cdr_enable_batch_mode(struct ast_cdr_config *config)
+{
+	SCOPED_LOCK(batch, &cdr_batch_lock, ast_mutex_lock, ast_mutex_unlock);
+
+	/* Only create the thread level portions once */
+	if (cdr_thread == AST_PTHREADT_NULL) {
+		ast_cond_init(&cdr_pending_cond, NULL);
+		if (ast_pthread_create_background(&cdr_thread, NULL, do_cdr, NULL) < 0) {
+			ast_log(LOG_ERROR, "Unable to start CDR thread.\n");
+			return;
+		}
+		ast_cli_register(&cli_submit);
+	}
+
+	/* Kill the currently scheduled item */
+	AST_SCHED_DEL(sched, cdr_sched);
+	cdr_sched = ast_sched_add(sched, config->batch_settings.time * 1000, submit_scheduled_batch, NULL);
+	ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n",
+			config->batch_settings.size, config->batch_settings.time);
 }
 
 int ast_cdr_engine_init(void)
 {
+	RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
+
+	if (process_config(0)) {
+		return -1;
+	}
+
+	/* The prime here should be the same as the channel container */
+	active_cdrs_by_channel = ao2_container_alloc(51, cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn);
+	if (!active_cdrs_by_channel) {
+		return -1;
+	}
+
+	active_cdrs_by_bridge = ao2_container_alloc(51, cdr_object_bridge_hash_fn, cdr_object_bridge_cmp_fn);
+	if (!active_cdrs_by_bridge) {
+		return -1;
+	}
+
+	cdr_topic = stasis_topic_create("cdr_engine");
+	if (!cdr_topic) {
+		return -1;
+	}
+
+	channel_subscription = stasis_forward_all(stasis_caching_get_topic(ast_channel_topic_all_cached()), cdr_topic);
+	if (!channel_subscription) {
+		return -1;
+	}
+	bridge_subscription = stasis_forward_all(stasis_caching_get_topic(ast_bridge_topic_all_cached()), cdr_topic);
+	if (!bridge_subscription) {
+		return -1;
+	}
+	stasis_router = stasis_message_router_create(cdr_topic);
+	if (!stasis_router) {
+		return -1;
+	}
+	stasis_message_router_add(stasis_router, stasis_cache_update_type(), handle_channel_cache_message, NULL);
+	stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
+	stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
+	stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
+
 	sched = ast_sched_context_create();
 	if (!sched) {
 		ast_log(LOG_ERROR, "Unable to create schedule context.\n");
@@ -1704,69 +3593,70 @@ int ast_cdr_engine_init(void)
 	}
 
 	ast_cli_register(&cli_status);
-	do_reload(0);
+	ast_cli_register(&cli_debug);
 	ast_register_atexit(cdr_engine_shutdown);
 
+	mod_cfg = ao2_global_obj_ref(module_configs);
+
+	if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+		if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+			cdr_enable_batch_mode(mod_cfg->general);
+		} else {
+			ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
+		}
+	} else {
+		ast_log(LOG_NOTICE, "CDR logging disabled.\n");
+	}
+
 	return 0;
 }
 
-/* \note This actually gets called a couple of times at shutdown.  Once, before we start
-   hanging up channels, and then again, after the channel hangup timeout expires */
 void ast_cdr_engine_term(void)
 {
-	ast_cdr_submit_batch(batchsafeshutdown);
-}
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 
-int ast_cdr_engine_reload(void)
-{
-	do_reload(1);
-	return 0;
+	/* Since this is called explicitly during process shutdown, we might not have ever
+	 * been initialized. If so, the config object will be NULL.
+	 */
+	if (!mod_cfg) {
+		return;
+	}
+	if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+		return;
+	}
+	cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
 }
 
-int ast_cdr_data_add_structure(struct ast_data *tree, struct ast_cdr *cdr, int recur)
+int ast_cdr_engine_reload(void)
 {
-	struct ast_cdr *tmpcdr;
-	struct ast_data *level;
-	struct ast_var_t *variables;
-	const char *var, *val;
-	int x = 1, i;
-	char workspace[256];
-	char *tmp;
+	RAII_VAR(struct module_config *, old_mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
 
-	if (!cdr) {
+	if (process_config(1)) {
 		return -1;
 	}
 
-	for (tmpcdr = cdr; tmpcdr; tmpcdr = (recur ? tmpcdr->next : NULL)) {
-		level = ast_data_add_node(tree, "level");
-		if (!level) {
-			continue;
-		}
-
-		ast_data_add_int(level, "level_number", x);
+	mod_cfg = ao2_global_obj_ref(module_configs);
 
-		AST_LIST_TRAVERSE(&tmpcdr->varshead, variables, entries) {
-			if (variables && (var = ast_var_name(variables)) &&
-					(val = ast_var_value(variables)) && !ast_strlen_zero(var)
-					&& !ast_strlen_zero(val)) {
-				ast_data_add_str(level, var, val);
-			} else {
-				break;
-			}
+	if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED) ||
+			!(ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE))) {
+		/* If batch mode used to be enabled, finalize the batch */
+		if (ast_test_flag(&old_mod_cfg->general->settings, CDR_BATCHMODE)) {
+			finalize_batch_mode();
 		}
+	}
 
-		for (i = 0; cdr_readonly_vars[i]; i++) {
-			workspace[0] = 0; /* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
-			ast_cdr_getvar(tmpcdr, cdr_readonly_vars[i], &tmp, workspace, sizeof(workspace), 0, 0);
-			if (!tmp) {
-				continue;
-			}
-			ast_data_add_str(level, cdr_readonly_vars[i], tmp);
+	if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+		if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+			ast_log(LOG_NOTICE, "CDR simple logging enabled.\n");
+		} else {
+			cdr_enable_batch_mode(mod_cfg->general);
 		}
-
-		x++;
+	} else {
+		ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
 	}
 
 	return 0;
 }
 
+
diff --git a/main/cel.c b/main/cel.c
index ff74ed1efeb0f75fa0e3058cdb843d1266f1f420..8110b116eb66df2dcd019a19ff4fac30ccff0dc6 100644
--- a/main/cel.c
+++ b/main/cel.c
@@ -433,16 +433,6 @@ static int dialstatus_cmp(void *obj, void *arg, int flags)
 	return !strcmp(blob1_id, blob2_id) ? CMP_MATCH | CMP_STOP : 0;
 }
 
-/*!
- * \brief Map of ast_cel_ama_flags to strings
- */
-static const char * const cel_ama_flags[AST_CEL_AMA_FLAG_TOTAL] = {
-	[AST_CEL_AMA_FLAG_NONE]          = "NONE",
-	[AST_CEL_AMA_FLAG_OMIT]          = "OMIT",
-	[AST_CEL_AMA_FLAG_BILLING]       = "BILLING",
-	[AST_CEL_AMA_FLAG_DOCUMENTATION] = "DOCUMENTATION",
-};
-
 unsigned int ast_cel_check_enabled(void)
 {
 	RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
@@ -625,16 +615,6 @@ const char *ast_cel_get_type_name(enum ast_cel_event_type type)
 	return S_OR(cel_event_types[type], "Unknown");
 }
 
-const char *ast_cel_get_ama_flag_name(enum ast_cel_ama_flag flag)
-{
-	if (flag < 0 || flag >= ARRAY_LEN(cel_ama_flags)) {
-		ast_log(LOG_WARNING, "Invalid AMA flag: %d\n", flag);
-		return "Unknown";
-	}
-
-	return S_OR(cel_ama_flags[flag], "Unknown");
-}
-
 static int cel_track_app(const char *const_app)
 {
 	RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
diff --git a/main/channel.c b/main/channel.c
index 86a8f4994599b34881ae564b7d743506f5bcf415..89ab9ca36dddc8c87f5f8f0133b05bcb8881a75c 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -104,7 +104,6 @@ struct ast_epoll_data {
 /*! \brief Prevent new channel allocation if shutting down. */
 static int shutting_down;
 
-static int uniqueint;
 static int chancount;
 
 unsigned long global_fin, global_fout;
@@ -116,6 +115,8 @@ AST_THREADSTORAGE(state2str_threadbuf);
  *  100ms */
 #define AST_DEFAULT_EMULATE_DTMF_DURATION 100
 
+#define DEFAULT_AMA_FLAGS AST_AMA_DOCUMENTATION
+
 /*! Minimum amount of time between the end of the last digit and the beginning
  *  of a new one - 45ms */
 #define AST_MIN_DTMF_GAP 45
@@ -984,7 +985,7 @@ static void ast_dummy_channel_destructor(void *obj);
 static struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 0)))
 __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char *cid_name,
 		       const char *acctcode, const char *exten, const char *context,
-		       const char *linkedid, const int amaflag, const char *file, int line,
+		       const char *linkedid, enum ama_flags amaflag, const char *file, int line,
 		       const char *function, const char *name_fmt, va_list ap)
 {
 	struct ast_channel *tmp;
@@ -1001,7 +1002,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 		return NULL;
 	}
 
-	if (!(tmp = ast_channel_internal_alloc(ast_channel_destructor))) {
+	if (!(tmp = ast_channel_internal_alloc(ast_channel_destructor, linkedid))) {
 		/* Channel structure allocation failure. */
 		return NULL;
 	}
@@ -1078,20 +1079,6 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 	now = ast_tvnow();
 	ast_channel_creationtime_set(tmp, &now);
 
-	if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
-		ast_channel_uniqueid_build(tmp, "%li.%d", (long) time(NULL),
-			ast_atomic_fetchadd_int(&uniqueint, 1));
-	} else {
-		ast_channel_uniqueid_build(tmp, "%s-%li.%d", ast_config_AST_SYSTEM_NAME,
-			(long) time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1));
-	}
-
-	if (!ast_strlen_zero(linkedid)) {
-		ast_channel_linkedid_set(tmp, linkedid);
-	} else {
-		ast_channel_linkedid_set(tmp, ast_channel_uniqueid(tmp));
-	}
-
 	ast_channel_internal_setup_topics(tmp);
 
 	if (!ast_strlen_zero(name_fmt)) {
@@ -1123,25 +1110,20 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 	/* Reminder for the future: under what conditions do we NOT want to track cdrs on channels? */
 
 	/* These 4 variables need to be set up for the cdr_init() to work right */
-	if (amaflag) {
+	if (amaflag != AST_AMA_NONE) {
 		ast_channel_amaflags_set(tmp, amaflag);
 	} else {
-		ast_channel_amaflags_set(tmp, ast_default_amaflags);
+		ast_channel_amaflags_set(tmp, DEFAULT_AMA_FLAGS);
 	}
 
-	if (!ast_strlen_zero(acctcode))
+	if (!ast_strlen_zero(acctcode)) {
 		ast_channel_accountcode_set(tmp, acctcode);
-	else
-		ast_channel_accountcode_set(tmp, ast_default_accountcode);
+	}
 
 	ast_channel_context_set(tmp, S_OR(context, "default"));
 	ast_channel_exten_set(tmp, S_OR(exten, "s"));
 	ast_channel_priority_set(tmp, 1);
 
-	ast_channel_cdr_set(tmp, ast_cdr_alloc());
-	ast_cdr_init(ast_channel_cdr(tmp), tmp);
-	ast_cdr_start(ast_channel_cdr(tmp));
-
 	ast_atomic_fetchadd_int(&chancount, +1);
 
 	headp = ast_channel_varshead(tmp);
@@ -1165,7 +1147,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 	 * a lot of data into this func to do it here!
 	 */
 	if (ast_get_channel_tech(tech) || (tech2 && ast_get_channel_tech(tech2))) {
-		ast_publish_channel_state(tmp);
+		ast_channel_publish_snapshot(tmp);
 	}
 
 	ast_channel_internal_finalize(tmp);
@@ -1176,7 +1158,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *cid_num,
 					const char *cid_name, const char *acctcode,
 					const char *exten, const char *context,
-					const char *linkedid, const int amaflag,
+					const char *linkedid, const enum ama_flags amaflag,
 					const char *file, int line, const char *function,
 					const char *name_fmt, ...)
 {
@@ -1202,7 +1184,7 @@ struct ast_channel *ast_dummy_channel_alloc(void)
 	struct ast_channel *tmp;
 	struct varshead *headp;
 
-	if (!(tmp = ast_channel_internal_alloc(ast_dummy_channel_destructor))) {
+	if (!(tmp = ast_channel_internal_alloc(ast_dummy_channel_destructor, NULL))) {
 		/* Dummy channel structure allocation failure. */
 		return NULL;
 	}
@@ -2470,7 +2452,7 @@ static void ast_channel_destructor(void *obj)
 	ast_jb_destroy(chan);
 
 	if (ast_channel_cdr(chan)) {
-		ast_cdr_discard(ast_channel_cdr(chan));
+		ast_cdr_free(ast_channel_cdr(chan));
 		ast_channel_cdr_set(chan, NULL);
 	}
 
@@ -2530,7 +2512,7 @@ static void ast_dummy_channel_destructor(void *obj)
 		ast_var_delete(vardata);
 
 	if (ast_channel_cdr(chan)) {
-		ast_cdr_discard(ast_channel_cdr(chan));
+		ast_cdr_free(ast_channel_cdr(chan));
 		ast_channel_cdr_set(chan, NULL);
 	}
 
@@ -2884,26 +2866,17 @@ int ast_hangup(struct ast_channel *chan)
 
 	ast_cc_offer(chan);
 
-	ast_publish_channel_state(chan);
-
-	if (ast_channel_cdr(chan) && !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_BRIDGED) &&
-		!ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_POST_DISABLED) &&
-		(ast_channel_cdr(chan)->disposition != AST_CDR_NULL || ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED))) {
-		ast_channel_lock(chan);
-		ast_cdr_end(ast_channel_cdr(chan));
-		ast_cdr_detach(ast_channel_cdr(chan));
-		ast_channel_cdr_set(chan, NULL);
-		ast_channel_unlock(chan);
-	}
+	ast_channel_publish_snapshot(chan);
 
 	ast_channel_unref(chan);
 
 	return 0;
 }
 
-int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
+int ast_raw_answer(struct ast_channel *chan)
 {
 	int res = 0;
+	struct timeval answertime;
 
 	ast_channel_lock(chan);
 
@@ -2919,6 +2892,9 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
 		return -1;
 	}
 
+	answertime = ast_tvnow();
+	ast_channel_answertime_set(chan, &answertime);
+
 	ast_channel_unlock(chan);
 
 	switch (ast_channel_state(chan)) {
@@ -2929,18 +2905,9 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
 			res = ast_channel_tech(chan)->answer(chan);
 		}
 		ast_setstate(chan, AST_STATE_UP);
-		if (cdr_answer) {
-			ast_cdr_answer(ast_channel_cdr(chan));
-		}
 		ast_channel_unlock(chan);
 		break;
 	case AST_STATE_UP:
-		/* Calling ast_cdr_answer when it it has previously been called
-		 * is essentially a no-op, so it is safe.
-		 */
-		if (cdr_answer) {
-			ast_cdr_answer(ast_channel_cdr(chan));
-		}
 		break;
 	default:
 		break;
@@ -2951,13 +2918,13 @@ int ast_raw_answer(struct ast_channel *chan, int cdr_answer)
 	return res;
 }
 
-int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
+int __ast_answer(struct ast_channel *chan, unsigned int delay)
 {
 	int res = 0;
 	enum ast_channel_state old_state;
 
 	old_state = ast_channel_state(chan);
-	if ((res = ast_raw_answer(chan, cdr_answer))) {
+	if ((res = ast_raw_answer(chan))) {
 		return res;
 	}
 
@@ -3059,7 +3026,27 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
 
 int ast_answer(struct ast_channel *chan)
 {
-	return __ast_answer(chan, 0, 1);
+	return __ast_answer(chan, 0);
+}
+
+int ast_channel_get_duration(struct ast_channel *chan)
+{
+	ast_assert(NULL != chan);
+
+	if (ast_tvzero(ast_channel_creationtime(chan))) {
+		return 0;
+	}
+	return (ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(chan)) / 1000);
+}
+
+int ast_channel_get_up_time(struct ast_channel *chan)
+{
+	ast_assert(NULL != chan);
+
+	if (ast_tvzero(ast_channel_answertime(chan))) {
+		return 0;
+	}
+	return (ast_tvdiff_ms(ast_tvnow(), ast_channel_answertime(chan)) / 1000);
 }
 
 void ast_deactivate_generator(struct ast_channel *chan)
@@ -4472,6 +4459,33 @@ void ast_channel_hangupcause_hash_set(struct ast_channel *chan, const struct ast
 	}
 }
 
+enum ama_flags ast_channel_string2amaflag(const char *flag)
+{
+	if (!strcasecmp(flag, "default"))
+		return DEFAULT_AMA_FLAGS;
+	if (!strcasecmp(flag, "omit"))
+		return AST_AMA_OMIT;
+	if (!strcasecmp(flag, "billing"))
+		return AST_AMA_BILLING;
+	if (!strcasecmp(flag, "documentation"))
+		return AST_AMA_DOCUMENTATION;
+	return AST_AMA_NONE;
+}
+
+const char *ast_channel_amaflags2string(enum ama_flags flag)
+{
+	switch (flag) {
+	case AST_AMA_OMIT:
+		return "OMIT";
+	case AST_AMA_BILLING:
+		return "BILLING";
+	case AST_AMA_DOCUMENTATION:
+		return "DOCUMENTATION";
+	default:
+		return "Unknown";
+	}
+}
+
 int ast_indicate_data(struct ast_channel *chan, int _condition,
 		const void *data, size_t datalen)
 {
@@ -5625,15 +5639,15 @@ struct ast_channel *ast_call_forward(struct ast_channel *caller, struct ast_chan
 		}
 		if (oh->account) {
 			ast_channel_lock(new_chan);
-			ast_cdr_setaccount(new_chan, oh->account);
+			ast_channel_accountcode_set(new_chan, oh->account);
 			ast_channel_unlock(new_chan);
 		}
 	} else if (caller) { /* no outgoing helper so use caller if avaliable */
 		call_forward_inherit(new_chan, caller, orig);
 	}
+	ast_set_flag(ast_channel_flags(new_chan), AST_FLAG_ORIGINATED);
 
 	ast_channel_lock_both(orig, new_chan);
-	ast_copy_flags(ast_channel_cdr(new_chan), ast_channel_cdr(orig), AST_CDR_FLAG_ORIGINATED);
 	ast_channel_accountcode_set(new_chan, ast_channel_accountcode(orig));
 	ast_party_connected_line_copy(ast_channel_connected(new_chan), ast_channel_connected(orig));
 	ast_party_redirecting_copy(ast_channel_redirecting(new_chan), ast_channel_redirecting(orig));
@@ -5699,7 +5713,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
 		}
 		if (oh->account) {
 			ast_channel_lock(chan);
-			ast_cdr_setaccount(chan, oh->account);
+			ast_channel_accountcode_set(chan, oh->account);
 			ast_channel_unlock(chan);
 		}
 	}
@@ -5714,7 +5728,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
 	 */
 	ast_set_callerid(chan, cid_num, cid_name, cid_num);
 
-	ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_ORIGINATED);
+	ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED);
 	ast_party_connected_line_set_init(&connected, ast_channel_connected(chan));
 	if (cid_num) {
 		connected.id.number.valid = 1;
@@ -5764,25 +5778,21 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
 					break;
 
 				case AST_CONTROL_BUSY:
-					ast_cdr_busy(ast_channel_cdr(chan));
 					*outstate = f->subclass.integer;
 					timeout = 0;
 					break;
 
 				case AST_CONTROL_INCOMPLETE:
-					ast_cdr_failed(ast_channel_cdr(chan));
 					*outstate = AST_CONTROL_CONGESTION;
 					timeout = 0;
 					break;
 
 				case AST_CONTROL_CONGESTION:
-					ast_cdr_failed(ast_channel_cdr(chan));
 					*outstate = f->subclass.integer;
 					timeout = 0;
 					break;
 
 				case AST_CONTROL_ANSWER:
-					ast_cdr_answer(ast_channel_cdr(chan));
 					*outstate = f->subclass.integer;
 					timeout = 0;		/* trick to force exit from the while() */
 					break;
@@ -5833,28 +5843,10 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
 		*outstate = AST_CONTROL_ANSWER;
 
 	if (res <= 0) {
-		struct ast_cdr *chancdr;
 		ast_channel_lock(chan);
 		if (AST_CONTROL_RINGING == last_subclass) {
 			ast_channel_hangupcause_set(chan, AST_CAUSE_NO_ANSWER);
 		}
-		if (!ast_channel_cdr(chan) && (chancdr = ast_cdr_alloc())) {
-			ast_channel_cdr_set(chan, chancdr);
-			ast_cdr_init(ast_channel_cdr(chan), chan);
-		}
-		if (ast_channel_cdr(chan)) {
-			char tmp[256];
-
-			snprintf(tmp, sizeof(tmp), "%s/%s", type, addr);
-			ast_cdr_setapp(ast_channel_cdr(chan), "Dial", tmp);
-			ast_cdr_update(chan);
-			ast_cdr_start(ast_channel_cdr(chan));
-			ast_cdr_end(ast_channel_cdr(chan));
-			/* If the cause wasn't handled properly */
-			if (ast_cdr_disposition(ast_channel_cdr(chan), ast_channel_hangupcause(chan))) {
-				ast_cdr_failed(ast_channel_cdr(chan));
-			}
-		}
 		ast_channel_unlock(chan);
 		ast_hangup(chan);
 		chan = NULL;
@@ -6024,9 +6016,6 @@ int ast_call(struct ast_channel *chan, const char *addr, int timeout)
 	/* Stop if we're a zombie or need a soft hangup */
 	ast_channel_lock(chan);
 	if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan)) {
-		if (ast_channel_cdr(chan)) {
-			ast_set_flag(ast_channel_cdr(chan), AST_CDR_FLAG_DIALED);
-		}
 		if (ast_channel_tech(chan)->call)
 			res = ast_channel_tech(chan)->call(chan, addr, timeout);
 		ast_set_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
@@ -6517,32 +6506,20 @@ static void clone_variables(struct ast_channel *original, struct ast_channel *cl
 	}
 }
 
-
-
-/* return the oldest of two linkedids.  linkedid is derived from
-   uniqueid which is formed like this: [systemname-]ctime.seq
-
-   The systemname, and the dash are optional, followed by the epoch
-   time followed by an integer sequence.  Note that this is not a
-   decimal number, since 1.2 is less than 1.11 in uniqueid land.
-
-   To compare two uniqueids, we parse out the integer values of the
-   time and the sequence numbers and compare them, with time trumping
-   sequence.
-*/
-static const char *oldest_linkedid(const char *a, const char *b)
+const char *ast_channel_oldest_linkedid(const char *a, const char *b)
 {
 	const char *satime, *saseq;
 	const char *sbtime, *sbseq;
 	const char *dash;
-
 	unsigned int atime, aseq, btime, bseq;
 
-	if (ast_strlen_zero(a))
+	if (ast_strlen_zero(a)) {
 		return b;
+	}
 
-	if (ast_strlen_zero(b))
+	if (ast_strlen_zero(b)) {
 		return a;
+	}
 
 	satime = a;
 	sbtime = b;
@@ -6558,8 +6535,9 @@ static const char *oldest_linkedid(const char *a, const char *b)
 	/* the sequence comes after the '.' */
 	saseq = strchr(satime, '.');
 	sbseq = strchr(sbtime, '.');
-	if (!saseq || !sbseq)
+	if (!saseq || !sbseq) {
 		return NULL;
+	}
 	saseq++;
 	sbseq++;
 
@@ -6578,122 +6556,6 @@ static const char *oldest_linkedid(const char *a, const char *b)
 	}
 }
 
-/*! Set the channel's linkedid to the given string, and also check to
- *  see if the channel's old linkedid is now being retired */
-static void ast_channel_change_linkedid(struct ast_channel *chan, const char *linkedid)
-{
-	ast_assert(linkedid != NULL);
-	/* if the linkedid for this channel is being changed from something, check... */
-	if (ast_channel_linkedid(chan) && !strcmp(ast_channel_linkedid(chan), linkedid)) {
-		return;
-	}
-
-	ast_channel_linkedid_set(chan, linkedid);
-	ast_cel_linkedid_ref(linkedid);
-}
-
-/*! \brief Propagate the oldest linkedid between associated channels */
-void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer)
-{
-	const char* linkedid=NULL;
-	struct ast_channel *bridged;
-
-/*
- * BUGBUG this needs to be updated to not use ast_channel_internal_bridged_channel().
- * BUGBUG this needs to be updated to not use ast_bridged_channel().
- *
- * We may be better off making this a function of the bridging
- * framework.  Essentially, as each channel joins a bridge, the
- * oldest linkedid should be propagated between all pairs of
- * channels.  This should be handled by bridging (unless you're
- * in an infinite wait bridge...) just like the BRIDGEPEER
- * channel variable.
- *
- * This is currently called in two places:
- *
- * (1) In channel masquerade. To some extent this shouldn't
- * really be done any longer - we don't really want a channel to
- * have its linkedid change, even if it replaces a channel that
- * had an older linkedid.  The two channels aren't really
- * 'related', they're simply swapping with each other.
- *
- * (2) In features.c as two channels are bridged.
- */
-	linkedid = oldest_linkedid(ast_channel_linkedid(chan), ast_channel_linkedid(peer));
-	linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(chan));
-	linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(peer));
-	if (ast_channel_internal_bridged_channel(chan)) {
-		bridged = ast_bridged_channel(chan);
-		if (bridged && bridged != peer) {
-			linkedid = oldest_linkedid(linkedid, ast_channel_linkedid(bridged));
-			linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(bridged));
-		}
-	}
-	if (ast_channel_internal_bridged_channel(peer)) {
-		bridged = ast_bridged_channel(peer);
-		if (bridged && bridged != chan) {
-			linkedid = oldest_linkedid(linkedid, ast_channel_linkedid(bridged));
-			linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(bridged));
-		}
-	}
-
-	/* just in case setting a stringfield to itself causes problems */
-	linkedid = ast_strdupa(linkedid);
-
-	ast_channel_change_linkedid(chan, linkedid);
-	ast_channel_change_linkedid(peer, linkedid);
-	if (ast_channel_internal_bridged_channel(chan)) {
-		bridged = ast_bridged_channel(chan);
-		if (bridged && bridged != peer) {
-			ast_channel_change_linkedid(bridged, linkedid);
-		}
-	}
-	if (ast_channel_internal_bridged_channel(peer)) {
-		bridged = ast_bridged_channel(peer);
-		if (bridged && bridged != chan) {
-			ast_channel_change_linkedid(bridged, linkedid);
-		}
-	}
-}
-
-#if 0	//BUGBUG setting up peeraccount needs to be removed.
-/* copy accountcode and peeraccount across during a link */
-static void ast_set_owners_and_peers(struct ast_channel *chan1,
-									 struct ast_channel *chan2)
-{
-	if (!ast_strlen_zero(ast_channel_accountcode(chan1)) && ast_strlen_zero(ast_channel_peeraccount(chan2))) {
-		ast_debug(1, "setting peeraccount to %s for %s from data on channel %s\n",
-				ast_channel_accountcode(chan1), ast_channel_name(chan2), ast_channel_name(chan1));
-		ast_channel_peeraccount_set(chan2, ast_channel_accountcode(chan1));
-	}
-	if (!ast_strlen_zero(ast_channel_accountcode(chan2)) && ast_strlen_zero(ast_channel_peeraccount(chan1))) {
-		ast_debug(1, "setting peeraccount to %s for %s from data on channel %s\n",
-				ast_channel_accountcode(chan2), ast_channel_name(chan1), ast_channel_name(chan2));
-		ast_channel_peeraccount_set(chan1, ast_channel_accountcode(chan2));
-	}
-	if (!ast_strlen_zero(ast_channel_peeraccount(chan1)) && ast_strlen_zero(ast_channel_accountcode(chan2))) {
-		ast_debug(1, "setting accountcode to %s for %s from data on channel %s\n",
-				ast_channel_peeraccount(chan1), ast_channel_name(chan2), ast_channel_name(chan1));
-		ast_channel_accountcode_set(chan2, ast_channel_peeraccount(chan1));
-	}
-	if (!ast_strlen_zero(ast_channel_peeraccount(chan2)) && ast_strlen_zero(ast_channel_accountcode(chan1))) {
-		ast_debug(1, "setting accountcode to %s for %s from data on channel %s\n",
-				ast_channel_peeraccount(chan2), ast_channel_name(chan1), ast_channel_name(chan2));
-		ast_channel_accountcode_set(chan1, ast_channel_peeraccount(chan2));
-	}
-	if (0 != strcmp(ast_channel_accountcode(chan1), ast_channel_peeraccount(chan2))) {
-		ast_debug(1, "changing peeraccount from %s to %s on %s to match channel %s\n",
-				ast_channel_peeraccount(chan2), ast_channel_peeraccount(chan1), ast_channel_name(chan2), ast_channel_name(chan1));
-		ast_channel_peeraccount_set(chan2, ast_channel_accountcode(chan1));
-	}
-	if (0 != strcmp(ast_channel_accountcode(chan2), ast_channel_peeraccount(chan1))) {
-		ast_debug(1, "changing peeraccount from %s to %s on %s to match channel %s\n",
-				ast_channel_peeraccount(chan1), ast_channel_peeraccount(chan2), ast_channel_name(chan1), ast_channel_name(chan2));
-		ast_channel_peeraccount_set(chan1, ast_channel_accountcode(chan2));
-	}
-}
-#endif	//BUGBUG
-
 /*!
  * \internal
  * \brief Transfer COLP between target and transferee channels.
@@ -6775,7 +6637,6 @@ void ast_do_masquerade(struct ast_channel *original)
 	} exchange;
 	struct ast_channel *clonechan, *chans[2];
 	struct ast_channel *bridged;
-	struct ast_cdr *cdr;
 	struct ast_datastore *xfer_ds;
 	struct xfer_masquerade_ds *xfer_colp;
 	struct ast_format rformat;
@@ -6943,9 +6804,6 @@ void ast_do_masquerade(struct ast_channel *original)
 	snprintf(tmp_name, sizeof(tmp_name), "%s<ZOMBIE>", ast_channel_name(clonechan)); /* quick, hide the brains! */
 	__ast_change_name_nolink(clonechan, tmp_name);
 
-	/* share linked id's */
-	ast_channel_set_linkgroup(original, clonechan);
-
 	/* Swap the technologies */
 	t = ast_channel_tech(original);
 	ast_channel_tech_set(original, ast_channel_tech(clonechan));
@@ -6955,11 +6813,6 @@ void ast_do_masquerade(struct ast_channel *original)
 	ast_channel_tech_pvt_set(original, ast_channel_tech_pvt(clonechan));
 	ast_channel_tech_pvt_set(clonechan, t_pvt);
 
-	/* Swap the cdrs */
-	cdr = ast_channel_cdr(original);
-	ast_channel_cdr_set(original, ast_channel_cdr(clonechan));
-	ast_channel_cdr_set(clonechan, cdr);
-
 	/* Swap the alertpipes */
 	ast_channel_internal_alertpipe_swap(original, clonechan);
 
@@ -7098,7 +6951,7 @@ void ast_do_masquerade(struct ast_channel *original)
 	ast_channel_redirecting_set(original, ast_channel_redirecting(clonechan));
 	ast_channel_redirecting_set(clonechan, &exchange.redirecting);
 
-	ast_publish_channel_state(original);
+	ast_channel_publish_snapshot(original);
 
 	/* Restore original timing file descriptor */
 	ast_channel_set_fd(original, AST_TIMING_FD, ast_channel_timingfd(original));
@@ -7257,11 +7110,8 @@ void ast_set_callerid(struct ast_channel *chan, const char *cid_num, const char
 		ast_free(ast_channel_caller(chan)->ani.number.str);
 		ast_channel_caller(chan)->ani.number.str = ast_strdup(cid_ani);
 	}
-	if (ast_channel_cdr(chan)) {
-		ast_cdr_setcid(ast_channel_cdr(chan), chan);
-	}
 
-	ast_publish_channel_state(chan);
+	ast_channel_publish_snapshot(chan);
 
 	ast_channel_unlock(chan);
 }
@@ -7287,10 +7137,7 @@ void ast_channel_set_caller_event(struct ast_channel *chan, const struct ast_par
 
 	ast_channel_lock(chan);
 	ast_party_caller_set(ast_channel_caller(chan), caller, update);
-	ast_publish_channel_state(chan);
-	if (ast_channel_cdr(chan)) {
-		ast_cdr_setcid(ast_channel_cdr(chan), chan);
-	}
+	ast_channel_publish_snapshot(chan);
 	ast_channel_unlock(chan);
 }
 
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 1e21cced0bf305c85b671aa672b1b89b20cbbed7..afa4c9f3d3eaf7f26a119d9465214ea75306bfc1 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -38,6 +38,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <unistd.h>
 #include <fcntl.h>
 
+#include "asterisk/paths.h"
 #include "asterisk/channel.h"
 #include "asterisk/channel_internal.h"
 #include "asterisk/data.h"
@@ -143,6 +144,7 @@ struct ast_channel {
 	struct ast_namedgroups *named_callgroups;	/*!< Named call group for call pickups */
 	struct ast_namedgroups *named_pickupgroups;	/*!< Named pickup group - which call groups can be picked up? */
 	struct timeval creationtime;			/*!< The time of channel creation */
+	struct timeval answertime;				/*!< The time the channel was answered */
 	struct ast_readq_list readq;
 	struct ast_jb jb;				/*!< The jitterbuffer state */
 	struct timeval dtmf_tv;				/*!< The time that an in process digit began, or the last digit ended */
@@ -206,6 +208,9 @@ struct ast_channel {
 	struct stasis_subscription *endpoint_forward;	/*!< Subscription for event forwarding to endpoint's topic */
 };
 
+/*! \brief The monotonically increasing integer counter for channel uniqueids */
+static int uniqueint;
+
 /* AST_DATA definitions, which will probably have to be re-thought since the channel will be opaque */
 
 #if 0	/* XXX AstData: ast_callerid no longer exists. (Equivalent code not readily apparent.) */
@@ -333,7 +338,7 @@ int ast_channel_data_add_structure(struct ast_data *tree,
 	if (!enum_node) {
 		return -1;
 	}
-	ast_data_add_str(enum_node, "text", ast_cdr_flags2str(ast_channel_amaflags(chan)));
+	ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(ast_channel_amaflags(chan)));
 	ast_data_add_int(enum_node, "value", ast_channel_amaflags(chan));
 
 	/* transfercapability */
@@ -400,8 +405,6 @@ int ast_channel_data_add_structure(struct ast_data *tree,
 		return -1;
 	}
 
-	ast_cdr_data_add_structure(data_cdr, ast_channel_cdr(chan), 1);
-
 	return 0;
 }
 
@@ -413,9 +416,11 @@ int ast_channel_data_cmp_structure(const struct ast_data_search *tree,
 
 /* ACCESSORS */
 
-#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish)			\
+#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish, assert_on_null) \
 void ast_channel_##field##_set(struct ast_channel *chan, const char *value) \
 { \
+	if ((assert_on_null)) ast_assert(!ast_strlen_zero(value)); \
+	if (!strcmp(value, chan->field)) return; \
 	ast_string_field_set(chan, field, value); \
 	if (publish) ast_channel_publish_snapshot(chan); \
 } \
@@ -431,20 +436,20 @@ void ast_channel_##field##_build(struct ast_channel *chan, const char *fmt, ...)
 	va_start(ap, fmt); \
 	ast_channel_##field##_build_va(chan, fmt, ap); \
 	va_end(ap); \
-	if (publish) ast_channel_publish_snapshot(chan); \
 }
 
-DEFINE_STRINGFIELD_SETTERS_FOR(name, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(language, 1);
-DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0);
-DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(name, 0, 1);
+DEFINE_STRINGFIELD_SETTERS_FOR(language, 1, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(musicclass, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(accountcode, 1, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(peeraccount, 1, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(userfield, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(call_forward, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(uniqueid, 0, 1);
+DEFINE_STRINGFIELD_SETTERS_FOR(linkedid, 1, 1);
+DEFINE_STRINGFIELD_SETTERS_FOR(parkinglot, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(hangupsource, 0, 0);
+DEFINE_STRINGFIELD_SETTERS_FOR(dialcontext, 0, 0);
 
 #define DEFINE_STRINGFIELD_GETTER_FOR(field) const char *ast_channel_##field(const struct ast_channel *chan) \
 { \
@@ -464,12 +469,6 @@ DEFINE_STRINGFIELD_GETTER_FOR(parkinglot);
 DEFINE_STRINGFIELD_GETTER_FOR(hangupsource);
 DEFINE_STRINGFIELD_GETTER_FOR(dialcontext);
 
-void ast_channel_linkedid_set(struct ast_channel *chan, const char *value)
-{
-	ast_assert(!ast_strlen_zero(value));
-	ast_string_field_set(chan, linkedid, value);
-}
-
 const char *ast_channel_appl(const struct ast_channel *chan)
 {
 	return chan->appl;
@@ -555,14 +554,20 @@ void ast_channel_sending_dtmf_tv_set(struct ast_channel *chan, struct timeval va
 	chan->sending_dtmf_tv = value;
 }
 
-int ast_channel_amaflags(const struct ast_channel *chan)
+enum ama_flags ast_channel_amaflags(const struct ast_channel *chan)
 {
 	return chan->amaflags;
 }
-void ast_channel_amaflags_set(struct ast_channel *chan, int value)
+
+void ast_channel_amaflags_set(struct ast_channel *chan, enum ama_flags value)
 {
+	if (chan->amaflags == value) {
+		return;
+	}
 	chan->amaflags = value;
+	ast_channel_publish_snapshot(chan);
 }
+
 #ifdef HAVE_EPOLL
 int ast_channel_epfd(const struct ast_channel *chan)
 {
@@ -1043,6 +1048,16 @@ void ast_channel_creationtime_set(struct ast_channel *chan, struct timeval *valu
 	chan->creationtime = *value;
 }
 
+struct timeval ast_channel_answertime(struct ast_channel *chan)
+{
+	return chan->answertime;
+}
+
+void ast_channel_answertime_set(struct ast_channel *chan, struct timeval *value)
+{
+	chan->answertime = *value;
+}
+
 /* Evil softhangup accessors */
 int ast_channel_softhangup_internal_flag(struct ast_channel *chan)
 {
@@ -1350,7 +1365,7 @@ static int pvt_cause_cmp_fn(void *obj, void *vstr, int flags)
 
 #define DIALED_CAUSES_BUCKETS 37
 
-struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *file, int line, const char *function)
+struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const char *linkedid, const char *file, int line, const char *function)
 {
 	struct ast_channel *tmp;
 #if defined(REF_DEBUG)
@@ -1368,7 +1383,21 @@ struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj),
 	}
 
 	if (!(tmp->dialed_causes = ao2_container_alloc(DIALED_CAUSES_BUCKETS, pvt_cause_hash_fn, pvt_cause_cmp_fn))) {
-	        return ast_channel_unref(tmp);
+		return ast_channel_unref(tmp);
+	}
+
+	if (ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
+		ast_channel_uniqueid_build(tmp, "%li.%d", (long)time(NULL),
+			ast_atomic_fetchadd_int(&uniqueint, 1));
+	} else {
+		ast_channel_uniqueid_build(tmp, "%s-%li.%d", ast_config_AST_SYSTEM_NAME,
+			(long)time(NULL), ast_atomic_fetchadd_int(&uniqueint, 1));
+	}
+
+	if (!ast_strlen_zero(linkedid)) {
+		ast_string_field_set(tmp, linkedid, linkedid);
+	} else {
+		ast_string_field_set(tmp, linkedid, tmp->uniqueid);
 	}
 
 	return tmp;
diff --git a/main/cli.c b/main/cli.c
index 683ae9c3ea31b6de454e1df0bef9a1ccd5e1d048..3431d1de827072a53bf433f717fe2c0b8acb1e7f 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -932,8 +932,8 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		bridge = ast_channel_get_bridge(c);
 
 		if (!count) {
-			if ((concise || verbose)  && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
-				int duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000);
+			if ((concise || verbose)  && !ast_tvzero(ast_channel_creationtime(c))) {
+				int duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(c)) / 1000);
 				if (verbose) {
 					int durh = duration / 3600;
 					int durm = (duration % 3600) / 60;
@@ -1465,8 +1465,8 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 
 	ast_channel_lock(c);
 
-	if (ast_channel_cdr(c)) {
-		elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
+	if (!ast_tvzero(ast_channel_creationtime(c))) {
+		elapsed_seconds = now.tv_sec - ast_channel_creationtime(c).tv_sec;
 		hour = elapsed_seconds / 3600;
 		min = (elapsed_seconds % 3600) / 60;
 		sec = elapsed_seconds % 60;
@@ -1565,7 +1565,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		ast_str_append(&output, 0, "      Variables:\n%s\n", ast_str_buffer(obuf));
 	}
 
-	if (ast_channel_cdr(c) && ast_cdr_serialize_variables(ast_channel_cdr(c), &obuf, '=', '\n', 1)) {
+	if (ast_cdr_serialize_variables(ast_channel_name(c), &obuf, '=', '\n')) {
 		ast_str_append(&output, 0, "  CDR Variables:\n%s\n", ast_str_buffer(obuf));
 	}
 
diff --git a/main/dial.c b/main/dial.c
index 840f681d683efb08db2dd9ea27d3cc6cf1451c1d..ab35373c5fe57a9a4732f57c5149b75abe744d84 100644
--- a/main/dial.c
+++ b/main/dial.c
@@ -332,7 +332,7 @@ int ast_dial_prerun(struct ast_dial *dial, struct ast_channel *chan, struct ast_
 }
 
 /*! \brief Helper function that does the beginning dialing per-appended channel */
-static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan)
+static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_channel *chan, int async)
 {
 	char numsubst[AST_MAX_EXTENSION];
 	int res = 1;
@@ -351,9 +351,10 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann
 		ast_hangup(channel->owner);
 		channel->owner = NULL;
 	} else {
-		if (chan)
+		if (chan) {
 			ast_poll_channel_add(chan, channel->owner);
-		ast_channel_publish_dial(chan, channel->owner, channel->device, NULL);
+		}
+		ast_channel_publish_dial(async ? NULL : chan, channel->owner, channel->device, NULL);
 		res = 1;
 		ast_verb(3, "Called %s\n", numsubst);
 	}
@@ -362,7 +363,7 @@ static int begin_dial_channel(struct ast_dial_channel *channel, struct ast_chann
 }
 
 /*! \brief Helper function that does the beginning dialing per dial structure */
-static int begin_dial(struct ast_dial *dial, struct ast_channel *chan)
+static int begin_dial(struct ast_dial *dial, struct ast_channel *chan, int async)
 {
 	struct ast_dial_channel *channel = NULL;
 	int success = 0;
@@ -370,7 +371,7 @@ static int begin_dial(struct ast_dial *dial, struct ast_channel *chan)
 	/* Iterate through channel list, requesting and calling each one */
 	AST_LIST_LOCK(&dial->channels);
 	AST_LIST_TRAVERSE(&dial->channels, channel, list) {
-		success += begin_dial_channel(channel, chan);
+		success += begin_dial_channel(channel, chan, async);
 	}
 	AST_LIST_UNLOCK(&dial->channels);
 
@@ -409,7 +410,7 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c
 	AST_LIST_UNLOCK(&dial->channels);
 
 	/* Finally give it a go... send it out into the world */
-	begin_dial_channel(channel, chan);
+	begin_dial_channel(channel, chan, chan ? 0 : 1);
 
 	/* Drop the original channel */
 	ast_hangup(original);
@@ -819,7 +820,7 @@ enum ast_dial_result ast_dial_run(struct ast_dial *dial, struct ast_channel *cha
 	}
 
 	/* Dial each requested channel */
-	if (!begin_dial(dial, chan))
+	if (!begin_dial(dial, chan, async))
 		return AST_DIAL_RESULT_FAILED;
 
 	/* If we are running async spawn a thread and send it away... otherwise block here */
diff --git a/main/features.c b/main/features.c
index d5b5ce295b3404531feeacfed205de5236d6d4a0..c44520c53e6c598a4f970864e92ebf8c25be2db9 100644
--- a/main/features.c
+++ b/main/features.c
@@ -3618,7 +3618,7 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
 
 	/* Answer if need be */
 	if (ast_channel_state(chan) != AST_STATE_UP) {
-		if (ast_raw_answer(chan, 1)) {
+		if (ast_raw_answer(chan)) {
 			return -1;
 		}
 	}
@@ -3628,8 +3628,6 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
 	ast_channel_log("Pre-bridge CHAN Channel info", chan);
 	ast_channel_log("Pre-bridge PEER Channel info", peer);
 #endif
-	/* two channels are being marked as linked here */
-	ast_channel_set_linkgroup(chan, peer);
 
 	/*
 	 * If we are bridging a call, stop worrying about forwarding
diff --git a/main/manager.c b/main/manager.c
index 0e402aeb8693b1a69fb1a144a86854ce8e6fec3a..d4960d6de15b97a70f31b3fba6aa93ee7dbff126 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -3770,8 +3770,8 @@ static int action_status(struct mansession *s, const struct message *m)
 			bridge_text[0] = '\0';
 		}
 		if (ast_channel_pbx(c)) {
-			if (ast_channel_cdr(c)) {
-				elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
+			if (!ast_tvzero(ast_channel_creationtime(c))) {
+				elapsed_seconds = now.tv_sec - ast_channel_creationtime(c).tv_sec;
 			}
 			astman_append(s,
 				"Event: Status\r\n"
@@ -5120,7 +5120,7 @@ static int action_coresettings(struct mansession *s, const struct message *m)
 			ast_config_AST_RUN_GROUP,
 			option_maxfiles,
 			AST_CLI_YESNO(ast_realtime_enabled()),
-			AST_CLI_YESNO(check_cdr_enabled()),
+			AST_CLI_YESNO(ast_cdr_is_enabled()),
 			AST_CLI_YESNO(check_webmanager_enabled())
 			);
 	return 0;
@@ -5228,8 +5228,8 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
 		ast_channel_lock(c);
 
 		bc = ast_bridged_channel(c);
-		if (ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
-			duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_cdr(c)->start) / 1000);
+		if (!ast_tvzero(ast_channel_creationtime(c))) {
+			duration = (int)(ast_tvdiff_ms(ast_tvnow(), ast_channel_creationtime(c)) / 1000);
 			durh = duration / 3600;
 			durm = (duration % 3600) / 60;
 			durs = duration % 60;
diff --git a/main/manager_channels.c b/main/manager_channels.c
index 277dc873d86d98d1253a7af619e9e31e1b541b9a..5ae35b21d63eaa4af0dabd58997120ac29b69121 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -153,6 +153,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			</syntax>
 		</managerEventInstance>
 	</managerEvent>
+	<managerEvent language="en_US" name="NewAccountCode">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a Channel's AccountCode is changed.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+				<parameter name="OldAccountCode">
+					<para>The channel's previous account code</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
 	<managerEvent language="en_US" name="DialBegin">
 		<managerEventInstance class="EVENT_FLAG_CALL">
 			<synopsis>Raised when a dial action has started.</synopsis>
@@ -627,7 +638,8 @@ static struct ast_manager_event_blob *channel_newexten(
 		return NULL;
 	}
 
-	if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) {
+	if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)
+		&& !strcmp(old_snapshot->appl, new_snapshot->appl)) {
 		return NULL;
 	}
 
@@ -662,10 +674,28 @@ static struct ast_manager_event_blob *channel_new_callerid(
 		ast_describe_caller_presentation(new_snapshot->caller_pres));
 }
 
+static struct ast_manager_event_blob *channel_new_accountcode(
+	struct ast_channel_snapshot *old_snapshot,
+	struct ast_channel_snapshot *new_snapshot)
+{
+	if (!old_snapshot || !new_snapshot) {
+		return NULL;
+	}
+
+	if (!strcmp(old_snapshot->accountcode, new_snapshot->accountcode)) {
+		return NULL;
+	}
+
+	return ast_manager_event_blob_create(
+		EVENT_FLAG_CALL, "NewAccountCode",
+		"OldAccountCode: %s\r\n", old_snapshot->accountcode);
+}
+
 channel_snapshot_monitor channel_monitors[] = {
 	channel_state_change,
 	channel_newexten,
-	channel_new_callerid
+	channel_new_callerid,
+	channel_new_accountcode
 };
 
 static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
diff --git a/main/pbx.c b/main/pbx.c
index 2e8c321696d247ad8bb514c39d0f3a72b813e4da..af988dcf961efa46afcf781f5462309e2ff70666 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -470,36 +470,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<ref type="function">Exception</ref>
 		</see-also>
 	</application>
-	<application name="ResetCDR" language="en_US">
-		<synopsis>
-			Resets the Call Data Record.
-		</synopsis>
-		<syntax>
-			<parameter name="options">
-				<optionlist>
-					<option name="w">
-						<para>Store the current CDR record before resetting it.</para>
-					</option>
-					<option name="a">
-						<para>Store any stacked records.</para>
-					</option>
-					<option name="v">
-						<para>Save CDR variables.</para>
-					</option>
-					<option name="e">
-						<para>Enable CDR only (negate effects of NoCDR).</para>
-					</option>
-				</optionlist>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application causes the Call Data Record to be reset.</para>
-		</description>
-		<see-also>
-			<ref type="application">ForkCDR</ref>
-			<ref type="application">NoCDR</ref>
-		</see-also>
-	</application>
 	<application name="Ringing" language="en_US">
 		<synopsis>
 			Indicate ringing tone.
@@ -657,9 +627,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 		</syntax>
 		<description>
 			<para>This application will set the channel's AMA Flags for billing purposes.</para>
+			<warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning>
 		</description>
 		<see-also>
 			<ref type="function">CDR</ref>
+			<ref type="function">CHANNEL</ref>
 		</see-also>
 	</application>
 	<application name="Wait" language="en_US">
@@ -1139,7 +1111,6 @@ static int pbx_builtin_background(struct ast_channel *, const char *);
 static int pbx_builtin_wait(struct ast_channel *, const char *);
 static int pbx_builtin_waitexten(struct ast_channel *, const char *);
 static int pbx_builtin_incomplete(struct ast_channel *, const char *);
-static int pbx_builtin_resetcdr(struct ast_channel *, const char *);
 static int pbx_builtin_setamaflags(struct ast_channel *, const char *);
 static int pbx_builtin_ringing(struct ast_channel *, const char *);
 static int pbx_builtin_proceeding(struct ast_channel *, const char *);
@@ -1326,7 +1297,6 @@ static struct pbx_builtin {
 	{ "Proceeding",     pbx_builtin_proceeding },
 	{ "Progress",       pbx_builtin_progress },
 	{ "RaiseException", pbx_builtin_raise_exception },
-	{ "ResetCDR",       pbx_builtin_resetcdr },
 	{ "Ringing",        pbx_builtin_ringing },
 	{ "SayAlpha",       pbx_builtin_saycharacters },
 	{ "SayDigits",      pbx_builtin_saydigits },
@@ -1565,15 +1535,13 @@ int pbx_exec(struct ast_channel *c,	/*!< Channel */
 	const char *saved_c_appl;
 	const char *saved_c_data;
 
-	if (ast_channel_cdr(c) && !ast_check_hangup(c))
-		ast_cdr_setapp(ast_channel_cdr(c), app->name, data);
-
 	/* save channel values */
 	saved_c_appl= ast_channel_appl(c);
 	saved_c_data= ast_channel_data(c);
 
 	ast_channel_appl_set(c, app->name);
 	ast_channel_data_set(c, data);
+	ast_channel_publish_snapshot(c);
 
 	if (app->module)
 		u = __ast_module_user_add(app->module, c);
@@ -5713,10 +5681,6 @@ void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
 
 	ast_channel_lock(chan);
 
-	if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) {
-		ast_cdr_end(ast_channel_cdr(chan));
-	}
-
 	/* Set h exten location */
 	if (context != ast_channel_context(chan)) {
 		ast_channel_context_set(chan, context);
@@ -5797,10 +5761,6 @@ int ast_pbx_hangup_handler_run(struct ast_channel *chan)
 		return 0;
 	}
 
-	if (ast_channel_cdr(chan) && ast_opt_end_cdr_before_h_exten) {
-		ast_cdr_end(ast_channel_cdr(chan));
-	}
-
 	/*
 	 * Make sure that the channel is marked as hungup since we are
 	 * going to run the hangup handlers on it.
@@ -6114,12 +6074,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
 		set_ext_pri(c, "s", 1);
 	}
 
-	ast_channel_lock(c);
-	if (ast_channel_cdr(c)) {
-		/* allow CDR variables that have been collected after channel was created to be visible during call */
-		ast_cdr_update(c);
-	}
-	ast_channel_unlock(c);
 	for (;;) {
 		char dst_exten[256];	/* buffer to accumulate digits */
 		int pos = 0;		/* XXX should check bounds */
@@ -6229,11 +6183,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
 					}
 					/* Call timed out with no special extension to jump to. */
 				}
-				ast_channel_lock(c);
-				if (ast_channel_cdr(c)) {
-					ast_cdr_update(c);
-				}
-				ast_channel_unlock(c);
 				error = 1;
 				break;
 			}
@@ -6339,12 +6288,6 @@ static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
 					}
 				}
 			}
-			ast_channel_lock(c);
-			if (ast_channel_cdr(c)) {
-				ast_verb(2, "CDR updated on %s\n",ast_channel_name(c));
-				ast_cdr_update(c);
-			}
-			ast_channel_unlock(c);
 		}
 	}
 
@@ -9991,13 +9934,16 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
 	}
 
 	dialed = ast_dial_get_channel(outgoing->dial, 0);
+	if (!dialed) {
+		return -1;
+	}
 
 	ast_set_variables(dialed, vars);
 
 	if (account) {
-		ast_cdr_setaccount(dialed, account);
+		ast_channel_accountcode_set(dialed, account);
 	}
-	ast_set_flag(ast_channel_cdr(dialed), AST_CDR_FLAG_ORIGINATED);
+	ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED);
 
 	if (!ast_strlen_zero(cid_num) && !ast_strlen_zero(cid_name)) {
 		struct ast_party_connected_line connected;
@@ -10043,12 +9989,13 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
 	/* Wait for dialing to complete */
 	if (channel || synchronous) {
 		if (channel) {
+			ast_channel_ref(*channel);
 			ast_channel_unlock(*channel);
 		}
 		while (!outgoing->dialed) {
 			ast_cond_wait(&outgoing->cond, &outgoing->lock);
 		}
-		if (channel) {
+		if (channel && *channel) {
 			ast_channel_lock(*channel);
 		}
 	}
@@ -10078,7 +10025,7 @@ static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap, co
 			}
 
 			if (account) {
-				ast_cdr_setaccount(failed, account);
+				ast_channel_accountcode_set(failed, account);
 			}
 
 			set_ext_pri(failed, "failed", 1);
@@ -10387,8 +10334,8 @@ static int pbx_builtin_busy(struct ast_channel *chan, const char *data)
 	/* Don't change state of an UP channel, just indicate
 	   busy in audio */
 	if (ast_channel_state(chan) != AST_STATE_UP) {
+		ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY);
 		ast_setstate(chan, AST_STATE_BUSY);
-		ast_cdr_busy(ast_channel_cdr(chan));
 	}
 	wait_for_hangup(chan, data);
 	return -1;
@@ -10403,8 +10350,8 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
 	/* Don't change state of an UP channel, just indicate
 	   congestion in audio */
 	if (ast_channel_state(chan) != AST_STATE_UP) {
+		ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION);
 		ast_setstate(chan, AST_STATE_BUSY);
-		ast_cdr_congestion(ast_channel_cdr(chan));
 	}
 	wait_for_hangup(chan, data);
 	return -1;
@@ -10416,7 +10363,6 @@ static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
 static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
 {
 	int delay = 0;
-	int answer_cdr = 1;
 	char *parse;
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(delay);
@@ -10424,7 +10370,7 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
 	);
 
 	if (ast_strlen_zero(data)) {
-		return __ast_answer(chan, 0, 1);
+		return __ast_answer(chan, 0);
 	}
 
 	parse = ast_strdupa(data);
@@ -10439,10 +10385,12 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
 	}
 
 	if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
-		answer_cdr = 0;
+		if (ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL)) {
+			ast_log(AST_LOG_WARNING, "Failed to disable CDR on %s\n", ast_channel_name(chan));
+		}
 	}
 
-	return __ast_answer(chan, delay, answer_cdr);
+	return __ast_answer(chan, delay);
 }
 
 static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
@@ -10459,7 +10407,7 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
 	if (ast_check_hangup(chan)) {
 		return -1;
 	} else if (ast_channel_state(chan) != AST_STATE_UP && answer) {
-		__ast_answer(chan, 0, 1);
+		__ast_answer(chan, 0);
 	}
 
 	ast_indicate(chan, AST_CONTROL_INCOMPLETE);
@@ -10467,39 +10415,30 @@ static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
 	return AST_PBX_INCOMPLETE;
 }
 
-AST_APP_OPTIONS(resetcdr_opts, {
-	AST_APP_OPTION('w', AST_CDR_FLAG_POSTED),
-	AST_APP_OPTION('a', AST_CDR_FLAG_LOCKED),
-	AST_APP_OPTION('v', AST_CDR_FLAG_KEEP_VARS),
-	AST_APP_OPTION('e', AST_CDR_FLAG_POST_ENABLE),
-});
-
 /*!
  * \ingroup applications
  */
-static int pbx_builtin_resetcdr(struct ast_channel *chan, const char *data)
+static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
 {
-	char *args;
-	struct ast_flags flags = { 0 };
+	ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n");
 
-	if (!ast_strlen_zero(data)) {
-		args = ast_strdupa(data);
-		ast_app_parse_options(resetcdr_opts, &flags, NULL, args);
+	if (ast_strlen_zero(data)) {
+		ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n");
+		return 0;
 	}
-
-	ast_cdr_reset(ast_channel_cdr(chan), &flags);
-
-	return 0;
-}
-
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
-{
 	/* Copy the AMA Flags as specified */
 	ast_channel_lock(chan);
-	ast_cdr_setamaflags(chan, data ? data : "");
+	if (isdigit(data[0])) {
+		int amaflags;
+		if (sscanf(data, "%30d", &amaflags) != 1) {
+			ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan));
+			ast_channel_unlock(chan);
+			return 0;
+		}
+		ast_channel_amaflags_set(chan, amaflags);
+	} else {
+		ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data));
+	}
 	ast_channel_unlock(chan);
 	return 0;
 }
diff --git a/main/stasis.c b/main/stasis.c
index e810dd852d5bfd9b5dea2df8e7c2af7eeff6cd74..406a1bb254b23cd778a9227921237398b14b9386 100644
--- a/main/stasis.c
+++ b/main/stasis.c
@@ -32,6 +32,7 @@
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "asterisk/astobj2.h"
+#include "asterisk/stasis_internal.h"
 #include "asterisk/stasis.h"
 #include "asterisk/threadpool.h"
 #include "asterisk/taskprocessor.h"
@@ -170,7 +171,7 @@ static void subscription_invoke(struct stasis_subscription *sub,
 
 static void send_subscription_change_message(struct stasis_topic *topic, char *uniqueid, char *description);
 
-static struct stasis_subscription *__stasis_subscribe(
+struct stasis_subscription *internal_stasis_subscribe(
 	struct stasis_topic *topic,
 	stasis_subscription_cb callback,
 	void *data,
@@ -213,7 +214,7 @@ struct stasis_subscription *stasis_subscribe(
 	stasis_subscription_cb callback,
 	void *data)
 {
-	return __stasis_subscribe(topic, callback, data, 1);
+	return internal_stasis_subscribe(topic, callback, data, 1);
 }
 
 struct stasis_subscription *stasis_unsubscribe(struct stasis_subscription *sub)
@@ -476,7 +477,7 @@ struct stasis_subscription *stasis_forward_all(struct stasis_topic *from_topic,
 	 * mailbox. Otherwise, messages forwarded to the same topic from
 	 * different topics may get reordered. Which is bad.
 	 */
-	sub = __stasis_subscribe(from_topic, stasis_forward_cb, to_topic, 0);
+	sub = internal_stasis_subscribe(from_topic, stasis_forward_cb, to_topic, 0);
 	if (sub) {
 		/* hold a ref to to_topic for this forwarding subscription */
 		ao2_ref(to_topic, +1);
diff --git a/main/stasis_cache.c b/main/stasis_cache.c
index 115bb7b67ba7bbafd15ee74de4627d72a8d3c420..5757c869a899db2c0fa42deb6e22ae9f159f94fd 100644
--- a/main/stasis_cache.c
+++ b/main/stasis_cache.c
@@ -33,6 +33,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "asterisk/astobj2.h"
 #include "asterisk/hashtab.h"
+#include "asterisk/stasis_internal.h"
 #include "asterisk/stasis.h"
 #include "asterisk/utils.h"
 
@@ -486,7 +487,7 @@ struct stasis_caching_topic *stasis_caching_topic_create(struct stasis_topic *or
 
 	caching_topic->id_fn = id_fn;
 
-	sub = stasis_subscribe(original_topic, caching_topic_exec, caching_topic);
+	sub = internal_stasis_subscribe(original_topic, caching_topic_exec, caching_topic, 0);
 	if (sub == NULL) {
 		return NULL;
 	}
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 2a88b00685ef23bec2fdb354d41c327c3fce6a0e..e76f258243d13365feede97f0a8be080670c9f08 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -156,10 +156,17 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha
 	ast_string_field_set(snapshot, exten, ast_channel_exten(chan));
 
 	ast_string_field_set(snapshot, caller_name,
-		S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""));
+		S_COR(ast_channel_caller(chan)->ani.name.valid, ast_channel_caller(chan)->ani.name.str,
+		S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "")));
 	ast_string_field_set(snapshot, caller_number,
-		S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""));
-
+		S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str,
+		S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "")));
+	ast_string_field_set(snapshot, caller_dnid, S_OR(ast_channel_dialed(chan)->number.str, ""));
+	ast_string_field_set(snapshot, caller_subaddr,
+		S_COR(ast_channel_caller(chan)->ani.subaddress.valid, ast_channel_caller(chan)->ani.subaddress.str,
+		S_COR(ast_channel_caller(chan)->id.subaddress.valid, ast_channel_caller(chan)->id.subaddress.str, "")));
+	ast_string_field_set(snapshot, dialed_subaddr,
+		S_COR(ast_channel_dialed(chan)->subaddress.valid, ast_channel_dialed(chan)->subaddress.str, ""));
 	ast_string_field_set(snapshot, caller_ani,
 		S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, ""));
 	ast_string_field_set(snapshot, caller_rdnis,
@@ -493,20 +500,6 @@ struct ast_json *ast_multi_channel_blob_get_json(struct ast_multi_channel_blob *
 	return obj->blob;
 }
 
-void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_type *type, struct ast_json *blob)
-{
-	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-
-	if (!blob) {
-		blob = ast_json_null();
-	}
-
-	message = ast_channel_blob_create(chan, type, blob);
-	if (message) {
-		stasis_publish(ast_channel_topic(chan), message);
-	}
-}
-
 void ast_channel_publish_snapshot(struct ast_channel *chan)
 {
 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -526,6 +519,20 @@ void ast_channel_publish_snapshot(struct ast_channel *chan)
 	stasis_publish(ast_channel_topic(chan), message);
 }
 
+void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_type *type, struct ast_json *blob)
+{
+	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+
+	if (!blob) {
+		blob = ast_json_null();
+	}
+
+	message = ast_channel_blob_create(chan, type, blob);
+	if (message) {
+		stasis_publish(ast_channel_topic(chan), message);
+	}
+}
+
 void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value)
 {
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
diff --git a/main/test.c b/main/test.c
index 2109c9478b1270afeb9ab9960fb017b3022bd85d..fdc4916e1653aa5cd71229576f94879e048697ed 100644
--- a/main/test.c
+++ b/main/test.c
@@ -48,6 +48,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
 #include "asterisk/stasis.h"
 #include "asterisk/json.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/stasis.h"
+#include "asterisk/json.h"
 
 /*! \since 12
  * \brief The topic for test suite messages
@@ -80,9 +82,11 @@ struct ast_test {
 	 * CLI in addition to being saved off in status_str.
 	 */
 	struct ast_cli_args *cli;
-	enum ast_test_result_state state; /*!< current test state */
-	unsigned int time;                /*!< time in ms test took */
-	ast_test_cb_t *cb;                /*!< test callback function */
+	enum ast_test_result_state state;   /*!< current test state */
+	unsigned int time;                  /*!< time in ms test took */
+	ast_test_cb_t *cb;                  /*!< test callback function */
+	ast_test_init_cb_t *init_cb;        /*!< test init function */
+	ast_test_cleanup_cb_t *cleanup_cb;  /*!< test cleanup function */
 	AST_LIST_ENTRY(ast_test) entry;
 };
 
@@ -159,6 +163,40 @@ int __ast_test_status_update(const char *file, const char *func, int line, struc
 	return 0;
 }
 
+int ast_test_register_init(const char *category, ast_test_init_cb_t *cb)
+{
+	struct ast_test *test;
+	int registered = 1;
+
+	AST_LIST_LOCK(&tests);
+	AST_LIST_TRAVERSE(&tests, test, entry) {
+		if (!(test_cat_cmp(test->info.category, category))) {
+			test->init_cb = cb;
+			registered = 0;
+		}
+	}
+	AST_LIST_UNLOCK(&tests);
+
+	return registered;
+}
+
+int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb)
+{
+	struct ast_test *test;
+	int registered = 1;
+
+	AST_LIST_LOCK(&tests);
+	AST_LIST_TRAVERSE(&tests, test, entry) {
+		if (!(test_cat_cmp(test->info.category, category))) {
+			test->cleanup_cb = cb;
+			registered = 0;
+		}
+	}
+	AST_LIST_UNLOCK(&tests);
+
+	return registered;
+}
+
 int ast_test_register(ast_test_cb_t *cb)
 {
 	struct ast_test *test;
@@ -203,14 +241,34 @@ int ast_test_unregister(ast_test_cb_t *cb)
 static void test_execute(struct ast_test *test)
 {
 	struct timeval begin;
+	enum ast_test_result_state result;
 
 	ast_str_reset(test->status_str);
 
 	begin = ast_tvnow();
-	test->state = test->cb(&test->info, TEST_EXECUTE, test);
+	if (test->init_cb && test->init_cb(&test->info, test)) {
+		test->state = AST_TEST_FAIL;
+		goto exit;
+	}
+	result = test->cb(&test->info, TEST_EXECUTE, test);
+	if (test->state != AST_TEST_FAIL) {
+		test->state = result;
+	}
+	if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) {
+		test->state = AST_TEST_FAIL;
+	}
+exit:
 	test->time = ast_tvdiff_ms(ast_tvnow(), begin);
 }
 
+void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state)
+{
+	if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) {
+		return;
+	}
+	test->state = state;
+}
+
 static void test_xml_entry(struct ast_test *test, FILE *f)
 {
 	if (!f || !test || test->state == AST_TEST_NOT_RUN) {
diff --git a/main/utils.c b/main/utils.c
index fde9b953b74a3b4a8a30230aafcc0a5e9ac34e1c..1007254875eae441ac19113dfd4474393a872976 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1546,6 +1546,15 @@ int ast_remaining_ms(struct timeval start, int max_ms)
 	return ms;
 }
 
+void ast_format_duration_hh_mm_ss(int duration, char *buf, size_t length)
+{
+	int durh, durm, durs;
+	durh = duration / 3600;
+	durm = (duration % 3600) / 60;
+	durs = duration % 60;
+	snprintf(buf, length, "%02d:%02d:%02d", durh, durm, durs);
+}
+
 #undef ONE_MILLION
 
 #ifndef linux
diff --git a/res/res_agi.c b/res/res_agi.c
index 486310dd6bc2b4ca94c5f2cdca3bff1b0cf8908c..a841f3623508368f1e164f9ca32d8dd7e7a928bc 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -3625,11 +3625,6 @@ static enum agi_result agi_handle_command(struct ast_channel *chan, AGI *agi, ch
 		the module we are using */
 		if (c->mod != ast_module_info->self)
 			ast_module_ref(c->mod);
-		/* If the AGI command being executed is an actual application (using agi exec)
-		the app field will be updated in pbx_exec via handle_exec */
-		if (ast_channel_cdr(chan) && !ast_check_hangup(chan) && strcasecmp(argv[0], "EXEC"))
-			ast_cdr_setapp(ast_channel_cdr(chan), "AGI", buf);
-
 		res = c->handler(chan, agi, argc, argv);
 		if (c->mod != ast_module_info->self)
 			ast_module_unref(c->mod);
diff --git a/res/res_config_sqlite.c b/res/res_config_sqlite.c
index e648f941b7f699e8d555ab71b861833f16bca59c..7d5fd83e6a636409ad81355830cded72efb930f8 100644
--- a/res/res_config_sqlite.c
+++ b/res/res_config_sqlite.c
@@ -791,7 +791,7 @@ static int cdr_handler(struct ast_cdr *cdr)
 
 	AST_RWLIST_TRAVERSE(&(tbl->columns), col, list) {
 		if (col->isint) {
-			ast_cdr_getvar(cdr, col->name, &tmp, workspace, sizeof(workspace), 0, 1);
+			ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 1);
 			if (!tmp) {
 				continue;
 			}
@@ -800,7 +800,7 @@ static int cdr_handler(struct ast_cdr *cdr)
 				ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", scannum);
 			}
 		} else {
-			ast_cdr_getvar(cdr, col->name, &tmp, workspace, sizeof(workspace), 0, 0);
+			ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 0);
 			if (!tmp) {
 				continue;
 			}
diff --git a/res/res_monitor.c b/res/res_monitor.c
index f1da4ec834b6390071ddf8d2748058ad60803c74..b5225010e2de14b34241109e4c530c4c5e2dfe08 100644
--- a/res/res_monitor.c
+++ b/res/res_monitor.c
@@ -692,18 +692,10 @@ static int start_monitor_exec(struct ast_channel *chan, const char *data)
 	}
 
 	if (!ast_strlen_zero(urlprefix) && !ast_strlen_zero(args.fname_base)) {
-		struct ast_cdr *chan_cdr;
 		snprintf(tmp, sizeof(tmp), "%s/%s.%s", urlprefix, args.fname_base,
 			((strcmp(args.format, "gsm")) ? "wav" : "gsm"));
 		ast_channel_lock(chan);
-		if (!ast_channel_cdr(chan)) {
-			if (!(chan_cdr = ast_cdr_alloc())) {
-				ast_channel_unlock(chan);
-				return -1;
-			}
-			ast_channel_cdr_set(chan, chan_cdr);
-		}
-		ast_cdr_setuserfield(chan, tmp);
+		ast_cdr_setuserfield(ast_channel_name(chan), tmp);
 		ast_channel_unlock(chan);
 	}
 	if (waitforbridge) {
diff --git a/res/res_stasis_answer.c b/res/res_stasis_answer.c
index b7534b93d90af042b00b593447612398a16e3f94..53d4b06e28abb6294940d6f5b0a68464504edcba 100644
--- a/res/res_stasis_answer.c
+++ b/res/res_stasis_answer.c
@@ -42,10 +42,9 @@ static void *app_control_answer(struct stasis_app_control *control,
 	struct ast_channel *chan, void *data)
 {
 	const int delay = 0;
-	const int cdr_answer = 1;
 	ast_debug(3, "%s: Answering",
 		stasis_app_control_get_channel_id(control));
-	return __ast_answer(chan, delay, cdr_answer) == 0 ? &OK : &FAIL;
+	return __ast_answer(chan, delay) == 0 ? &OK : &FAIL;
 }
 
 int stasis_app_control_answer(struct stasis_app_control *control)
diff --git a/tests/test_cdr.c b/tests/test_cdr.c
new file mode 100644
index 0000000000000000000000000000000000000000..c9621a450ff3ee2a185238671642f28e9db6b64b
--- /dev/null
+++ b/tests/test_cdr.c
@@ -0,0 +1,2413 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief CDR unit tests
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ *
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <math.h>
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/cdr.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/utils.h"
+#include "asterisk/causes.h"
+#include "asterisk/time.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+
+#define EPSILON 0.001
+
+#define TEST_CATEGORY "/main/cdr/"
+
+#define MOCK_CDR_BACKEND "mock_cdr_backend"
+
+#define CHANNEL_TECH_NAME "CDRTestChannel"
+
+/*! \brief A placeholder for Asterisk's 'real' CDR configuration */
+static struct ast_cdr_config *saved_config;
+
+/*! \brief A configuration suitable for 'normal' CDRs */
+static struct ast_cdr_config debug_cdr_config = {
+	.settings.flags = CDR_ENABLED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with unanswered records */
+static struct ast_cdr_config unanswered_cdr_config = {
+	.settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with congestion enabled */
+static struct ast_cdr_config congestion_cdr_config = {
+	.settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG | CDR_CONGESTION,
+};
+
+/*! \brief Macro to swap a configuration out from the CDR engine. This should be
+ * used at the beginning of each test to set the needed configuration for that
+ * test.
+ */
+#define SWAP_CONFIG(ao2_config, template) do { \
+	*(ao2_config) = (template); \
+	ast_cdr_set_config((ao2_config)); \
+	} while (0)
+
+/*! \brief A linked list of received CDR entries from the engine */
+static AST_LIST_HEAD(, test_cdr_entry) actual_cdr_entries = AST_LIST_HEAD_INIT_VALUE;
+
+/*! \brief The Mock CDR backend condition wait */
+static ast_cond_t mock_cdr_cond;
+
+/*! \brief A channel technology used for the unit tests */
+static struct ast_channel_tech test_cdr_chan_tech = {
+	.type = CHANNEL_TECH_NAME,
+	.description = "Mock channel technology for CDR tests",
+};
+
+struct test_cdr_entry {
+	struct ast_cdr *cdr;
+	AST_LIST_ENTRY(test_cdr_entry) list;
+};
+
+/*! \brief The number of CDRs the mock backend has received */
+static int global_mock_cdr_count;
+
+/*! \internal
+ * \brief Callback function for the mock CDR backend
+ *
+ * This function 'processes' a dispatched CDR record by adding it to the
+ * \ref actual_cdr_entries list. When a test completes, it can verify the
+ * expected records against this list of actual CDRs created by the engine.
+ *
+ * \param cdr The public CDR object created by the engine
+ *
+ * \retval -1 on error
+ * \retval 0 on success
+ */
+static int mock_cdr_backend_cb(struct ast_cdr *cdr)
+{
+	struct ast_cdr *cdr_copy, *cdr_prev = NULL;
+	struct ast_cdr *mock_cdr = NULL;
+	struct test_cdr_entry *cdr_wrapper;
+
+	cdr_wrapper = ast_calloc(1, sizeof(*cdr_wrapper));
+	if (!cdr_wrapper) {
+		return -1;
+	}
+
+	for (; cdr; cdr = cdr->next) {
+		struct ast_var_t *var_entry, *var_copy;
+
+		cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+		if (!cdr_copy) {
+			return -1;
+		}
+		*cdr_copy = *cdr;
+		cdr_copy->varshead.first = NULL;
+		cdr_copy->varshead.last = NULL;
+		cdr_copy->next = NULL;
+
+		AST_LIST_TRAVERSE(&cdr->varshead, var_entry, entries) {
+			var_copy = ast_var_assign(var_entry->name, var_entry->value);
+			if (!var_copy) {
+				return -1;
+			}
+			AST_LIST_INSERT_TAIL(&cdr_copy->varshead, var_copy, entries);
+		}
+
+		if (!mock_cdr) {
+			mock_cdr = cdr_copy;
+		}
+		if (cdr_prev) {
+			cdr_prev->next = cdr_copy;
+		}
+		cdr_prev = cdr_copy;
+	}
+	cdr_wrapper->cdr = mock_cdr;
+
+	AST_LIST_LOCK(&actual_cdr_entries);
+	AST_LIST_INSERT_TAIL(&actual_cdr_entries, cdr_wrapper, list);
+	global_mock_cdr_count++;
+	ast_cond_signal(&mock_cdr_cond);
+	AST_LIST_UNLOCK(&actual_cdr_entries);
+
+	return 0;
+}
+
+/*! \internal
+ * \brief Remove all entries from \ref actual_cdr_entries
+ */
+static void clear_mock_cdr_backend(void)
+{
+	struct test_cdr_entry *cdr_wrapper;
+
+	AST_LIST_LOCK(&actual_cdr_entries);
+	while ((cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list))) {
+		ast_cdr_free(cdr_wrapper->cdr);
+		ast_free(cdr_wrapper);
+	}
+	global_mock_cdr_count = 0;
+	AST_LIST_UNLOCK(&actual_cdr_entries);
+}
+
+/*! \brief Verify a string field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_STRING_FIELD(field, actual, expected) do { \
+	if (strcmp((actual)->field, (expected)->field)) { \
+		ast_test_status_update(test, "Field %s failed: actual %s, expected %s\n", #field, (actual)->field, (expected)->field); \
+		ast_test_set_result(test, AST_TEST_FAIL); \
+		res = AST_TEST_FAIL; \
+	} } while (0)
+
+/*! \brief Verify a numeric field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_NUMERIC_FIELD(field, actual, expected) do { \
+	if ((actual)->field != (expected)->field) { \
+		ast_test_status_update(test, "Field %s failed: actual %ld, expected %ld\n", #field, (long)(actual)->field, (long)(expected)->field); \
+		ast_test_set_result(test, AST_TEST_FAIL); \
+		res = AST_TEST_FAIL; \
+	} } while (0)
+
+/*! \brief Verify a time field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
+#define VERIFY_TIME_VALUE(field, actual) do { \
+	if (ast_tvzero((actual)->field)) { \
+		ast_test_status_update(test, "Field %s failed: should not be 0\n", #field); \
+		ast_test_set_result(test, AST_TEST_FAIL); \
+		res = AST_TEST_FAIL; \
+	} } while (0)
+
+/*! \brief Alice's Caller ID */
+#define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, }
+
+/*! \brief Bob's Caller ID */
+#define BOB_CALLERID { .id.name.str = "Bob", .id.name.valid = 1, .id.number.str = "200", .id.number.valid = 1, }
+
+/*! \brief Charlie's Caller ID */
+#define CHARLIE_CALLERID { .id.name.str = "Charlie", .id.name.valid = 1, .id.number.str = "300", .id.number.valid = 1, }
+
+/*! \brief David's Caller ID */
+#define DAVID_CALLERID { .id.name.str = "David", .id.name.valid = 1, .id.number.str = "400", .id.number.valid = 1, }
+
+/*! \brief Copy the linkedid and uniqueid from a channel to an expected CDR */
+#define COPY_IDS(channel_var, expected_record) do { \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Alice, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_ALICE_CHANNEL(channel_var, caller_id, expected_record) do { \
+	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice"); \
+	ast_channel_set_caller((channel_var), (caller_id), NULL); \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Bob, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_BOB_CHANNEL(channel_var, caller_id, expected_record) do { \
+	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob"); \
+	ast_channel_set_caller((channel_var), (caller_id), NULL); \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_CHARLIE_CHANNEL(channel_var, caller_id, expected_record) do { \
+	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "300", "Charlie", "300", "300", "default", NULL, 0, CHANNEL_TECH_NAME "/Charlie"); \
+	ast_channel_set_caller((channel_var), (caller_id), NULL); \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Charlie, and set the expected
+ * CDR records' linkedid and uniqueid. */
+#define CREATE_DAVID_CHANNEL(channel_var, caller_id, expected_record) do { \
+	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David"); \
+	ast_channel_set_caller((channel_var), (caller_id), NULL); \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Emulate a channel entering into an application */
+#define EMULATE_APP_DATA(channel, priority, application, data) do { \
+	if ((priority) > 0) { \
+		ast_channel_priority_set((channel), (priority)); \
+	} \
+	ast_channel_appl_set((channel), (application)); \
+	ast_channel_data_set((channel), (data)); \
+	ast_channel_publish_snapshot((channel)); \
+	} while (0)
+
+/*! \brief Hang up a test channel safely */
+#define HANGUP_CHANNEL(channel, cause) do { \
+	ast_channel_hangupcause_set((channel), (cause)); \
+	if (!ast_hangup((channel))) { \
+		channel = NULL; \
+	} } while (0)
+
+static enum ast_test_result_state verify_mock_cdr_record(struct ast_test *test, struct ast_cdr *expected, int record)
+{
+	struct ast_cdr *actual = NULL;
+	struct test_cdr_entry *cdr_wrapper;
+	int count = 0;
+	struct timeval wait_now = ast_tvnow();
+	struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 5, .tv_nsec = wait_now.tv_usec * 1000 };
+	enum ast_test_result_state res = AST_TEST_PASS;
+
+	while (count < record) {
+		AST_LIST_LOCK(&actual_cdr_entries);
+		if (global_mock_cdr_count < record) {
+			ast_cond_timedwait(&mock_cdr_cond, &actual_cdr_entries.lock, &wait_time);
+		}
+		cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list);
+		AST_LIST_UNLOCK(&actual_cdr_entries);
+
+		if (!cdr_wrapper) {
+			ast_test_status_update(test, "Unable to find actual CDR record at %d\n", count);
+			return AST_TEST_FAIL;
+		}
+		actual = cdr_wrapper->cdr;
+
+		if (!expected && actual) {
+			ast_test_status_update(test, "CDRs recorded where no record expected\n");
+			return AST_TEST_FAIL;
+		}
+
+		VERIFY_STRING_FIELD(accountcode, actual, expected);
+		VERIFY_NUMERIC_FIELD(amaflags, actual, expected);
+		VERIFY_STRING_FIELD(channel, actual, expected);
+		VERIFY_STRING_FIELD(clid, actual, expected);
+		VERIFY_STRING_FIELD(dcontext, actual, expected);
+		VERIFY_NUMERIC_FIELD(disposition, actual, expected);
+		VERIFY_STRING_FIELD(dst, actual, expected);
+		VERIFY_STRING_FIELD(dstchannel, actual, expected);
+		VERIFY_STRING_FIELD(lastapp, actual, expected);
+		VERIFY_STRING_FIELD(lastdata, actual, expected);
+		VERIFY_STRING_FIELD(linkedid, actual, expected);
+		VERIFY_STRING_FIELD(peeraccount, actual, expected);
+		VERIFY_STRING_FIELD(src, actual, expected);
+		VERIFY_STRING_FIELD(uniqueid, actual, expected);
+		VERIFY_STRING_FIELD(userfield, actual, expected);
+		VERIFY_TIME_VALUE(start, actual);
+		VERIFY_TIME_VALUE(end, actual);
+		/* Note: there's no way we can really calculate a duration or
+		 * billsec - the unit tests are too short. However, if billsec is
+		 * non-zero in the expected, then make sure we have an answer time
+		 */
+		if (expected->billsec) {
+			VERIFY_TIME_VALUE(answer, actual);
+		}
+		ast_test_debug(test, "Finished expected record %s, %s\n",
+				expected->channel, S_OR(expected->dstchannel, "<none>"));
+		expected = expected->next;
+		++count;
+	}
+	return res;
+}
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+	if (!chan) {
+		return;
+	}
+	ast_channel_release(chan);
+}
+
+AST_TEST_DEFINE(test_cdr_channel_creation)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test that a CDR is created when a channel is created";
+		info->description =
+			"Test that a CDR is created when a channel is created";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan, (&caller), &expected);
+
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_inbound_call)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Wait",
+		.lastdata = "1",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test inbound unanswered calls";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"inbound to Asterisk, executes some dialplan, but\n"
+			"is never answered.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+	EMULATE_APP_DATA(chan, 1, "Wait", "1");
+
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_outbound_call)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = {
+			.id.name.str = "",
+			.id.name.valid = 1,
+			.id.number.str = "",
+			.id.number.valid = 1, };
+	struct ast_cdr expected = {
+		.clid = "\"\" <>",
+		.dst = "s",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "AppDial",
+		.lastdata = "(Outgoing Line)",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test outbound unanswered calls";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"outbound to Asterisk but is never answered.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+	ast_channel_exten_set(chan, "s");
+	ast_channel_context_set(chan, "default");
+	ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED);
+	EMULATE_APP_DATA(chan, 0, "AppDial", "(Outgoing Line)");
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_party)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "VoiceMailMain",
+		.lastdata = "1",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, but only involves a single channel\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+	EMULATE_APP_DATA(chan, 1, "Answer", "");
+	ast_setstate(chan, AST_STATE_UP);
+	EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1");
+
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_bridge)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party entering/leaving a bridge";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, enters a bridge, and leaves it.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+	EMULATE_APP_DATA(chan, 1, "Answer", "");
+	ast_setstate(chan, AST_STATE_UP);
+	EMULATE_APP_DATA(chan, 2, "Bridge", "");
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_impart(bridge, chan, NULL, NULL, 0);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_depart(chan);
+
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_bridge_continue)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge_one, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_bridge *, bridge_two, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Wait",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	struct ast_cdr expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.next = &expected_two,
+	};
+
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party entering/leaving a bridge";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, enters a bridge, and leaves it.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan, &caller, &expected_one);
+	COPY_IDS(chan, &expected_two);
+
+	EMULATE_APP_DATA(chan, 1, "Answer", "");
+	ast_setstate(chan, AST_STATE_UP);
+	EMULATE_APP_DATA(chan, 2, "Bridge", "");
+
+	bridge_one = ast_bridge_basic_new();
+	ast_test_validate(test, bridge_one != NULL);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_impart(bridge_one, chan, NULL, NULL, 0);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_depart(chan);
+
+	EMULATE_APP_DATA(chan, 3, "Wait", "");
+
+	/* And then it hangs up */
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected_one, 2);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_a)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller caller_alice = ALICE_CALLERID;
+	struct ast_party_caller caller_bob = BOB_CALLERID;
+	struct ast_cdr bob_expected = {
+		.clid = "\"Bob\" <200>",
+		.src = "200",
+		.dst = "200",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "200",
+	};
+	struct ast_cdr alice_expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+		.next = &bob_expected,
+	};
+
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party entering/leaving a bridge";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, enters a bridge, and leaves it. In this scenario, the\n"
+			"Party A should answer the bridge first.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected);
+
+	CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+	ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+
+	EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+	ast_setstate(chan_alice, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+	ast_setstate(chan_bob, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_depart(chan_alice);
+	ast_bridge_depart(chan_bob);
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_twoparty_bridge_b)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller caller_alice = ALICE_CALLERID;
+	struct ast_party_caller caller_bob = BOB_CALLERID;
+	struct ast_cdr bob_expected = {
+		.clid = "\"Bob\" <200>",
+		.src = "200",
+		.dst = "200",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "200",
+	};
+	struct ast_cdr alice_expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+		.next = &bob_expected,
+	};
+
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party entering/leaving a bridge";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, enters a bridge, and leaves it. In this scenario, the\n"
+			"Party B should answer the bridge first.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected);
+
+	CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+	ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+
+	EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+	ast_setstate(chan_alice, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+
+	EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+	ast_setstate(chan_bob, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_depart(chan_alice);
+	ast_bridge_depart(chan_bob);
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_multiparty_bridge)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller caller_alice = ALICE_CALLERID;
+	struct ast_party_caller caller_bob = BOB_CALLERID;
+	struct ast_party_caller caller_charlie = CHARLIE_CALLERID;
+	struct ast_cdr charlie_expected = {
+		.clid = "\"Charlie\" <300>",
+		.src = "300",
+		.dst = "300",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Charlie",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "300",
+	};
+	struct ast_cdr bob_expected = {
+		.clid = "\"Bob\" <200>",
+		.src = "200",
+		.dst = "200",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Bob",
+		.dstchannel = CHANNEL_TECH_NAME "/Charlie",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "200",
+		.peeraccount = "300",
+		.next = &charlie_expected,
+	};
+	struct ast_cdr alice_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Charlie",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "300",
+		.next = &bob_expected,
+	};
+	struct ast_cdr alice_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Bridge",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+		.next = &alice_expected_two,
+	};
+
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party entering/leaving a multi-party bridge";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, enters a bridge, and leaves it. A total of three\n"
+			"parties perform this action.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan_alice, &caller_alice, &alice_expected_one);
+	COPY_IDS(chan_alice, &alice_expected_two);
+	CREATE_BOB_CHANNEL(chan_bob, &caller_bob, &bob_expected);
+	ast_copy_string(bob_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected.linkedid));
+	CREATE_CHARLIE_CHANNEL(chan_charlie, &caller_charlie, &charlie_expected);
+	ast_copy_string(charlie_expected.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected.linkedid));
+
+	EMULATE_APP_DATA(chan_alice, 1, "Answer", "");
+	ast_setstate(chan_alice, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+
+	EMULATE_APP_DATA(chan_bob, 1, "Answer", "");
+	ast_setstate(chan_bob, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	EMULATE_APP_DATA(chan_charlie, 1, "Answer", "");
+	ast_setstate(chan_charlie, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_charlie, 2, "Bridge", "");
+	ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_depart(chan_alice);
+	ast_bridge_depart(chan_bob);
+	ast_bridge_depart(chan_charlie);
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &alice_expected_one, 4);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_unanswered)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test CDRs for a dial that isn't answered";
+		info->description =
+			"Test the properties of a CDR for a channel that\n"
+			"performs a dial operation that isn't answered\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", "CDRTestChannel/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER");
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+
+AST_TEST_DEFINE(test_cdr_dial_busy)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_BUSY,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test CDRs for a dial that results in a busy";
+		info->description =
+			"Test the properties of a CDR for a channel that\n"
+			"performs a dial operation to an endpoint that's busy\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "BUSY");
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_congestion)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_CONGESTION,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test CDRs for a dial that results in congestion";
+		info->description =
+			"Test the properties of a CDR for a channel that\n"
+			"performs a dial operation to an endpoint that's congested\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, congestion_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CONGESTION");
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_unavailable)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test CDRs for a dial that results in unavailable";
+		info->description =
+			"Test the properties of a CDR for a channel that\n"
+			"performs a dial operation to an endpoint that's unavailable\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CHANUNAVAIL");
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_caller_cancel)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test CDRs for a dial where the caller cancels";
+		info->description =
+			"Test the properties of a CDR for a channel that\n"
+			"performs a dial operation to an endpoint but then decides\n"
+			"to hang up, cancelling the dial\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CANCEL");
+
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_parallel_failed)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr bob_expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+	struct ast_cdr charlie_expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Charlie",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_BUSY,
+		.accountcode = "100",
+		.peeraccount = "300",
+	};
+	struct ast_cdr david_expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/David",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_CONGESTION,
+		.accountcode = "100",
+		.peeraccount = "400",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	struct ast_cdr *expected = &bob_expected;
+	bob_expected.next = &charlie_expected;
+	charlie_expected.next = &david_expected;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test a parallel dial where all channels fail to answer";
+		info->description =
+			"This tests dialing three parties: Bob, Charlie, David. Charlie\n"
+			"returns BUSY; David returns CONGESTION; Bob fails to answer and\n"
+			"Alice hangs up. Three records are created for Alice as a result.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, congestion_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &bob_expected);
+	COPY_IDS(chan_caller, &charlie_expected);
+	COPY_IDS(chan_caller, &david_expected);
+
+	/* Channel enters Dial app */
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David");
+
+	/* Outbound channels are created */
+	chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+
+	chan_charlie = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "300", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Charlie");
+	ast_set_flag(ast_channel_flags(chan_charlie), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_charlie, 0, "AppDial", "(Outgoing Line)");
+
+	chan_david = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "400", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/David");
+	ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)");
+
+	/* Dial starts */
+	ast_channel_publish_dial(chan_caller, chan_bob, "Bob", NULL);
+	ast_channel_publish_dial(chan_caller, chan_charlie, "Charlie", NULL);
+	ast_channel_publish_dial(chan_caller, chan_david, "David", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+
+	/* Charlie is busy */
+	ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY");
+	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY);
+
+	/* David is congested */
+	ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION");
+	HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION);
+
+	/* Bob is canceled */
+	ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL");
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+	/* Alice hangs up */
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, expected, 3);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_no_bridge)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr bob_expected_one = {
+		.clid = "\"\" <>",
+		.src = "",
+		.dst = "s",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Wait",
+		.lastdata = "1",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "200",
+	};
+	struct ast_cdr alice_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Wait",
+		.lastdata = "1",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.next = &bob_expected_one,
+	};
+	struct ast_cdr alice_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+		.next = &alice_expected_two,
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test dialing, answering, and not going into a bridge.";
+		info->description =
+			"This is a weird one, but theoretically possible. You can perform\n"
+			"a dial, then bounce both channels to different priorities and\n"
+			"never have them enter a bridge together. Ew. This makes sure that\n"
+			"when we answer, we get a CDR, it gets ended at that point, and\n"
+			"that it gets finalized appropriately. We should get three CDRs in\n"
+			"the end - one for the dial, and one for each CDR as they continued\n"
+			"on.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, debug_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &alice_expected_one);
+	COPY_IDS(chan_caller, &alice_expected_two);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	COPY_IDS(chan_callee, &bob_expected_one);
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+	ast_channel_state_set(chan_caller, AST_STATE_UP);
+	ast_clear_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+	EMULATE_APP_DATA(chan_caller, 2, "Wait", "1");
+	EMULATE_APP_DATA(chan_callee, 1, "Wait", "1");
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &alice_expected_one, 3);
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_a)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test dialing, answering, and going into a 2-party bridge";
+		info->description =
+			"The most 'basic' of scenarios\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, debug_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+	ast_channel_state_set(chan_caller, AST_STATE_UP);
+	ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0);
+	ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_bridge_depart(chan_caller);
+	ast_bridge_depart(chan_callee);
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_twoparty_bridge_b)
+{
+	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test dialing, answering, and going into a 2-party bridge";
+		info->description =
+			"The most 'basic' of scenarios\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, debug_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller, &expected);
+
+	EMULATE_APP_DATA(chan_caller, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "200", NULL, NULL, ast_channel_linkedid(chan_caller), 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_callee, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_caller, chan_callee, "Bob", NULL);
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER");
+
+	ast_channel_state_set(chan_caller, AST_STATE_UP);
+	ast_channel_state_set(chan_callee, AST_STATE_UP);
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_depart(chan_caller);
+	ast_bridge_depart(chan_callee);
+
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &expected, 1);
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	struct ast_party_caller alice_caller = ALICE_CALLERID;
+	struct ast_party_caller charlie_caller = CHARLIE_CALLERID;
+	struct ast_cdr charlie_expected_two = {
+		.clid = "\"Charlie\" <300>",
+		.src = "300",
+		.dst = "300",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Charlie",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/David",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "300",
+		.peeraccount = "200",
+	};
+	struct ast_cdr charlie_expected_one = {
+		.clid = "\"Charlie\" <300>",
+		.src = "300",
+		.dst = "300",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Charlie",
+		.dstchannel = CHANNEL_TECH_NAME "/David",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/David",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "300",
+		.peeraccount = "400",
+		.next = &charlie_expected_two,
+	};
+	struct ast_cdr alice_expected_three = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/David",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "400",
+		.next = &charlie_expected_one,
+	};
+	struct ast_cdr alice_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Charlie",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "300",
+		.next = &alice_expected_three,
+	};
+	struct ast_cdr alice_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Dial",
+		.lastdata = CHANNEL_TECH_NAME "/Bob",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "200",
+		.next = &alice_expected_two,
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test dialing, answering, and going into a multi-party bridge";
+		info->description =
+			"A little tricky to get to do, but possible with some redirects.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, debug_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected_one);
+	COPY_IDS(chan_alice, &alice_expected_two);
+	COPY_IDS(chan_alice, &alice_expected_three);
+
+	EMULATE_APP_DATA(chan_alice, 1, "Dial", CHANNEL_TECH_NAME "/Bob");
+
+	chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob");
+	ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+
+	CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller, &charlie_expected_one);
+	EMULATE_APP_DATA(chan_charlie, 1, "Dial", CHANNEL_TECH_NAME "/David");
+	ast_copy_string(charlie_expected_one.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_one.uniqueid));
+	ast_copy_string(charlie_expected_one.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_one.linkedid));
+	ast_copy_string(charlie_expected_two.uniqueid, ast_channel_uniqueid(chan_charlie), sizeof(charlie_expected_two.uniqueid));
+	ast_copy_string(charlie_expected_two.linkedid, ast_channel_linkedid(chan_alice), sizeof(charlie_expected_two.linkedid));
+
+	chan_david = ast_channel_alloc(0, AST_STATE_DOWN, "400", "David", "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David");
+	ast_set_flag(ast_channel_flags(chan_david), AST_FLAG_OUTGOING);
+	EMULATE_APP_DATA(chan_david, 0, "AppDial", "(Outgoing Line)");
+
+	ast_channel_publish_dial(chan_alice, chan_bob, "Bob", NULL);
+	ast_channel_state_set(chan_alice, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_charlie, chan_david, "David", NULL);
+	ast_channel_state_set(chan_charlie, AST_STATE_RINGING);
+	ast_channel_publish_dial(chan_alice, chan_bob, NULL, "ANSWER");
+	ast_channel_publish_dial(chan_charlie, chan_david, NULL, "ANSWER");
+
+	ast_channel_state_set(chan_alice, AST_STATE_UP);
+	ast_channel_state_set(chan_bob, AST_STATE_UP);
+	ast_channel_state_set(chan_charlie, AST_STATE_UP);
+	ast_channel_state_set(chan_david, AST_STATE_UP);
+
+	bridge = ast_bridge_basic_new();
+	ast_test_validate(test, bridge != NULL);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0));
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_david, NULL, NULL, 0));
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0));
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0));
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, 0 == ast_bridge_depart(chan_alice));
+	ast_test_validate(test, 0 == ast_bridge_depart(chan_bob));
+	ast_test_validate(test, 0 == ast_bridge_depart(chan_charlie));
+	ast_test_validate(test, 0 == ast_bridge_depart(chan_david));
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &alice_expected_one, 5);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_park)
+{
+	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller bob_caller = BOB_CALLERID;
+	struct ast_party_caller alice_caller = ALICE_CALLERID;
+	struct ast_cdr bob_expected = {
+		.clid = "\"Bob\" <200>",
+		.src = "200",
+		.dst = "200",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Bob",
+		.lastapp = "Park",
+		.lastdata = "701",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "200",
+	};
+	struct ast_cdr alice_expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Park",
+		.lastdata = "700",
+		.billsec = 1,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.next = &bob_expected,
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test cdrs for a single party entering Park";
+		info->description =
+			"Test the properties of a CDR for calls that are\n"
+			"answered, enters Park, and leaves it.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	SWAP_CONFIG(config, debug_cdr_config);
+	CREATE_ALICE_CHANNEL(chan_alice, &alice_caller, &alice_expected);
+	CREATE_BOB_CHANNEL(chan_bob, &bob_caller, &bob_expected);
+
+	EMULATE_APP_DATA(chan_alice, 1, "Park", "700");
+	ast_setstate(chan_alice, AST_STATE_UP);
+	EMULATE_APP_DATA(chan_bob, 1, "Park", "701");
+	ast_setstate(chan_bob, AST_STATE_UP);
+
+	bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING,
+		AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+			| AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+	ast_test_validate(test, bridge != NULL);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_bridge_depart(chan_alice);
+	ast_bridge_depart(chan_bob);
+
+	/* And then it hangs up */
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL);
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL);
+
+	result = verify_mock_cdr_record(test, &alice_expected, 2);
+
+	return result;
+}
+
+
+AST_TEST_DEFINE(test_cdr_fields)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	char varbuffer[128];
+	int int_buffer;
+	double db_buffer;
+	struct timespec to_sleep = {2, 0};
+	struct ast_flags fork_options = { 0, };
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr original = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Wait",
+		.lastdata = "10",
+		.billsec = 0,
+		.amaflags = AST_AMA_OMIT,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "XXX",
+		.userfield = "yackity",
+	};
+	struct ast_cdr fork_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Wait",
+		.lastdata = "10",
+		.billsec = 0,
+		.amaflags = AST_AMA_OMIT,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "XXX",
+		.userfield = "yackity",
+	};
+	struct ast_cdr fork_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.lastapp = "Answer",
+		.billsec = 0,
+		.amaflags = AST_AMA_OMIT,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "ZZZ",
+		.userfield = "schmackity",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	struct ast_cdr *expected = &original;
+	original.next = &fork_expected_one;
+	fork_expected_one.next = &fork_expected_two;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test field access CDRs";
+		info->description =
+			"This tests setting/retrieving data on CDR records.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan, &caller, &original);
+	ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+	ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+	ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+	ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+	/* Channel enters Wait app */
+	ast_channel_appl_set(chan, "Wait");
+	ast_channel_data_set(chan, "10");
+	ast_channel_priority_set(chan, 1);
+	ast_channel_publish_snapshot(chan);
+
+	/* Set properties on the channel that propagate to the CDR */
+	ast_channel_amaflags_set(chan, AST_AMA_OMIT);
+	ast_channel_accountcode_set(chan, "XXX");
+
+	/* Wait one second so we get a duration. */
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	ast_cdr_setuserfield(ast_channel_name(chan), "foobar");
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+
+	/* Verify that we can't set read-only fields or other fields directly */
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "clid", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "src", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dst", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dcontext", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "channel", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dstchannel", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastapp", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastdata", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "start", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "answer", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "end", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "duration", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "billsec", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "disposition", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "amaflags", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "accountcode", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "uniqueid", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "linkedid", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "userfield", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "sequence", "junk") != 0);
+
+	/* Verify the values */
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "userfield", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "foobar") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "amaflags", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%d", &int_buffer);
+	ast_test_validate(test, int_buffer == AST_AMA_OMIT);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "accountcode", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "XXX") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "clid", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "\"Alice\" <100>") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "src", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dst", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dcontext", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "default") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "channel", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, CHANNEL_TECH_NAME "/Alice") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dstchannel", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastapp", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "Wait") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastdata", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "10") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) > 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) < EPSILON);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "end", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) < EPSILON);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "duration", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) > 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "billsec", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) < EPSILON);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "disposition", varbuffer, sizeof(varbuffer)) == 0);
+	sscanf(varbuffer, "%d", &int_buffer);
+	ast_test_validate(test, int_buffer == AST_CDR_NULL);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "uniqueid", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, ast_channel_uniqueid(chan)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "linkedid", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, ast_channel_linkedid(chan)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "sequence", varbuffer, sizeof(varbuffer)) == 0);
+
+	/* Fork the CDR, and check that we change the properties on both CDRs. */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Change some properties */
+	ast_cdr_setuserfield(ast_channel_name(chan), "yackity");
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1b") == 0);
+
+	/* Fork the CDR again, finalizing all current CDRs */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS | AST_CDR_FLAG_FINALIZE);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Channel enters Answer app */
+	ast_channel_appl_set(chan, "Answer");
+	ast_channel_data_set(chan, "");
+	ast_channel_priority_set(chan, 1);
+	ast_channel_publish_snapshot(chan);
+	ast_setstate(chan, AST_STATE_UP);
+
+	/* Set properties on the last record */
+	ast_channel_accountcode_set(chan, "ZZZ");
+	ast_cdr_setuserfield(ast_channel_name(chan), "schmackity");
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+
+	/* Hang up and verify */
+	ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+	result = verify_mock_cdr_record(test, expected, 3);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_no_reset_cdr)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct ast_flags fork_options = { 0, };
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test field access CDRs";
+		info->description =
+			"This tests setting/retrieving data on CDR records.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan, &caller, &expected);
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	/* Disable the CDR */
+	ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+	/* Fork the CDR. This should be enabled */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Disable and enable the forked CDR */
+	ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+	ast_test_validate(test, ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+	/* Fork and finalize again. This CDR should be propagated */
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Disable all future CDRs */
+	ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL) == 0);
+
+	/* Fork a few more */
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_fork_cdr)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	char varbuffer[128];
+	char fork_varbuffer[128];
+	char answer_time[128];
+	char fork_answer_time[128];
+	char start_time[128];
+	char fork_start_time[128];
+	struct ast_flags fork_options = { 0, };
+	struct timespec to_sleep = {1, 10000};
+
+	struct ast_party_caller caller = ALICE_CALLERID;
+	struct ast_cdr original = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	struct ast_cdr fork_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	struct ast_cdr fork_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+	struct ast_cdr *expected = &original;
+	original.next = &fork_expected_one;
+	fork_expected_one.next = &fork_expected_two;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test field access CDRs";
+		info->description =
+			"This tests setting/retrieving data on CDR records.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, debug_cdr_config);
+
+	CREATE_ALICE_CHANNEL(chan, &caller, &original);
+	ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+	ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+	ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+	ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+
+	/* Test blowing away variables */
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+	ast_copy_string(varbuffer, "", sizeof(varbuffer));
+
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_1") != 0);
+
+	/* Test finalizing previous CDRs */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Test keep variables; setting a new answer time */
+	ast_setstate(chan, AST_STATE_UP);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", varbuffer, sizeof(varbuffer)) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_2") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", answer_time, sizeof(answer_time)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", start_time, sizeof(start_time)) == 0);
+
+	ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+	ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+	ast_set_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", fork_varbuffer, sizeof(fork_varbuffer)) == 0);
+	ast_test_validate(test, strcmp(fork_varbuffer, varbuffer) == 0);
+	ast_test_validate(test, strcmp(fork_start_time, start_time) == 0);
+	ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0);
+
+	ast_clear_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+	ast_set_flag(&fork_options, AST_CDR_FLAG_RESET);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", fork_answer_time, sizeof(fork_answer_time)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", fork_start_time, sizeof(fork_start_time)) == 0);
+	ast_test_validate(test, strcmp(fork_start_time, start_time) != 0);
+	ast_test_validate(test, strcmp(fork_answer_time, answer_time) != 0);
+
+	ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+	result = verify_mock_cdr_record(test, expected, 3);
+
+	return result;
+}
+
+/*!
+ * \internal \brief Callback function called before each test executes
+ */
+static int test_cdr_init_cb(struct ast_test_info *info, struct ast_test *test)
+{
+	/* Back up the real config */
+	saved_config = ast_cdr_get_config();
+	clear_mock_cdr_backend();
+	return 0;
+}
+
+/*!
+ * \internal \brief Callback function called after each test executes
+ */
+static int test_cdr_cleanup_cb(struct ast_test_info *info, struct ast_test *test)
+{
+	/* Restore the real config */
+	ast_cdr_set_config(saved_config);
+	ao2_cleanup(saved_config);
+	saved_config = NULL;
+	clear_mock_cdr_backend();
+
+	return 0;
+}
+
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(test_cdr_channel_creation);
+	AST_TEST_UNREGISTER(test_cdr_unanswered_inbound_call);
+	AST_TEST_UNREGISTER(test_cdr_unanswered_outbound_call);
+	AST_TEST_UNREGISTER(test_cdr_single_party);
+	AST_TEST_UNREGISTER(test_cdr_single_bridge);
+	AST_TEST_UNREGISTER(test_cdr_single_bridge_continue);
+	AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_a);
+	AST_TEST_UNREGISTER(test_cdr_single_twoparty_bridge_b);
+	AST_TEST_UNREGISTER(test_cdr_single_multiparty_bridge);
+
+	AST_TEST_UNREGISTER(test_cdr_dial_unanswered);
+	AST_TEST_UNREGISTER(test_cdr_dial_congestion);
+	AST_TEST_UNREGISTER(test_cdr_dial_busy);
+	AST_TEST_UNREGISTER(test_cdr_dial_unavailable);
+	AST_TEST_UNREGISTER(test_cdr_dial_caller_cancel);
+	AST_TEST_UNREGISTER(test_cdr_dial_parallel_failed);
+	AST_TEST_UNREGISTER(test_cdr_dial_answer_no_bridge);
+	AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_a);
+	AST_TEST_UNREGISTER(test_cdr_dial_answer_twoparty_bridge_b);
+	AST_TEST_UNREGISTER(test_cdr_dial_answer_multiparty);
+
+	AST_TEST_UNREGISTER(test_cdr_park);
+
+	AST_TEST_UNREGISTER(test_cdr_fields);
+	AST_TEST_UNREGISTER(test_cdr_no_reset_cdr);
+	AST_TEST_UNREGISTER(test_cdr_fork_cdr);
+
+	ast_cdr_unregister(MOCK_CDR_BACKEND);
+	ast_channel_unregister(&test_cdr_chan_tech);
+	clear_mock_cdr_backend();
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	ast_cond_init(&mock_cdr_cond, NULL);
+
+	AST_TEST_REGISTER(test_cdr_channel_creation);
+	AST_TEST_REGISTER(test_cdr_unanswered_inbound_call);
+	AST_TEST_REGISTER(test_cdr_unanswered_outbound_call);
+
+	AST_TEST_REGISTER(test_cdr_single_party);
+	AST_TEST_REGISTER(test_cdr_single_bridge);
+	AST_TEST_REGISTER(test_cdr_single_bridge_continue);
+	AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_a);
+	AST_TEST_REGISTER(test_cdr_single_twoparty_bridge_b);
+	AST_TEST_REGISTER(test_cdr_single_multiparty_bridge);
+
+	AST_TEST_REGISTER(test_cdr_dial_unanswered);
+	AST_TEST_REGISTER(test_cdr_dial_congestion);
+	AST_TEST_REGISTER(test_cdr_dial_busy);
+	AST_TEST_REGISTER(test_cdr_dial_unavailable);
+	AST_TEST_REGISTER(test_cdr_dial_caller_cancel);
+	AST_TEST_REGISTER(test_cdr_dial_parallel_failed);
+	AST_TEST_REGISTER(test_cdr_dial_answer_no_bridge);
+	AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_a);
+	AST_TEST_REGISTER(test_cdr_dial_answer_twoparty_bridge_b);
+	AST_TEST_REGISTER(test_cdr_dial_answer_multiparty);
+
+	AST_TEST_REGISTER(test_cdr_park);
+
+	AST_TEST_REGISTER(test_cdr_fields);
+	AST_TEST_REGISTER(test_cdr_no_reset_cdr);
+	AST_TEST_REGISTER(test_cdr_fork_cdr);
+
+	ast_test_register_init(TEST_CATEGORY, test_cdr_init_cb);
+	ast_test_register_cleanup(TEST_CATEGORY, test_cdr_cleanup_cb);
+
+	ast_channel_register(&test_cdr_chan_tech);
+	ast_cdr_register(MOCK_CDR_BACKEND, "Mock CDR backend", mock_cdr_backend_cb);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "CDR unit tests");