Replace most uses of ast_register_atexit with ast_register_cleanup.
[asterisk/asterisk.git] / main / cdr.c
index 9f710fe..5e24dae 100644 (file)
@@ -65,9 +65,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/data.h"
 #include "asterisk/config_options.h"
 #include "asterisk/json.h"
+#include "asterisk/parking.h"
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_channels.h"
-#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_bridges.h"
 #include "asterisk/stasis_message_router.h"
 #include "asterisk/astobj2.h"
 
@@ -97,19 +98,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        </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>
+                                       <synopsis>Log calls that are never answered and don't set an outgoing party.</synopsis>
+                                       <description><para>
+                                       Define whether or not to log unanswered calls that don't involve an outgoing party. Setting
+                                       this to "yes" will make calls to extensions that don't answer and don't set a side B channel
+                                       (such as by using the Dial application) receive CDR log entries. If this option is set to
+                                       "no", then those log entries will not be created. Unanswered calls which get offered to an
+                                       outgoing line will always receive log entries regardless of this option, and that is the
+                                       intended behavior.
+                                       </para>
                                        </description>
                                </configOption>
                                <configOption name="congestion">
@@ -119,12 +116,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        </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>
+                                       <synopsis>Don't produce CDRs while executing hangup logic</synopsis>
+                                       <description>
+                                               <para>As each CDR for a channel is finished, its end time is updated
+                                               and the CDR is finalized. When a channel is hung up and hangup
+                                               logic is present (in the form of a hangup handler or the
+                                               <literal>h</literal> extension), a new CDR is generated for the
+                                               channel. Any statistics are gathered from this new CDR. By enabling
+                                               this option, no new CDR is created for the dialplan logic that is
+                                               executed in <literal>h</literal> extensions or attached hangup handler
+                                               subroutines. The default value is <literal>yes</literal>, indicating
+                                               that a CDR will be generated during hangup logic.</para>
                                        </description>
                                </configOption>
                                <configOption name="initiatedseconds">
@@ -186,11 +188,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  ***/
 
 
+/* The prime here should be similar in size to the channel container. */
+#ifdef LOW_MEMORY
+#define NUM_CDR_BUCKETS 61
+#else
+#define NUM_CDR_BUCKETS 769
+#endif
+
 #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_END_BEFORE_H_EXTEN "1"
 #define DEFAULT_INITIATED_SECONDS "0"
 
 #define DEFAULT_BATCH_SIZE "100"
@@ -202,12 +211,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #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)
+               if (ast_test_flag(&(mod_cfg)->general->settings, CDR_DEBUG)) { \
+                       ast_verbose((fmt), ##__VA_ARGS__); \
+               } \
+       } while (0)
 
 static void cdr_detach(struct ast_cdr *cdr);
 static void cdr_submit_batch(int shutdown);
+static int cdr_toggle_runtime_options(void);
 
 /*! \brief The configuration settings for this module */
 struct module_config {
@@ -280,6 +291,7 @@ struct cdr_beitem {
        char desc[80];
        ast_cdrbe be;
        AST_RWLIST_ENTRY(cdr_beitem) list;
+       int suspended:1;
 };
 
 /*! \brief List of registered backends */
@@ -314,26 +326,49 @@ AST_MUTEX_DEFINE_STATIC(cdr_batch_lock);
 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 */
+/*! \brief A container of the active CDRs indexed by Party A channel id */
 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;
+static struct stasis_forward *bridge_subscription;
 
 /*! \brief Our subscription for channels */
-static struct stasis_subscription *channel_subscription;
+static struct stasis_forward *channel_subscription;
+
+/*! \brief Our subscription for parking */
+static struct stasis_forward *parking_subscription;
 
 /*! \brief The parent topic for all topics we want to aggregate for CDRs */
 static struct stasis_topic *cdr_topic;
 
+/*! \brief A message type used to synchronize with the CDR topic */
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_sync_message_type);
+
 struct cdr_object;
 
+/*! \brief Return types for \ref process_bridge_enter functions */
+enum process_bridge_enter_results {
+       /*!
+        * The CDR was the only party in the bridge.
+        */
+       BRIDGE_ENTER_ONLY_PARTY,
+       /*!
+        * The CDR was able to obtain a Party B from some other party already in the bridge
+        */
+       BRIDGE_ENTER_OBTAINED_PARTY_B,
+       /*!
+        * The CDR was not able to obtain a Party B
+        */
+       BRIDGE_ENTER_NO_PARTY_B,
+       /*!
+        * This CDR can't handle a bridge enter message and a new CDR needs to be created
+        */
+       BRIDGE_ENTER_NEED_CDR,
+};
+
 /*!
  * \brief A virtual table used for \ref cdr_object.
  *
@@ -416,15 +451,34 @@ struct cdr_object_fn_table {
         * of this channel into the bridge is handled by the higher level message
         * handler.
         *
+        * Note that this handler is for when a channel enters into a "normal"
+        * bridge, where people actually talk to each other. Parking is its own
+        * thing.
+        *
         * \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
+        * \retval process_bridge_enter_results Defines whether or not this CDR was able
+        * to fully handle the bridge enter message.
+        */
+       enum process_bridge_enter_results (* const process_bridge_enter)(
+                       struct cdr_object *cdr,
+                       struct ast_bridge_snapshot *bridge,
+                       struct ast_channel_snapshot *channel);
+
+       /*!
+        * \brief Process entering into a parking bridge.
+        *
+        * \param cdr The \ref cdr_object
+        * \param bridge The parking bridge that Party A just entered into
+        * \param channel The \ref ast_channel_snapshot for this CDR's Party A
+        *
+        * \retval 0 This CDR successfully transitioned itself into the parked state
+        * \retval 1 This CDR couldn't handle the parking transition and we need a
+        *  new CDR.
         */
-       int (* const process_bridge_enter)(struct cdr_object *cdr,
+       int (* const process_parking_bridge_enter)(struct cdr_object *cdr,
                        struct ast_bridge_snapshot *bridge,
                        struct ast_channel_snapshot *channel);
 
@@ -441,16 +495,31 @@ struct cdr_object_fn_table {
        int (* const process_bridge_leave)(struct cdr_object *cdr,
                        struct ast_bridge_snapshot *bridge,
                        struct ast_channel_snapshot *channel);
+
+       /*!
+        * \brief Process an update informing us that the channel got itself parked
+        *
+        * \param cdr The \ref cdr_object
+        * \param channel The parking information for this CDR's party A
+        *
+        * \retval 0 This CDR successfully parked itself
+        * \retval 1 This CDR couldn't handle the park
+        */
+       int (* const process_parked_channel)(struct cdr_object *cdr,
+                       struct ast_parked_call_payload *parking_info);
 };
 
 static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static enum process_bridge_enter_results base_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 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 int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info);
 
 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);
+static enum process_bridge_enter_results single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
  * \brief The virtual table for the Single state.
@@ -471,13 +540,15 @@ struct cdr_object_fn_table single_state_fn_table = {
        .process_dial_begin = single_state_process_dial_begin,
        .process_dial_end = base_process_dial_end,
        .process_bridge_enter = single_state_process_bridge_enter,
+       .process_parking_bridge_enter = single_state_process_parking_bridge_enter,
        .process_bridge_leave = base_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 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);
+static enum process_bridge_enter_results 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.
@@ -504,7 +575,8 @@ struct cdr_object_fn_table dial_state_fn_table = {
 
 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);
+static enum process_bridge_enter_results dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+static int dialed_pending_state_process_parking_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.
@@ -529,7 +601,9 @@ struct cdr_object_fn_table dialed_pending_state_fn_table = {
        .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_parking_bridge_enter = dialed_pending_state_process_parking_bridge_enter,
        .process_bridge_leave = base_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 static void bridge_state_process_party_b(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
@@ -543,40 +617,31 @@ static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_
  *
  * 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,
+       .process_parked_channel = base_process_parked_channel,
 };
 
-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);
+static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 /*!
- * \brief The virtual table for the Pending state
+ * \brief The virtual table for the Parked 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!
+ * Parking is weird. Unlike typical bridges, it has to be treated somewhat
+ * uniquely - a channel in a parking bridge (which is a subclass of a holding
+ * bridge) has to be handled as if the channel went into an application.
+ * However, when the channel comes out, we need a new CDR - unlike the Single
+ * state.
  */
-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,
+struct cdr_object_fn_table parked_state_fn_table = {
+       .name = "Parked",
+       .process_party_a = base_process_party_a,
+       .process_bridge_leave = parked_state_process_bridge_leave,
+       .process_parked_channel = base_process_parked_channel,
 };
 
 static void finalized_state_init_function(struct cdr_object *cdr);
@@ -592,6 +657,7 @@ 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,
+       .process_bridge_enter = base_process_bridge_enter,
 };
 
 /*! \brief A wrapper object around a snapshot.
@@ -618,10 +684,13 @@ struct cdr_object {
        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(uniqueid);                     /*!< Unique id of party A. Cached here as it is the primary key of this CDR */
                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 */
+               AST_STRING_FIELD(context);          /*!< The accepted context for Party A */
+               AST_STRING_FIELD(exten);            /*!< The accepted extension for Party A */
        );
        struct cdr_object *next;                /*!< The next CDR object in the chain */
        struct cdr_object *last;                /*!< The last CDR object in the chain */
@@ -635,17 +704,25 @@ struct cdr_object {
  */
 static int copy_variables(struct varshead *to_list, struct varshead *from_list)
 {
-       struct ast_var_t *variables, *newvariable = NULL;
-       const char *var, *val;
+       struct ast_var_t *variables;
+       struct ast_var_t *newvariable;
+       const char *var;
+       const char *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);
+               var = ast_var_name(variables);
+               if (ast_strlen_zero(var)) {
+                       continue;
+               }
+               val = ast_var_value(variables);
+               if (ast_strlen_zero(val)) {
+                       continue;
+               }
+               newvariable = ast_var_assign(var, val);
+               if (newvariable) {
                        AST_LIST_INSERT_HEAD(to_list, newvariable, entries);
-                       x++;
+                       ++x;
                }
        }
 
@@ -700,52 +777,58 @@ static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_objec
        }
 }
 /*! \internal
- * \brief Hash function for containers of CDRs indexing by Party A name */
+ * \brief Hash function for containers of CDRs indexing by Party A uniqueid */
 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);
-}
+       const struct cdr_object *cdr;
+       const char *key;
 
-/*! \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);
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       case OBJ_KEY:
+               key = obj;
+               break;
+       case OBJ_POINTER:
+               cdr = obj;
+               key = cdr->uniqueid;
+               break;
+       default:
+               ast_assert(0);
+               return 0;
+       }
+       return ast_str_case_hash(key);
 }
 
 /*! \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
+ * \brief Comparison function for containers of CDRs indexing by Party A uniqueid
  */
-static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
+static int cdr_object_channel_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;
+    struct cdr_object *left = obj;
+    struct cdr_object *right = arg;
+    const char *right_key = arg;
+    int cmp;
+
+    switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+    case OBJ_POINTER:
+        right_key = right->uniqueid;
+        /* Fall through */
+    case OBJ_KEY:
+        cmp = strcmp(left->uniqueid, right_key);
+        break;
+    case OBJ_PARTIAL_KEY:
+        /*
+         * We could also use a partial key struct containing a length
+         * so strlen() does not get called for every comparison instead.
+         */
+        cmp = strncmp(left->uniqueid, right_key, strlen(right_key));
+        break;
+    default:
+        /* Sort can only work on something with a full or partial key. */
+        ast_assert(0);
+        cmp = 0;
+        break;
+    }
+    return cmp ? 0 : CMP_MATCH;
 }
 
 /*!
@@ -756,10 +839,6 @@ 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))) {
@@ -770,9 +849,7 @@ static void cdr_object_dtor(void *obj)
        }
        ast_string_field_free_memory(cdr);
 
-       if (cdr->next) {
-               ao2_cleanup(cdr->next);
-       }
+       ao2_cleanup(cdr->next);
 }
 
 /*!
@@ -795,8 +872,10 @@ static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
        }
        cdr->last = cdr;
        if (ast_string_field_init(cdr, 64)) {
+               ao2_cleanup(cdr);
                return NULL;
        }
+       ast_string_field_set(cdr, uniqueid, chan->uniqueid);
        ast_string_field_set(cdr, name, chan->name);
        ast_string_field_set(cdr, linkedid, chan->linkedid);
        cdr->disposition = AST_CDR_NULL;
@@ -848,6 +927,42 @@ static struct cdr_object *cdr_object_create_and_append(struct cdr_object *cdr)
 }
 
 /*!
+ * \brief Return whether or not a channel has changed its state in the dialplan, subject
+ * to endbeforehexten logic
+ *
+ * \param old_snapshot The previous state
+ * \param new_snapshot The new state
+ *
+ * \retval 0 if the state has not changed
+ * \retval 1 if the state changed
+ */
+static int snapshot_cep_changed(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 we ignore hangup logic, don't indicate that we're executing anything new */
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)
+               && ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               return 0;
+       }
+
+       /* 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". Ignore application changes to AppDialX as a result.
+        */
+       if (strcmp(new_snapshot->appl, old_snapshot->appl) && strncasecmp(new_snapshot->appl, "appdial", 7)
+               && (strcmp(new_snapshot->context, old_snapshot->context)
+               || strcmp(new_snapshot->exten, old_snapshot->exten)
+               || new_snapshot->priority != old_snapshot->priority)) {
+               return 1;
+       }
+
+       return 0;
+}
+
+/*!
  * \brief Return whether or not a \ref ast_channel_snapshot is for a channel
  * that was created as the result of a dial operation
  *
@@ -892,7 +1007,7 @@ static struct cdr_object_snapshot *cdr_object_pick_party_a(struct cdr_object_sna
        } 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;
+               return right;
        } else {
                /* Okay, fine, take the left one */
                return left;
@@ -904,11 +1019,7 @@ static struct cdr_object_snapshot *cdr_object_pick_party_a(struct cdr_object_sna
  */
 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);
-       }
+       return (long)(ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->start) / 1000);
 }
 
 /*!
@@ -922,7 +1033,7 @@ static long cdr_object_get_billsec(struct cdr_object *cdr)
        if (ast_tvzero(cdr->answer)) {
                return 0;
        }
-       ms = ast_tvdiff_ms(cdr->end, cdr->answer);
+       ms = ast_tvdiff_ms(ast_tvzero(cdr->end) ? ast_tvnow() : cdr->end, cdr->answer);
        if (ast_test_flag(&mod_cfg->general->settings, CDR_INITIATED_SECONDS)
                && (ms % 1000 >= 500)) {
                ms = (ms / 1000) + 1;
@@ -934,6 +1045,32 @@ static long cdr_object_get_billsec(struct cdr_object *cdr)
 }
 
 /*!
+ * \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
+ */
+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 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
@@ -942,17 +1079,19 @@ static long cdr_object_get_billsec(struct cdr_object *cdr)
  */
 static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
 {
-       struct ast_cdr *pub_cdr = NULL, *cdr_prev;
+       struct ast_cdr *pub_cdr = NULL, *cdr_prev = NULL;
+       struct cdr_object *it_cdr;
        struct ast_var_t *it_var, *it_copy_var;
        struct ast_channel_snapshot *party_a;
        struct ast_channel_snapshot *party_b;
 
-       while (cdr) {
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
                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;
+               if (snapshot_is_dialed(it_cdr->party_a.snapshot) && !it_cdr->party_b.snapshot) {
+                       ast_debug(1, "CDR for %s is dialed and has no Party B; discarding\n",
+                               it_cdr->party_a.snapshot->name);
                        continue;
                }
 
@@ -962,8 +1101,8 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                        return NULL;
                }
 
-               party_a = cdr->party_a.snapshot;
-               party_b = cdr->party_b.snapshot;
+               party_a = it_cdr->party_a.snapshot;
+               party_b = it_cdr->party_b.snapshot;
 
                /* Party A */
                ast_assert(party_a != NULL);
@@ -973,49 +1112,49 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                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));
+               ast_copy_string(cdr_copy->lastapp, it_cdr->appl, sizeof(cdr_copy->lastapp));
+               ast_copy_string(cdr_copy->lastdata, it_cdr->data, sizeof(cdr_copy->lastdata));
+               ast_copy_string(cdr_copy->dst, it_cdr->exten, sizeof(cdr_copy->dst));
+               ast_copy_string(cdr_copy->dcontext, it_cdr->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(it_cdr->party_b.userfield)) {
+                               snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", it_cdr->party_a.userfield, it_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));
+               if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(it_cdr->party_a.userfield)) {
+                       ast_copy_string(cdr_copy->userfield, it_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);
+               cdr_copy->start = it_cdr->start;
+               cdr_copy->answer = it_cdr->answer;
+               cdr_copy->end = it_cdr->end;
+               cdr_copy->billsec = cdr_object_get_billsec(it_cdr);
+               cdr_copy->duration = cdr_object_get_duration(it_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;
+               ast_copy_flags(cdr_copy, &it_cdr->flags, AST_FLAGS_ALL);
+               ast_copy_string(cdr_copy->linkedid, it_cdr->linkedid, sizeof(cdr_copy->linkedid));
+               cdr_copy->disposition = it_cdr->disposition;
+               cdr_copy->sequence = it_cdr->sequence;
 
                /* Variables */
-               copy_variables(&cdr_copy->varshead, &cdr->party_a.variables);
-               AST_LIST_TRAVERSE(&cdr->party_b.variables, it_var, entries) {
+               copy_variables(&cdr_copy->varshead, &it_cdr->party_a.variables);
+               AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
                        int found = 0;
+                       struct ast_var_t *newvariable;
                        AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
-                               if (!strcmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
+                               if (!strcasecmp(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 (!found && (newvariable = ast_var_assign(ast_var_name(it_var), ast_var_value(it_var)))) {
+                               AST_LIST_INSERT_TAIL(&cdr_copy->varshead, newvariable, entries);
                        }
                }
 
@@ -1026,7 +1165,6 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
                        cdr_prev->next = cdr_copy;
                        cdr_prev = cdr_copy;
                }
-               cdr = cdr->next;
        }
 
        return pub_cdr;
@@ -1099,9 +1237,6 @@ static void cdr_object_set_disposition(struct cdr_object *cdr, int hangupcause)
  */
 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;
        }
@@ -1119,14 +1254,15 @@ static void cdr_object_finalize(struct cdr_object *cdr)
                }
        }
 
-       ast_debug(1, "Finalized CDR for %s - start %ld.%ld answer %ld.%ld end %ld.%ld dispo %s\n",
+       /* tv_usec is suseconds_t, which could be int or long */
+       ast_debug(1, "Finalized CDR for %s - start %ld.%06ld answer %ld.%06ld end %ld.%06ld 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,
+                       (long)cdr->start.tv_sec,
+                       (long)cdr->start.tv_usec,
+                       (long)cdr->answer.tv_sec,
+                       (long)cdr->answer.tv_usec,
+                       (long)cdr->end.tv_sec,
+                       (long)cdr->end.tv_usec,
                        ast_cdr_disp2str(cdr->disposition));
 }
 
@@ -1136,7 +1272,14 @@ static void cdr_object_finalize(struct cdr_object *cdr)
  */
 static void cdr_object_check_party_a_hangup(struct cdr_object *cdr)
 {
-       if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)
+       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)
+               && ast_test_flag(&cdr->party_a.snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               cdr_object_finalize(cdr);
+       }
+
+       if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_DEAD)
                && cdr->fn_table != &finalized_state_fn_table) {
                cdr_object_transition_state(cdr, &finalized_state_fn_table);
        }
@@ -1151,38 +1294,10 @@ static void cdr_object_check_party_a_answer(struct cdr_object *cdr) {
 
        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);
+               /* tv_usec is suseconds_t, which could be int or long */
+               CDR_DEBUG(mod_cfg, "%p - Set answered time to %ld.%06ld\n", cdr,
+                       (long)cdr->answer.tv_sec,
+                       (long)cdr->answer.tv_usec);
        }
 }
 
@@ -1229,7 +1344,25 @@ static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snaps
 {
        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);
+       ast_assert(strcasecmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
+
+       /* Ignore any snapshots from a dead or dying channel */
+       if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+                       && ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+               cdr_object_check_party_a_hangup(cdr);
+               return 0;
+       }
+
+       /*
+        * Only record the context and extension if we aren't in a subroutine, or if
+        * we are executing hangup logic.
+        */
+       if (!ast_test_flag(&snapshot->flags, AST_FLAG_SUBROUTINE_EXEC)
+               || ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)) {
+               ast_string_field_set(cdr, context, snapshot->context);
+               ast_string_field_set(cdr, exten, snapshot->exten);
+       }
+
        cdr_object_swap_snapshot(&cdr->party_a, snapshot);
 
        /* When Party A is originated to an application and the application exits, the stack
@@ -1237,9 +1370,20 @@ static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snaps
         * 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))) {
+       if (!ast_strlen_zero(snapshot->appl)
+                       && (strncasecmp(snapshot->appl, "appdial", 7) || ast_strlen_zero(cdr->appl))
+                       && !ast_test_flag(&cdr->flags, AST_CDR_LOCK_APP)) {
                ast_string_field_set(cdr, appl, snapshot->appl);
                ast_string_field_set(cdr, data, snapshot->data);
+
+               /* Dial (app_dial) is a special case. Because pre-dial handlers, which
+                * execute before the dial begins, will alter the application/data to
+                * something people typically don't want to see, if we see a channel enter
+                * into Dial here, we set the appl/data accordingly and lock it.
+                */
+               if (!strcmp(snapshot->appl, "Dial")) {
+                       ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
+               }
        }
 
        ast_string_field_set(cdr, linkedid, snapshot->linkedid);
@@ -1263,6 +1407,32 @@ static int base_process_dial_end(struct cdr_object *cdr, struct ast_channel_snap
        return 0;
 }
 
+static enum process_bridge_enter_results base_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+       /* Base process bridge enter simply indicates that we can't handle it */
+       return BRIDGE_ENTER_NEED_CDR;
+}
+
+static int base_process_parked_channel(struct cdr_object *cdr, struct ast_parked_call_payload *parking_info)
+{
+       char park_info[128];
+
+       ast_assert(!strcasecmp(parking_info->parkee->name, cdr->party_a.snapshot->name));
+
+       /* Update Party A information regardless */
+       cdr->fn_table->process_party_a(cdr, parking_info->parkee);
+
+       /* Fake out where we're parked */
+       ast_string_field_set(cdr, appl, "Park");
+       snprintf(park_info, sizeof(park_info), "%s:%u", parking_info->parkinglot, parking_info->parkingspace);
+       ast_string_field_set(cdr, data, park_info);
+
+       /* Prevent any further changes to the App/Data fields for this record */
+       ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
+
+       return 0;
+}
+
 /* SINGLE STATE */
 
 static void single_state_init_function(struct cdr_object *cdr) {
@@ -1282,16 +1452,22 @@ static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_ch
 {
        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);
+       if (caller && !strcasecmp(cdr->party_a.snapshot->name, caller->name)) {
+               base_process_party_a(cdr, 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)) {
+
+               /* If we have two parties, lock the application that caused the
+                * two parties to be associated. This prevents mid-call event
+                * macros/gosubs from perturbing the CDR application/data
+                */
+               ast_set_flag(&cdr->flags, AST_CDR_LOCK_APP);
+       } else if (!strcasecmp(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);
+               base_process_party_a(cdr, peer);
                CDR_DEBUG(mod_cfg, "%p - Updated Party A %s snapshot\n", cdr,
                                cdr->party_a.snapshot->name);
        }
@@ -1315,11 +1491,19 @@ static int single_state_process_dial_begin(struct cdr_object *cdr, struct ast_ch
 static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
                struct cdr_object *cand_cdr)
 {
+       RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object_snapshot *party_a;
 
+       /* Don't match on ourselves */
+       if (!strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)) {
+               return 1;
+       }
+
        /* 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)) {
+       if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+               CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+                       cdr, cdr->party_a.snapshot->name, cand_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
@@ -1331,12 +1515,15 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
                return 0;
        }
 
-       /* Try their Party B */
-       if (!cand_cdr->party_b.snapshot) {
+       /* Try their Party B, unless it's us */
+       if (!cand_cdr->party_b.snapshot
+               || !strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name)) {
                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)) {
+       if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+               CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+                       cdr, cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name);
                cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
                return 0;
        }
@@ -1344,27 +1531,32 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
        return 1;
 }
 
-static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+static enum process_bridge_enter_results 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;
+       struct ao2_iterator it_cdrs;
+       char *channel_id;
+       int success = 0;
 
        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! */
+       if (ao2_container_count(bridge->channels) == 1) {
+               /* No one in the bridge yet but us! */
                cdr_object_transition_state(cdr, &bridge_state_fn_table);
-               return 0;
+               return BRIDGE_ENTER_ONLY_PARTY;
        }
 
-       while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+       for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
+               !success && (channel_id = ao2_iterator_next(&it_cdrs));
+               ao2_ref(channel_id, -1)) {
+               RAII_VAR(struct cdr_object *, cand_cdr_master,
+                       ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
+                       ao2_cleanup);
                struct cdr_object *cand_cdr;
 
+               if (!cand_cdr_master) {
+                       continue;
+               }
+
                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.
@@ -1378,34 +1570,45 @@ static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_
                                continue;
                        }
                        /* We successfully got a party B - break out */
-                       success = 0;
+                       success = 1;
                        break;
                }
                ao2_unlock(cand_cdr_master);
-               ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference");
        }
-       ao2_iterator_destroy(it_cdrs);
+       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;
+       if (success) {
+               return BRIDGE_ENTER_OBTAINED_PARTY_B;
+       }
+
+       return BRIDGE_ENTER_NO_PARTY_B;
 }
 
+static int single_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+       cdr_object_transition_state(cdr, &parked_state_fn_table);
+       return 0;
+}
+
+
 /* 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)) {
+       if (!cdr->party_b.snapshot
+               || strcasecmp(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)) {
+       if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_DEAD)) {
                cdr_object_transition_state(cdr, &finalized_state_fn_table);
        }
 }
@@ -1418,7 +1621,10 @@ static int dial_state_process_dial_begin(struct cdr_object *cdr, struct ast_chan
        return 1;
 }
 
-/*! \internal \brief Convert a dial status to a CDR disposition */
+/*!
+ * \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,
@@ -1444,8 +1650,6 @@ static enum ast_cdr_disposition dial_status_to_disposition(const char *dial_stat
 
 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) {
@@ -1453,11 +1657,11 @@ static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channe
        } else {
                party_a = peer;
        }
-       ast_assert(!strcmp(cdr->party_a.snapshot->name, party_a->name));
+       ast_assert(!strcasecmp(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)) {
+               if (strcasecmp(cdr->party_b.snapshot->name, peer->name)) {
                        /* Not the status for this CDR - defer back to the message router */
                        return 1;
                }
@@ -1476,27 +1680,33 @@ static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channe
        return 0;
 }
 
-static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+static enum process_bridge_enter_results 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;
+       struct ao2_iterator it_cdrs;
+       char *channel_id;
+       int success = 0;
 
        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! */
+       if (ao2_container_count(bridge->channels) == 1) {
+               /* No one in the bridge yet but us! */
                cdr_object_transition_state(cdr, &bridge_state_fn_table);
-               return 0;
+               return BRIDGE_ENTER_ONLY_PARTY;
        }
 
-       while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+       for (it_cdrs = ao2_iterator_init(bridge->channels, 0);
+               !success && (channel_id = ao2_iterator_next(&it_cdrs));
+               ao2_ref(channel_id, -1)) {
+               RAII_VAR(struct cdr_object *, cand_cdr_master,
+                       ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
+                       ao2_cleanup);
                struct cdr_object *cand_cdr;
 
+               if (!cand_cdr_master) {
+                       continue;
+               }
+
                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.
@@ -1506,11 +1716,15 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
                                continue;
                        }
 
-                       /* Skip any records that aren't our Party B */
-                       if (strcmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) {
+                       /* If we don't have a Party B (originated channel), skip it */
+                       if (!cdr->party_b.snapshot) {
                                continue;
                        }
 
+                       /* Skip any records that aren't our Party B */
+                       if (strcasecmp(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
@@ -1519,19 +1733,21 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
                        if (!cand_cdr->party_b.snapshot) {
                                cdr_object_finalize(cand_cdr);
                        }
-                       success = 0;
+                       success = 1;
                        break;
                }
                ao2_unlock(cand_cdr_master);
-               ao2_t_ref(cand_cdr_master, -1, "Drop iterator reference");
        }
-       ao2_iterator_destroy(it_cdrs);
+       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;
+       if (success) {
+               return BRIDGE_ENTER_OBTAINED_PARTY_B;
+       }
+       return BRIDGE_ENTER_NO_PARTY_B;
 }
 
 /* DIALED PENDING STATE */
@@ -1541,10 +1757,7 @@ static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct a
        /* 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 (snapshot_cep_changed(cdr->party_a.snapshot, snapshot)) {
                if (cdr->party_b.snapshot) {
                        cdr_object_transition_state(cdr, &finalized_state_fn_table);
                        cdr->fn_table->process_party_a(cdr, snapshot);
@@ -1559,33 +1772,42 @@ static int dialed_pending_state_process_party_a(struct cdr_object *cdr, struct a
        return 0;
 }
 
-static int dialed_pending_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+static enum process_bridge_enter_results 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)
+static int dialed_pending_state_process_parking_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
-       struct cdr_object *new_cdr;
+       if (cdr->party_b.snapshot) {
+               /* We can't handle this as we have a Party B - ask for a new one */
+               return 1;
+       }
+       cdr_object_transition_state(cdr, &parked_state_fn_table);
+       return 0;
+}
 
+static int dialed_pending_state_process_dial_begin(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer)
+{
        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);
+
+       /* Ask for a new CDR */
+       return 1;
 }
 
 /* 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)) {
+       if (!cdr->party_b.snapshot
+               || strcasecmp(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)) {
+       if (ast_test_flag(&cdr->party_b.snapshot->flags, AST_FLAG_DEAD)) {
                cdr_object_transition_state(cdr, &finalized_state_fn_table);
        }
 }
@@ -1595,9 +1817,9 @@ static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_
        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)) {
+       if (strcasecmp(cdr->party_a.snapshot->name, channel->name)
+               && cdr->party_b.snapshot
+               && strcasecmp(cdr->party_b.snapshot->name, channel->name)) {
                return 1;
        }
        cdr_object_transition_state(cdr, &finalized_state_fn_table);
@@ -1605,69 +1827,68 @@ static int bridge_state_process_bridge_leave(struct cdr_object *cdr, struct ast_
        return 0;
 }
 
-/* PENDING STATE */
-
-static void pending_state_init_function(struct cdr_object *cdr)
-{
-       ast_cdr_set_property(cdr->name, AST_CDR_FLAG_DISABLE);
-}
+/* PARKED STATE */
 
-static int pending_state_process_party_a(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+static int parked_state_process_bridge_leave(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
 {
-       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;
+       if (strcasecmp(cdr->party_a.snapshot->name, channel->name)) {
+               return 1;
        }
+       cdr_object_transition_state(cdr, &finalized_state_fn_table);
 
-       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);
-       }
+       RAII_VAR(struct module_config *, mod_cfg,
+               ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+                       && ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+               return 0;
+       }
 
        /* Indicate that, if possible, we should get a new CDR */
        return 1;
 }
 
+/*!
+ * \internal
+ * \brief Filter channel snapshots by technology
+ */
+static int filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
+{
+       return snapshot->tech_properties & AST_CHAN_TP_INTERNAL;
+}
+
+/*!
+ * \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;
+}
+
 /* TOPIC ROUTER CALLBACKS */
 
 /*!
@@ -1677,25 +1898,18 @@ static int finalized_state_process_party_a(struct cdr_object *cdr, struct ast_ch
  * \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)
+static void handle_dial_message(void *data, struct stasis_subscription *sub, 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;
+       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
        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) {
@@ -1706,31 +1920,27 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                dial_status = ast_json_string_get(dial_status_blob);
        }
 
+       CDR_DEBUG(mod_cfg, "Dial %s message for %s, %s: %u.%08u\n",
+                       ast_strlen_zero(dial_status) ? "Begin" : "End",
+                       caller ? caller->name : "(none)",
+                       peer ? peer->name : "(none)",
+                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
+                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+       if (filter_channel_snapshot(peer) || (caller && filter_channel_snapshot(caller))) {
+               return;
+       }
+
        /* 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;
+               cdr = ao2_find(active_cdrs_by_channel, caller->uniqueid, OBJ_KEY);
        } else {
+               cdr = ao2_find(active_cdrs_by_channel, peer->uniqueid, OBJ_KEY);
+       }
+
+       if (!cdr) {
+               ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", caller ? caller->name : peer->name);
+               ast_assert(0);
                return;
        }
 
@@ -1741,23 +1951,23 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
                                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)");
+                                       it_cdr,
+                                       caller ? caller->name : "(none)",
+                                       peer ? peer->name : "(none)");
                        res &= it_cdr->fn_table->process_dial_begin(it_cdr,
-                                       party_a ? party_a->snapshot : NULL,
-                                       party_b ? party_b->snapshot : NULL);
+                                       caller,
+                                       peer);
                } 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,
+                                       caller ? caller->name : "(none)",
+                                       peer ? peer->name : "(none)");
                        it_cdr->fn_table->process_dial_end(it_cdr,
-                                       party_a ? party_a->snapshot : NULL,
-                                       party_b ? party_b->snapshot : NULL,
+                                       caller,
+                                       peer,
                                        dial_status);
                }
        }
@@ -1768,11 +1978,12 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
 
                new_cdr = cdr_object_create_and_append(cdr);
                if (!new_cdr) {
+                       ao2_unlock(cdr);
                        return;
                }
                new_cdr->fn_table->process_dial_begin(new_cdr,
-                               party_a ? party_a->snapshot : NULL,
-                               party_b ? party_b->snapshot : NULL);
+                               caller,
+                               peer);
        }
        ao2_unlock(cdr);
 }
@@ -1783,7 +1994,8 @@ static int cdr_object_finalize_party_b(void *obj, void *arg, int flags)
        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)) {
+               if (it_cdr->party_b.snapshot
+                       && !strcasecmp(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
                         */
@@ -1802,40 +2014,14 @@ static int cdr_object_update_party_b(void *obj, void *arg, int flags)
                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)) {
+               if (it_cdr->party_b.snapshot
+                       && !strcasecmp(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)
@@ -1843,11 +2029,10 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_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)) {
+       /* If we're dead, we don't need a new CDR */
+       if (!new_snapshot
+               || (ast_test_flag(&new_snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
+                       && ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN))) {
                return 0;
        }
 
@@ -1856,10 +2041,7 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
                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))) {
+       if (old_snapshot && !snapshot_cep_changed(old_snapshot, new_snapshot)) {
                return 0;
        }
 
@@ -1873,34 +2055,29 @@ static int check_new_cdr_needed(struct ast_channel_snapshot *old_snapshot,
  * \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)
+static void handle_channel_cache_message(void *data, struct stasis_subscription *sub, 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 *uniqueid;
        const char *name;
        struct cdr_object *it_cdr;
 
        ast_assert(update != NULL);
-       if (ast_channel_snapshot_type() != update->type) {
-               return;
-       }
+       ast_assert(ast_channel_snapshot_type() == update->type);
 
        old_snapshot = stasis_message_data(update->old_snapshot);
        new_snapshot = stasis_message_data(update->new_snapshot);
+       uniqueid = new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid;
        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) {
@@ -1911,10 +2088,11 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
 
        /* Handle Party A */
        if (!cdr) {
-               cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
+               cdr = ao2_find(active_cdrs_by_channel, uniqueid, OBJ_KEY);
        }
        if (!cdr) {
                ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", name);
+               ast_assert(0);
        } else {
                ao2_lock(cdr);
                if (new_snapshot) {
@@ -1923,14 +2101,15 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
                                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);
+                               if (new_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);
@@ -1976,16 +2155,13 @@ static int cdr_object_party_b_left_bridge_cb(void *obj, void *arg, int flags)
                if (!it_cdr->party_b.snapshot) {
                        continue;
                }
-               if (strcmp(it_cdr->party_b.snapshot->name, leave_data->channel->name)) {
+               if (strcasecmp(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);
-               }
+               /* It is our Party B, in our bridge. Set the end time and let the handler
+                * transition our CDR appropriately when we leave the bridge.
+                */
+               cdr_object_finalize(it_cdr);
        }
        return 0;
 }
@@ -1996,7 +2172,7 @@ 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")) {
+       if (!strcmp(bridge->technology, "holding_bridge") && strcmp(bridge->subclass, "parking")) {
                return 1;
        }
        return 0;
@@ -2004,11 +2180,13 @@ static int filter_bridge_messages(struct ast_bridge_snapshot *bridge)
 
 /*!
  * \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
+ * \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_leave_message(void *data, struct stasis_subscription *sub,
-               struct stasis_topic *topic, struct stasis_message *message)
+               struct stasis_message *message)
 {
        struct ast_bridge_blob *update = stasis_message_data(message);
        struct ast_bridge_snapshot *bridge = update->bridge;
@@ -2016,10 +2194,9 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
        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_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY),
                        ao2_cleanup);
        struct cdr_object *it_cdr;
-       struct cdr_object *pending_cdr;
        struct bridge_leave_data leave_data = {
                .bridge = bridge,
                .channel = channel,
@@ -2030,10 +2207,18 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                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 (filter_channel_snapshot(channel)) {
+               return;
+       }
+
+       CDR_DEBUG(mod_cfg, "Bridge Leave message for %s: %u.%08u\n",
+                       channel->name,
+                       (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);
+               ast_assert(0);
                return;
        }
 
@@ -2050,268 +2235,105 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
                        left_bridge = 1;
                }
        }
+       ao2_unlock(cdr);
        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 */
-};
-
-/*! \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;
-
-       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);
-
-       return bcand;
-}
-
-/*!
- * \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;
-
-       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);
-
-               party_snapshot = party_a ? &it_cdr->party_a : &it_cdr->party_b;
-
-               if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge_id, it_cdr->bridge)) {
-                       continue;
-               }
-
-               if (!party_snapshot->snapshot) {
-                       continue;
-               }
-
-               /* Don't add a party twice */
-               bcand = ao2_find(candidates, party_snapshot->snapshot->name, OBJ_KEY);
-               if (bcand) {
-                       continue;
-               }
-
-               bcand = bridge_candidate_alloc(it_cdr, party_snapshot);
-               if (bcand) {
-                       ao2_link(candidates, bcand);
-               }
+       if (strcmp(bridge->subclass, "parking")) {
+               /* Party B */
+               ao2_callback(active_cdrs_by_channel, OBJ_NODATA,
+                               cdr_object_party_b_left_bridge_cb,
+                               &leave_data);
        }
 }
 
 /*!
- * \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
+ * \internal
+ * \brief Create a new CDR, append it to an existing CDR, and update its snapshots
  *
- * 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;
-
-       if (!candidates) {
-               return NULL;
-       }
-
-       /* 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);
-
-       /* 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);
-
-       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)
 {
+       RAII_VAR(struct module_config *,  mod_cfg,
+               ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object *new_cdr;
 
        new_cdr = cdr_object_create_and_append(cdr);
+       if (!new_cdr) {
+               return;
+       }
        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);
+       CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+               new_cdr, new_cdr->party_a.snapshot->name,
+               party_b->snapshot->name);
 }
 
 /*!
- * \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.
+ * \brief Process a single \ref bridge_candidate
+ *
+ * When a CDR enters a bridge, it needs to make pairings with everyone else
+ * that it is not currently paired with. This function determines, for the
+ * CDR for the channel that entered the bridge and the CDR for every other
+ * channel currently in the bridge, who is Party A and makes new CDRs.
  *
- * \param obj The \ref bridge_candidate being processed
- * \param arg The \ref cdr_object that is being compared against the candidates
+ * \param cdr The \ref cdr_obj being processed
+ * \param cand_cdr The \ref cdr_object that is a candidate
  *
- * 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)
+static int bridge_candidate_process(struct cdr_object *cdr, struct cdr_object *base_cand_cdr)
 {
-       struct bridge_candidate *bcand = obj;
-       struct cdr_object *cdr = arg;
+       RAII_VAR(struct module_config *, mod_cfg,
+               ao2_global_obj_ref(module_configs), ao2_cleanup);
        struct cdr_object_snapshot *party_a;
+       struct cdr_object *cand_cdr;
 
-       /* 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;
-       }
+       SCOPED_AO2LOCK(lock, base_cand_cdr);
 
-       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;
-       }
+       for (cand_cdr = base_cand_cdr; cand_cdr; cand_cdr = cand_cdr->next) {
+               /* Skip any records that are not in this bridge */
+               if (strcmp(cand_cdr->bridge, cdr->bridge)) {
+                       continue;
+               }
 
-       /* 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);
+               /* If the candidate is us or someone we've taken on, pass on by */
+               if (!strcasecmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)
+                       || (cdr->party_b.snapshot
+                               && !strcasecmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name))) {
+                       return 0;
+               }
+
+               party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
+               /* We're party A - make a new CDR, append it to us, and set the candidate as
+                * Party B */
+               if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
+                       bridge_candidate_add_to_cdr(cdr, &cand_cdr->party_a);
+                       return 0;
+               }
+
+               /* We're Party B. Check if we can add ourselves immediately or if we need
+                * a new CDR for them (they already have a Party B) */
+               if (cand_cdr->party_b.snapshot
+                       && strcasecmp(cand_cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
+                       bridge_candidate_add_to_cdr(cand_cdr, &cdr->party_a);
                } else {
-                       cdr_object_snapshot_copy(&bcand->cdr->party_b, &cdr->party_a);
+                       CDR_DEBUG(mod_cfg, "%p - Party A %s has new Party B %s\n",
+                               cand_cdr, cand_cdr->party_a.snapshot->name,
+                               cdr->party_a.snapshot->name);
+                       cdr_object_snapshot_copy(&cand_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));
+                       memset(&cand_cdr->end, 0, sizeof(cand_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;
        }
-
        return 0;
 }
 
@@ -2323,53 +2345,82 @@ static int bridge_candidate_process(void *obj, void *arg, int flags)
  */
 static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge)
 {
-       RAII_VAR(struct ao2_container *, candidates,
-                       create_candidates_for_bridge(bridge),
+       struct ao2_iterator it_channels;
+       char *channel_id;
+
+       it_channels = ao2_iterator_init(bridge->channels, 0);
+       while ((channel_id = ao2_iterator_next(&it_channels))) {
+               RAII_VAR(struct cdr_object *, cand_cdr,
+                       ao2_find(active_cdrs_by_channel, channel_id, OBJ_KEY),
                        ao2_cleanup);
 
-       if (!candidates) {
-               return;
-       }
+               if (!cand_cdr) {
+                       ao2_ref(channel_id, -1);
+                       continue;
+               }
 
-       ao2_callback(candidates, OBJ_NODATA,
-                       bridge_candidate_process,
-                       cdr);
+               bridge_candidate_process(cdr, cand_cdr);
 
-       return;
+               ao2_ref(channel_id, -1);
+       }
+       ao2_iterator_destroy(&it_channels);
 }
 
-/*!
- * \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!
+/*! \brief Handle entering into a parking bridge
+ * \param cdr The CDR to operate on
+ * \param bridge The bridge the channel just entered
+ * \param channel The channel snapshot
  */
-static void handle_bridge_enter_message(void *data, struct stasis_subscription *sub,
-               struct stasis_topic *topic, struct stasis_message *message)
+static void handle_parking_bridge_enter_message(struct cdr_object *cdr,
+               struct ast_bridge_snapshot *bridge,
+               struct ast_channel_snapshot *channel)
 {
-       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;
+       struct cdr_object *new_cdr;
 
-       if (filter_bridge_messages(bridge)) {
-               return;
-       }
+       ao2_lock(cdr);
 
-       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);
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+               if (it_cdr->fn_table->process_parking_bridge_enter) {
+                       res &= it_cdr->fn_table->process_parking_bridge_enter(it_cdr, bridge, channel);
+               }
+               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);
+               }
+       }
 
-       if (!cdr) {
-               ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
-               return;
+       if (res) {
+               /* No one handled it - we need a new one! */
+               new_cdr = cdr_object_create_and_append(cdr);
+               if (new_cdr) {
+                       /* Let the single state transition us to Parked */
+                       cdr_object_transition_state(new_cdr, &single_state_fn_table);
+                       new_cdr->fn_table->process_parking_bridge_enter(new_cdr, bridge, channel);
+               }
        }
+       ao2_unlock(cdr);
+}
+
+/*! \brief Handle a bridge enter message for a 'normal' bridge
+ * \param cdr The CDR to operate on
+ * \param bridge The bridge the channel just entered
+ * \param channel The channel snapshot
+ */
+static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
+               struct ast_bridge_snapshot *bridge,
+               struct ast_channel_snapshot *channel)
+{
+       RAII_VAR(struct module_config *, mod_cfg,
+                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+       enum process_bridge_enter_results result;
+       struct cdr_object *it_cdr;
+       struct cdr_object *new_cdr;
+       struct cdr_object *handled_cdr = NULL;
 
        ao2_lock(cdr);
 
@@ -2384,20 +2435,31 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
                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;
+                       result = it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
+                       switch (result) {
+                       case BRIDGE_ENTER_ONLY_PARTY:
+                               /* Fall through */
+                       case BRIDGE_ENTER_OBTAINED_PARTY_B:
+                               if (!handled_cdr) {
+                                       handled_cdr = it_cdr;
+                               }
+                               break;
+                       case BRIDGE_ENTER_NEED_CDR:
+                               /* Pass */
+                               break;
+                       case BRIDGE_ENTER_NO_PARTY_B:
+                               /* 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.
+                                */
+                               if (!handled_cdr) {
+                                       handled_cdr = it_cdr;
+                               }
+                               cdr_object_finalize(cdr);
+                               break;
                        }
                }
        }
 
-       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.
@@ -2405,13 +2467,145 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
         *    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;
+       if (handled_cdr) {
+               handle_bridge_pairings(handled_cdr, bridge);
+       } else {
+               /* Nothing handled it - we need a new one! */
+               new_cdr = cdr_object_create_and_append(cdr);
+               if (new_cdr) {
+                       /* This is guaranteed to succeed: the new CDR is created in the single state
+                        * and will be able to handle the bridge enter message
+                        */
+                       handle_standard_bridge_enter_message(cdr, bridge, channel);
+               }
+       }
+       ao2_unlock(cdr);
+}
+
+/*!
+ * \internal
+ * \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_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->uniqueid, OBJ_KEY),
+                       ao2_cleanup);
+       RAII_VAR(struct module_config *, mod_cfg,
+                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       if (filter_bridge_messages(bridge)) {
+               return;
+       }
+
+       if (filter_channel_snapshot(channel)) {
+               return;
+       }
+
+       CDR_DEBUG(mod_cfg, "Bridge Enter message for channel %s: %u.%08u\n",
+                       channel->name,
+                       (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);
+               ast_assert(0);
+               return;
+       }
+
+       if (!strcmp(bridge->subclass, "parking")) {
+               handle_parking_bridge_enter_message(cdr, bridge, channel);
+       } else {
+               handle_standard_bridge_enter_message(cdr, bridge, channel);
+       }
+}
+
+/*!
+ * \brief Handler for when a channel is parked
+ * \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 about who got parked
+ * */
+static void handle_parked_call_message(void *data, struct stasis_subscription *sub,
+               struct stasis_message *message)
+{
+       struct ast_parked_call_payload *payload = stasis_message_data(message);
+       struct ast_channel_snapshot *channel = payload->parkee;
+       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+       RAII_VAR(struct module_config *, mod_cfg,
+                       ao2_global_obj_ref(module_configs), ao2_cleanup);
+       int unhandled = 1;
+       struct cdr_object *it_cdr;
+
+       /* Anything other than getting parked will be handled by other updates */
+       if (payload->event_type != PARKED_CALL) {
+               return;
+       }
+
+       /* No one got parked? */
+       if (!channel) {
+               return;
+       }
+
+       if (filter_channel_snapshot(channel)) {
+               return;
+       }
+
+       CDR_DEBUG(mod_cfg, "Parked Call message for channel %s: %u.%08u\n",
+                       channel->name,
+                       (unsigned int)stasis_message_timestamp(message)->tv_sec,
+                       (unsigned int)stasis_message_timestamp(message)->tv_usec);
+
+       cdr = ao2_find(active_cdrs_by_channel, channel->uniqueid, OBJ_KEY);
+       if (!cdr) {
+               ast_log(AST_LOG_WARNING, "No CDR for channel %s\n", channel->name);
+               ast_assert(0);
+               return;
+       }
+
+       ao2_lock(cdr);
+
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+               if (it_cdr->fn_table->process_parked_channel) {
+                       unhandled &= it_cdr->fn_table->process_parked_channel(it_cdr, payload);
+               }
+       }
+
+       if (unhandled) {
+               /* Nothing handled the messgae - we need a new one! */
+               struct cdr_object *new_cdr = cdr_object_create_and_append(cdr);
+               if (new_cdr) {
+                       /* As the new CDR is created in the single state, it is guaranteed
+                        * to have a function for the parked call message and will handle
+                        * the message */
+                       new_cdr->fn_table->process_parked_channel(new_cdr, payload);
+               }
        }
-       handle_bridge_pairings(handled_cdr, bridge);
 
-       ao2_link(active_cdrs_by_bridge, cdr);
        ao2_unlock(cdr);
+
+}
+
+/*!
+ * \brief Handler for a synchronization message
+ * \param data Passed on
+ * \param sub The stasis subscription for this message callback
+ * \param topic The topic this message was published for
+ * \param message A blank ao2 object
+ * */
+static void handle_cdr_sync_message(void *data, struct stasis_subscription *sub,
+               struct stasis_message *message)
+{
+       return;
 }
 
 struct ast_cdr_config *ast_cdr_get_config(void)
@@ -2424,9 +2618,12 @@ struct ast_cdr_config *ast_cdr_get_config(void)
 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);
+
+       cdr_toggle_runtime_options();
 }
 
 int ast_cdr_is_enabled(void)
@@ -2435,6 +2632,42 @@ int ast_cdr_is_enabled(void)
        return ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED);
 }
 
+int ast_cdr_backend_suspend(const char *name)
+{
+       int success = -1;
+       struct cdr_beitem *i = NULL;
+
+       AST_RWLIST_WRLOCK(&be_list);
+       AST_RWLIST_TRAVERSE(&be_list, i, list) {
+               if (!strcasecmp(name, i->name)) {
+                       ast_debug(3, "Suspending CDR backend %s\n", i->name);
+                       i->suspended = 1;
+                       success = 0;
+               }
+       }
+       AST_RWLIST_UNLOCK(&be_list);
+
+       return success;
+}
+
+int ast_cdr_backend_unsuspend(const char *name)
+{
+       int success = -1;
+       struct cdr_beitem *i = NULL;
+
+       AST_RWLIST_WRLOCK(&be_list);
+       AST_RWLIST_TRAVERSE(&be_list, i, list) {
+               if (!strcasecmp(name, i->name)) {
+                       ast_debug(3, "Unsuspending CDR backend %s\n", i->name);
+                       i->suspended = 0;
+                       success = 0;
+               }
+       }
+       AST_RWLIST_UNLOCK(&be_list);
+
+       return success;
+}
+
 int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
 {
        struct cdr_beitem *i = NULL;
@@ -2469,24 +2702,39 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
        return 0;
 }
 
-void ast_cdr_unregister(const char *name)
+int ast_cdr_unregister(const char *name)
 {
-       struct cdr_beitem *i = NULL;
+       struct cdr_beitem *match = NULL;
+       int active_count;
 
        AST_RWLIST_WRLOCK(&be_list);
-       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&be_list, i, list) {
-               if (!strcasecmp(name, i->name)) {
-                       AST_RWLIST_REMOVE_CURRENT(list);
+       AST_RWLIST_TRAVERSE(&be_list, match, list) {
+               if (!strcasecmp(name, match->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);
+       if (!match) {
+               AST_RWLIST_UNLOCK(&be_list);
+               return 0;
+       }
+
+       active_count = ao2_container_count(active_cdrs_by_channel);
+
+       if (!match->suspended && active_count != 0) {
+               AST_RWLIST_UNLOCK(&be_list);
+               ast_log(AST_LOG_WARNING, "Unable to unregister CDR backend %s; %d CDRs are still active\n",
+                       name, active_count);
+               return -1;
        }
+
+       AST_RWLIST_REMOVE(&be_list, match, list);
+       AST_RWLIST_UNLOCK(&be_list);
+
+       ast_verb(2, "Unregistered '%s' CDR backend\n", name);
+       ast_free(match);
+
+       return 0;
 }
 
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
@@ -2501,8 +2749,8 @@ struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
                return NULL;
        }
 
-       memcpy(newcdr, cdr, sizeof(*newcdr));
-       memset(&newcdr->varshead, 0, sizeof(newcdr->varshead));
+       *newcdr = *cdr;
+       AST_LIST_HEAD_INIT_NOLOCK(&newcdr->varshead);
        copy_variables(&newcdr->varshead, &cdr->varshead);
        newcdr->next = NULL;
 
@@ -2512,19 +2760,18 @@ struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
 static const char *cdr_format_var_internal(struct ast_cdr *cdr, const char *name)
 {
        struct ast_var_t *variables;
-       struct varshead *headp = &cdr->varshead;
 
        if (ast_strlen_zero(name)) {
                return NULL;
        }
 
-       AST_LIST_TRAVERSE(headp, variables, entries) {
+       AST_LIST_TRAVERSE(&cdr->varshead, variables, entries) {
                if (!strcasecmp(name, ast_var_name(variables))) {
                        return ast_var_value(variables);
                }
        }
 
-       return '\0';
+       return NULL;
 }
 
 static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufsize)
@@ -2532,6 +2779,7 @@ static void cdr_get_tv(struct timeval when, const char *fmt, char *buf, int bufs
        if (fmt == NULL) {      /* raw mode */
                snprintf(buf, bufsize, "%ld.%06ld", (long)when.tv_sec, (long)when.tv_usec);
        } else {
+               buf[0] = '\0';/* Ensure the buffer is initialized. */
                if (when.tv_sec) {
                        struct ast_tm tm;
 
@@ -2615,15 +2863,13 @@ void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char
 
 /*
  * \internal
- * \brief Callback that finds all CDRs that reference a particular channel
+ * \brief Callback that finds all CDRs that reference a particular channel by name
  */
-static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
+static int cdr_object_select_all_by_name_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;
@@ -2631,11 +2877,45 @@ static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
        return 0;
 }
 
+/*
+ * \internal
+ * \brief Callback that finds a CDR by channel name
+ */
+static int cdr_object_get_by_name_cb(void *obj, void *arg, int flags)
+{
+       struct cdr_object *cdr = obj;
+       const char *name = arg;
+
+       if (!strcasecmp(cdr->party_a.snapshot->name, name)) {
+               return CMP_MATCH;
+       }
+       return 0;
+}
+
 /* 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 };
+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
+};
 
 int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
 {
@@ -2652,30 +2932,30 @@ int ast_cdr_setvar(const char *channel_name, const char *name, const char *value
                }
        }
 
-       it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE | OBJ_KEY, cdr_object_select_all_by_channel_cb, arg);
+       it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE, cdr_object_select_all_by_name_cb, arg);
        if (!it_cdrs) {
                ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
                return -1;
        }
 
-       while ((cdr = ao2_iterator_next(it_cdrs))) {
+       for (; (cdr = ao2_iterator_next(it_cdrs)); ao2_unlock(cdr), ao2_cleanup(cdr)) {
                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 (!strcmp(channel_name, it_cdr->party_a.snapshot->name)) {
+                       if (!strcasecmp(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)) {
+                       } else if (it_cdr->party_b.snapshot
+                               && !strcasecmp(channel_name, it_cdr->party_b.snapshot->name)) {
                                headp = &it_cdr->party_b.variables;
                        }
                        if (headp) {
                                set_variable(headp, name, value);
                        }
                }
-               ao2_unlock(cdr);
-               ao2_ref(cdr, -1);
        }
        ao2_iterator_destroy(it_cdrs);
 
@@ -2738,7 +3018,7 @@ static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *na
        } 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);
+               snprintf(value, length, "%u", cdr_obj->disposition);
        } else if (!strcasecmp(name, "amaflags")) {
                snprintf(value, length, "%d", party_a->amaflags);
        } else if (!strcasecmp(name, "accountcode")) {
@@ -2756,7 +3036,7 @@ static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *na
        } 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);
+               snprintf(value, length, "%u", cdr_obj->sequence);
        } else {
                return 1;
        }
@@ -2764,11 +3044,28 @@ static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *na
        return 0;
 }
 
+/*! \internal
+ * \brief Look up and retrieve a CDR object by channel name
+ * \param name The name of the channel
+ * \retval NULL on error
+ * \retval The \ref cdr_object for the channel on success, with the reference
+ *     count bumped by one.
+ */
+static struct cdr_object *cdr_object_get_by_name(const char *name)
+{
+       char *param;
+
+       if (ast_strlen_zero(name)) {
+               return NULL;
+       }
+
+       param = ast_strdupa(name);
+       return ao2_callback(active_cdrs_by_channel, 0, cdr_object_get_by_name_cb, param);
+}
+
 int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
 {
-       RAII_VAR(struct cdr_object *, cdr,
-               ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
-               ao2_cleanup);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct cdr_object *cdr_obj;
 
        if (!cdr) {
@@ -2783,11 +3080,11 @@ int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size
        ao2_lock(cdr);
 
        cdr_obj = cdr->last;
-
        if (cdr_object_format_property(cdr_obj, name, value, length)) {
                /* Property failed; attempt variable */
                cdr_object_format_var_internal(cdr_obj, name, value, length);
        }
+
        ao2_unlock(cdr);
 
        return 0;
@@ -2795,9 +3092,7 @@ int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size
 
 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);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct cdr_object *it_cdr;
        struct ast_var_t *variable;
        const char *var;
@@ -2805,12 +3100,18 @@ int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf,
        int total = 0, x = 0, i;
 
        if (!workspace) {
-               return 1;
+               return 0;
        }
 
        if (!cdr) {
-               ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
-               return 1;
+               RAII_VAR(struct module_config *, mod_cfg,
+                        ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+               if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+                       ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
+               }
+
+               return 0;
        }
 
        ast_str_reset(*buf);
@@ -2834,9 +3135,11 @@ int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf,
                }
 
                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));
+                       if (cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace))) {
+                               /* Unhandled read-only CDR variable. */
+                               ast_assert(0);
+                               continue;
+                       }
 
                        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) {
@@ -2846,7 +3149,7 @@ int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf,
                        total++;
                }
        }
-
+       ao2_unlock(cdr);
        return total;
 }
 
@@ -2904,7 +3207,7 @@ static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flag
                        continue;
                }
                if (it_cdr->party_b.snapshot
-                       && !strcmp(it_cdr->party_b.snapshot->name, info->channel_name)) {
+                       && !strcasecmp(it_cdr->party_b.snapshot->name, info->channel_name)) {
                        strcpy(it_cdr->party_b.userfield, info->userfield);
                }
        }
@@ -2913,9 +3216,7 @@ static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, int flag
 
 void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
 {
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
-                       ao2_cleanup);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct party_b_userfield_update party_b_info = {
                        .channel_name = channel_name,
                        .userfield = userfield,
@@ -2951,6 +3252,7 @@ static void post_cdr(struct ast_cdr *cdr)
                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))) {
+                       ast_debug(1, "Skipping CDR  for %s since we weren't answered\n", cdr->channel);
                        continue;
                }
 
@@ -2959,7 +3261,9 @@ static void post_cdr(struct ast_cdr *cdr)
                }
                AST_RWLIST_RDLOCK(&be_list);
                AST_RWLIST_TRAVERSE(&be_list, i, list) {
-                       i->be(cdr);
+                       if (!i->suspended) {
+                               i->be(cdr);
+                       }
                }
                AST_RWLIST_UNLOCK(&be_list);
        }
@@ -2967,9 +3271,7 @@ static void post_cdr(struct ast_cdr *cdr)
 
 int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
 {
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
-                       ao2_cleanup);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct cdr_object *it_cdr;
 
        if (!cdr) {
@@ -2981,7 +3283,11 @@ int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
                if (it_cdr->fn_table == &finalized_state_fn_table) {
                        continue;
                }
+               /* Note: in general, set the flags on both the CDR record as well as the
+                * Party A. Sometimes all we have is the Party A to look at.
+                */
                ast_set_flag(&it_cdr->flags, option);
+               ast_set_flag(&it_cdr->party_a, option);
        }
        ao2_unlock(cdr);
 
@@ -2990,9 +3296,7 @@ int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
 
 int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
 {
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
-                       ao2_cleanup);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct cdr_object *it_cdr;
 
        if (!cdr) {
@@ -3011,11 +3315,9 @@ int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option
        return 0;
 }
 
-int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
+int ast_cdr_reset(const char *channel_name, int keep_variables)
 {
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
-                       ao2_cleanup);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct ast_var_t *vardata;
        struct cdr_object *it_cdr;
 
@@ -3026,7 +3328,7 @@ int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
        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)) {
+               if (!keep_variables) {
                        while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
                                ast_var_delete(vardata);
                        }
@@ -3051,9 +3353,7 @@ int ast_cdr_reset(const char *channel_name, struct ast_flags *options)
 
 int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
 {
-       RAII_VAR(struct cdr_object *, cdr,
-                       ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
-                       ao2_cleanup);
+       RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
        struct cdr_object *new_cdr;
        struct cdr_object *it_cdr;
        struct cdr_object *cdr_obj;
@@ -3064,12 +3364,12 @@ int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
 
        {
                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;
                }
 
@@ -3083,7 +3383,16 @@ int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
                }
                new_cdr->fn_table = cdr_obj->fn_table;
                ast_string_field_set(new_cdr, bridge, cdr->bridge);
+               ast_string_field_set(new_cdr, appl, cdr->appl);
+               ast_string_field_set(new_cdr, data, cdr->data);
+               ast_string_field_set(new_cdr, context, cdr->context);
+               ast_string_field_set(new_cdr, exten, cdr->exten);
                new_cdr->flags = cdr->flags;
+               /* Explicitly clear the AST_CDR_LOCK_APP flag - we want
+                * the application to be changed on the new CDR if the
+                * dialplan demands it
+                */
+               ast_clear_flag(&new_cdr->flags, AST_CDR_LOCK_APP);
 
                /* If there's a Party B, copy it over as well */
                if (cdr_obj->party_b.snapshot) {
@@ -3207,7 +3516,7 @@ static int submit_scheduled_batch(const void *data)
        /* manually reschedule from this point in time */
 
        ast_mutex_lock(&cdr_sched_lock);
-       cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.size * 1000, submit_scheduled_batch, NULL);
+       cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.time * 1000, submit_scheduled_batch, NULL);
        ast_mutex_unlock(&cdr_sched_lock);
        /* returning zero so the scheduler does not automatically reschedule */
        return 0;
@@ -3325,7 +3634,10 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        switch (cmd) {
        case CLI_INIT:
                e->command = "cdr set debug [on|off]";
-               e->usage = "Enable or disable extra debugging in the CDR Engine";
+               e->usage = "Enable or disable extra debugging in the CDR Engine. Note\n"
+                               "that this will dump debug information to the VERBOSE setting\n"
+                               "and should only be used when debugging information from the\n"
+                               "CDR engine is needed.\n";
                return NULL;
        case CLI_GENERATE:
                return NULL;
@@ -3335,10 +3647,12 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
                return CLI_SHOWUSAGE;
        }
 
-       if (!strcmp(a->argv[3], "on") && !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
+       if (!strcasecmp(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)) {
+       } else if (!strcasecmp(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");
        }
@@ -3346,6 +3660,181 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
        return CLI_SUCCESS;
 }
 
+/*! \brief Complete user input for 'cdr show' */
+static char *cli_complete_show(struct ast_cli_args *a)
+{
+       char *result = NULL;
+       int wordlen = strlen(a->word);
+       int which = 0;
+       struct ao2_iterator it_cdrs;
+       struct cdr_object *cdr;
+
+       it_cdrs = ao2_iterator_init(active_cdrs_by_channel, 0);
+       while ((cdr = ao2_iterator_next(&it_cdrs))) {
+               if (!strncasecmp(a->word, cdr->party_a.snapshot->name, wordlen) &&
+                       (++which > a->n)) {
+                       result = ast_strdup(cdr->party_a.snapshot->name);
+                       if (result) {
+                               ao2_ref(cdr, -1);
+                               break;
+                       }
+               }
+               ao2_ref(cdr, -1);
+       }
+       ao2_iterator_destroy(&it_cdrs);
+       return result;
+}
+
+static void cli_show_channels(struct ast_cli_args *a)
+{
+       struct ao2_iterator it_cdrs;
+       struct cdr_object *cdr;
+       char start_time_buffer[64];
+       char answer_time_buffer[64];
+       char end_time_buffer[64];
+
+#define TITLE_STRING "%-25.25s %-25.25s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
+#define FORMAT_STRING "%-25.25s %-25.25s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"
+
+       ast_cli(a->fd, "\n");
+       ast_cli(a->fd, "Channels with Call Detail Record (CDR) Information\n");
+       ast_cli(a->fd, "--------------------------------------------------\n");
+       ast_cli(a->fd, TITLE_STRING, "Channel", "Dst. Channel", "LastApp", "Start", "Answer", "End", "Billsec", "Duration");
+
+       it_cdrs = ao2_iterator_init(active_cdrs_by_channel, 0);
+       for (; (cdr = ao2_iterator_next(&it_cdrs)); ao2_cleanup(cdr)) {
+               struct cdr_object *it_cdr;
+               struct timeval start_time = { 0, };
+               struct timeval answer_time = { 0, };
+               struct timeval end_time = { 0, };
+
+               SCOPED_AO2LOCK(lock, cdr);
+
+               /* Calculate the start, end, answer, billsec, and duration over the
+                * life of all of the CDR entries
+                */
+               for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+                       if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
+                               continue;
+                       }
+                       if (ast_tvzero(start_time)) {
+                               start_time = it_cdr->start;
+                       }
+                       if (!ast_tvzero(it_cdr->answer) && ast_tvzero(answer_time)) {
+                               answer_time = it_cdr->answer;
+                       }
+               }
+
+               /* If there was no start time, then all CDRs were for a dialed channel; skip */
+               if (ast_tvzero(start_time)) {
+                       continue;
+               }
+               it_cdr = cdr->last;
+
+               end_time = ast_tvzero(it_cdr->end) ? ast_tvnow() : it_cdr->end;
+               cdr_get_tv(start_time, "%T", start_time_buffer, sizeof(start_time_buffer));
+               cdr_get_tv(answer_time, "%T", answer_time_buffer, sizeof(answer_time_buffer));
+               cdr_get_tv(end_time, "%T", end_time_buffer, sizeof(end_time_buffer));
+               ast_cli(a->fd, FORMAT_STRING, it_cdr->party_a.snapshot->name,
+                               it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<none>",
+                               it_cdr->appl,
+                               start_time_buffer,
+                               answer_time_buffer,
+                               end_time_buffer,
+                               ast_tvzero(answer_time) ? 0 : (long)ast_tvdiff_ms(end_time, answer_time) / 1000,
+                               (long)ast_tvdiff_ms(end_time, start_time) / 1000);
+       }
+       ao2_iterator_destroy(&it_cdrs);
+#undef FORMAT_STRING
+#undef TITLE_STRING
+}
+
+static void cli_show_channel(struct ast_cli_args *a)
+{
+       struct cdr_object *it_cdr;
+       char clid[64];
+       char start_time_buffer[64];
+       char answer_time_buffer[64];
+       char end_time_buffer[64];
+       const char *channel_name = a->argv[3];
+       RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+
+#define TITLE_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
+#define FORMAT_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"
+
+       cdr = cdr_object_get_by_name(channel_name);
+       if (!cdr) {
+               ast_cli(a->fd, "Unknown channel: %s\n", channel_name);
+               return;
+       }
+
+       ast_cli(a->fd, "\n");
+       ast_cli(a->fd, "Call Detail Record (CDR) Information for %s\n", channel_name);
+       ast_cli(a->fd, "--------------------------------------------------\n");
+       ast_cli(a->fd, TITLE_STRING, "AccountCode", "CallerID", "Dst. Channel", "LastApp", "Data", "Start", "Answer", "End", "Billsec", "Duration");
+
+       ao2_lock(cdr);
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+               struct timeval end;
+               if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
+                       continue;
+               }
+               ast_callerid_merge(clid, sizeof(clid), it_cdr->party_a.snapshot->caller_name, it_cdr->party_a.snapshot->caller_number, "");
+               if (ast_tvzero(it_cdr->end)) {
+                       end = ast_tvnow();
+               } else {
+                       end = it_cdr->end;
+               }
+               cdr_get_tv(it_cdr->start, "%T", start_time_buffer, sizeof(start_time_buffer));
+               cdr_get_tv(it_cdr->answer, "%T", answer_time_buffer, sizeof(answer_time_buffer));
+               cdr_get_tv(end, "%T", end_time_buffer, sizeof(end_time_buffer));
+               ast_cli(a->fd, FORMAT_STRING,
+                               it_cdr->party_a.snapshot->accountcode,
+                               clid,
+                               it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<none>",
+                               it_cdr->appl,
+                               it_cdr->data,
+                               start_time_buffer,
+                               answer_time_buffer,
+                               end_time_buffer,
+                               (long)ast_tvdiff_ms(end, it_cdr->answer) / 1000,
+                               (long)ast_tvdiff_ms(end, it_cdr->start) / 1000);
+       }
+       ao2_unlock(cdr);
+#undef FORMAT_STRING
+#undef TITLE_STRING
+}
+
+static char *handle_cli_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+                       e->command = "cdr show active";
+                       e->usage =
+                               "Usage: cdr show active [channel]\n"
+                               "       Displays a summary of all Call Detail Records when [channel]\n"
+                               "       is omitted; displays all of the Call Detail Records\n"
+                               "       currently in flight for a given [channel] when [channel] is\n"
+                               "       specified.\n\n"
+                               "       Note that this will not display Call Detail Records that\n"
+                               "       have already been dispatched to a backend storage, nor for\n"
+                               "       channels that are no longer active.\n";
+                       return NULL;
+       case CLI_GENERATE:
+               return cli_complete_show(a);
+       }
+
+       if (a->argc > 4) {
+               return CLI_SHOWUSAGE;
+       } else if (a->argc < 4) {
+               cli_show_channels(a);
+       } else {
+               cli_show_channel(a);
+       }
+
+       return CLI_SUCCESS;
+}
+
 static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct cdr_beitem *beitem = NULL;
@@ -3364,8 +3853,9 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
                return NULL;
        }
 
-       if (a->argc > 3)
+       if (a->argc > 3) {
                return CLI_SHOWUSAGE;
+       }
 
        ast_cli(a->fd, "\n");
        ast_cli(a->fd, "Call Detail Record (CDR) settings\n");
@@ -3385,8 +3875,8 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
                        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", 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, "  Maximum batch size:         %u record%s\n", mod_cfg->general->batch_settings.size, ESS(mod_cfg->general->batch_settings.size));
+                       ast_cli(a->fd, "  Maximum batch time:         %u 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");
@@ -3396,7 +3886,7 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
                        ast_cli(a->fd, "    (none)\n");
                } else {
                        AST_RWLIST_TRAVERSE(&be_list, beitem, list) {
-                               ast_cli(a->fd, "    %s\n", beitem->name);
+                               ast_cli(a->fd, "    %s%s\n", beitem->name, beitem->suspended ? " (suspended) " : "");
                        }
                }
                AST_RWLIST_UNLOCK(&be_list);
@@ -3408,18 +3898,32 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
 
 static char *handle_cli_submit(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 submit";
                e->usage =
                        "Usage: cdr submit\n"
-                       "       Posts all pending batched CDR data to the configured CDR backend engine modules.\n";
+                       "Posts all pending batched CDR data to the configured CDR\n"
+                       "backend engine modules.\n";
                return NULL;
        case CLI_GENERATE:
                return NULL;
        }
-       if (a->argc > 2)
+       if (a->argc > 2) {
                return CLI_SHOWUSAGE;
+       }
+
+       if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+               ast_cli(a->fd, "Cannot submit CDR batch: CDR engine disabled.\n");
+               return CLI_SUCCESS;
+       }
+
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+               ast_cli(a->fd, "Cannot submit CDR batch: batch mode not enabled.\n");
+               return CLI_SUCCESS;
+       }
 
        submit_unscheduled_batch();
        ast_cli(a->fd, "Submitted CDRs to backend engines for processing.  This may take a while.\n");
@@ -3427,10 +3931,12 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_
        return CLI_SUCCESS;
 }
 
-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 struct ast_cli_entry cli_commands[] = {
+       AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data"),
+       AST_CLI_DEFINE(handle_cli_status, "Display the CDR status"),
+       AST_CLI_DEFINE(handle_cli_show, "Display active CDRs for channels"),
+       AST_CLI_DEFINE(handle_cli_debug, "Enable debugging in the CDR engine"),
+};
 
 /*!
  * \brief This dispatches *all* \ref cdr_objects. It should only be used during
@@ -3441,10 +3947,12 @@ static int cdr_object_dispatch_all_cb(void *obj, void *arg, int flags)
        struct cdr_object *cdr = obj;
        struct cdr_object *it_cdr;
 
+       ao2_lock(cdr);
        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);
+       ao2_unlock(cdr);
 
        return 0;
 }
@@ -3460,10 +3968,58 @@ static void finalize_batch_mode(void)
        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();
 }
 
+struct stasis_message_router *ast_cdr_message_router(void)
+{
+       if (!stasis_router) {
+               return NULL;
+       }
+
+       ao2_bump(stasis_router);
+       return stasis_router;
+}
+
+/*!
+ * \brief Destroy the active Stasis subscriptions
+ */
+static void destroy_subscriptions(void)
+{
+       channel_subscription = stasis_forward_cancel(channel_subscription);
+       bridge_subscription = stasis_forward_cancel(bridge_subscription);
+       parking_subscription = stasis_forward_cancel(parking_subscription);
+}
+
+/*!
+ * \brief Create the Stasis subcriptions for CDRs
+ */
+static int create_subscriptions(void)
+{
+       if (!cdr_topic) {
+               return -1;
+       }
+
+       if (channel_subscription || bridge_subscription || parking_subscription) {
+               return 0;
+       }
+
+       channel_subscription = stasis_forward_all(ast_channel_topic_all_cached(), cdr_topic);
+       if (!channel_subscription) {
+               return -1;
+       }
+       bridge_subscription = stasis_forward_all(ast_bridge_topic_all_cached(), cdr_topic);
+       if (!bridge_subscription) {
+               return -1;
+       }
+       parking_subscription = stasis_forward_all(ast_parking_topic(), cdr_topic);
+       if (!parking_subscription) {
+               return -1;
+       }
+
+       return 0;
+}
+
 static int process_config(int reload)
 {
        RAII_VAR(struct module_config *, mod_cfg, module_config_alloc(), ao2_cleanup);
@@ -3495,33 +4051,45 @@ static int process_config(int reload)
                 */
                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);
+                       ao2_global_obj_replace_unref(module_configs, mod_cfg);
                        return 0;
                }
                return 1;
        }
 
-       if (reload) {
-               manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: CDR\r\nMessage: CDR subsystem reload requested\r\n");
-       }
        return 0;
 }
 
+static void cdr_engine_cleanup(void)
+{
+       destroy_subscriptions();
+}
+
 static void cdr_engine_shutdown(void)
 {
+       stasis_message_router_unsubscribe_and_join(stasis_router);
+       stasis_router = NULL;
+
+       ao2_cleanup(cdr_topic);
+       cdr_topic = NULL;
+
+       STASIS_MESSAGE_TYPE_CLEANUP(cdr_sync_message_type);
+
        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_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
        ast_sched_context_destroy(sched);
        sched = NULL;
        ast_free(batch);
        batch = NULL;
 
+       aco_info_destroy(&cfg_info);
+       ao2_global_obj_release(module_configs);
+
+       ao2_container_unregister("cdrs_by_channel");
        ao2_ref(active_cdrs_by_channel, -1);
-       ao2_ref(active_cdrs_by_bridge, -1);
+       active_cdrs_by_channel = NULL;
 }
 
 static void cdr_enable_batch_mode(struct ast_cdr_config *config)
@@ -3535,32 +4103,69 @@ static void cdr_enable_batch_mode(struct ast_cdr_config *config)
                        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",
+       ast_log(LOG_NOTICE, "CDR batch mode logging enabled, first of either size %u or time %u seconds.\n",
                        config->batch_settings.size, config->batch_settings.time);
 }
 
-int ast_cdr_engine_init(void)
+/*!
+ * \internal
+ * \brief Print channel object key (name).
+ * \since 12.0.0
+ *
+ * \param v_obj A pointer to the object we want the key printed.
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ *
+ * \return Nothing
+ */
+static void cdr_container_print_fn(void *v_obj, void *where, ao2_prnt_fn *prnt)
 {
-       RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
-
-       if (process_config(0)) {
-               return -1;
+       struct cdr_object *cdr = v_obj;
+       struct cdr_object *it_cdr;
+       if (!cdr) {
+               return;
+       }
+       for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+               prnt(where, "Party A: %s; Party B: %s; Bridge %s\n", it_cdr->party_a.snapshot->name, it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<unknown>",
+                               it_cdr->bridge);
        }
+}
 
-       /* 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;
+/*!
+ * \brief Checks if CDRs are enabled and enables/disables the necessary options
+ */
+static int cdr_toggle_runtime_options(void)
+{
+       RAII_VAR(struct module_config *, mod_cfg,
+               ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
+               if (create_subscriptions()) {
+                       destroy_subscriptions();
+                       ast_log(AST_LOG_ERROR, "Failed to create Stasis subscriptions\n");
+                       return -1;
+               }
+               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 {
+               destroy_subscriptions();
+               ast_log(LOG_NOTICE, "CDR logging disabled.\n");
        }
 
-       active_cdrs_by_bridge = ao2_container_alloc(51, cdr_object_bridge_hash_fn, cdr_object_bridge_cmp_fn);
-       if (!active_cdrs_by_bridge) {
+       return 0;
+}
+
+int ast_cdr_engine_init(void)
+{
+       if (process_config(0)) {
                return -1;
        }
 
@@ -3569,22 +4174,28 @@ int ast_cdr_engine_init(void)
                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);
+
+       if (STASIS_MESSAGE_TYPE_INIT(cdr_sync_message_type)) {
+               return -1;
+       }
+
+       stasis_message_router_add_cache_update(stasis_router, ast_channel_snapshot_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);
+       stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL);
+       stasis_message_router_add(stasis_router, cdr_sync_message_type(), handle_cdr_sync_message, NULL);
+
+       active_cdrs_by_channel = ao2_container_alloc(NUM_CDR_BUCKETS,
+               cdr_object_channel_hash_fn, cdr_object_channel_cmp_fn);
+       if (!active_cdrs_by_channel) {
+               return -1;
+       }
+       ao2_container_register("cdrs_by_channel", active_cdrs_by_channel, cdr_container_print_fn);
 
        sched = ast_sched_context_create();
        if (!sched) {
@@ -3592,28 +4203,18 @@ int ast_cdr_engine_init(void)
                return -1;
        }
 
-       ast_cli_register(&cli_status);
-       ast_cli_register(&cli_debug);
+       ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+       ast_register_cleanup(cdr_engine_cleanup);
        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;
+       return cdr_toggle_runtime_options();
 }
 
 void ast_cdr_engine_term(void)
 {
        RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+       RAII_VAR(void *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
 
        /* Since this is called explicitly during process shutdown, we might not have ever
         * been initialized. If so, the config object will be NULL.
@@ -3621,10 +4222,25 @@ void ast_cdr_engine_term(void)
        if (!mod_cfg) {
                return;
        }
-       if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
-               return;
+
+       if (cdr_sync_message_type()) {
+               /* Make sure we have the needed items */
+               payload = ao2_alloc(sizeof(*payload), NULL);
+               if (!stasis_router || !payload) {
+                       return;
+               }
+
+               ast_debug(1, "CDR Engine termination request received; waiting on messages...\n");
+
+               message = stasis_message_create(cdr_sync_message_type(), payload);
+               if (message) {
+                       stasis_message_router_publish_sync(stasis_router, message);
+               }
+       }
+
+       if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
+               cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
        }
-       cdr_submit_batch(ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SAFE_SHUTDOWN));
 }
 
 int ast_cdr_engine_reload(void)
@@ -3646,17 +4262,7 @@ int ast_cdr_engine_reload(void)
                }
        }
 
-       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);
-               }
-       } else {
-               ast_log(LOG_NOTICE, "CDR logging disabled, data will be lost.\n");
-       }
-
-       return 0;
+       return cdr_toggle_runtime_options();
 }