diff --git a/main/cdr.c b/main/cdr.c
index 80f0a58c3aa533063492d3d365d9faed6fdb5766..9180c739d94609bb228022dc2dfa7b55dc8a474b 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -327,7 +327,7 @@ 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 Message router for stasis messages regarding channel state */
@@ -682,6 +682,7 @@ 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 */
@@ -772,42 +773,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);
-}
+	const struct cdr_object *cdr;
+	const char *key;
 
-/*! \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);
+	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 *it_cdr;
-	const char *match = arg;
-
-	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;
 }
 
 /*!
@@ -854,6 +871,7 @@ static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
 		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;
@@ -985,7 +1003,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;
@@ -1062,12 +1080,15 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
 	struct ast_var_t *it_var, *it_copy_var;
 	struct ast_channel_snapshot *party_a;
 	struct ast_channel_snapshot *party_b;
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 
 	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(it_cdr->party_a.snapshot)) {
+		if (snapshot_is_dialed(it_cdr->party_a.snapshot) && !it_cdr->party_b.snapshot) {
+			CDR_DEBUG(mod_cfg, "%p - %s is dialed and has no Party B; discarding\n", it_cdr,
+				it_cdr->party_a.snapshot->name);
 			continue;
 		}
 
@@ -1437,6 +1458,7 @@ 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 */
@@ -1447,6 +1469,8 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
 	/* Try the candidate CDR's Party A first */
 	party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
 	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
@@ -1465,6 +1489,8 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
 	}
 	party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
 	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;
 	}
@@ -1474,27 +1500,31 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
 
 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);
+	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_channel, OBJ_MULTIPLE,
-			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 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;
-		RAII_VAR(struct cdr_object *, cdr_cleanup, cand_cdr_master, ao2_cleanup);
-		SCOPED_AO2LOCK(lock, cand_cdr_master);
 
+		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.
 			 * I'm not sure how that would happen, but it pays to be careful. */
@@ -1510,8 +1540,9 @@ static enum process_bridge_enter_results single_state_process_bridge_enter(struc
 			success = 1;
 			break;
 		}
+		ao2_unlock(cand_cdr_master);
 	}
-	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);
@@ -1620,27 +1651,32 @@ static int dial_state_process_dial_end(struct cdr_object *cdr, struct ast_channe
 
 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;
+	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_channel, OBJ_MULTIPLE,
-			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 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;
-		RAII_VAR(struct cdr_object *, cdr_cleanup, cand_cdr_master, ao2_cleanup);
-		SCOPED_AO2LOCK(lock, cand_cdr_master);
 
+		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.
 			 * I'm not sure how that would happen, but it pays to be careful. */
@@ -1669,8 +1705,9 @@ static enum process_bridge_enter_results dial_state_process_bridge_enter(struct
 			success = 1;
 			break;
 		}
+		ao2_unlock(cand_cdr_master);
 	}
-	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);
@@ -1829,9 +1866,9 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
 
 	/* Figure out who is running this show */
 	if (caller) {
-		cdr = ao2_find(active_cdrs_by_channel, caller->name, OBJ_KEY);
+		cdr = ao2_find(active_cdrs_by_channel, caller->uniqueid, OBJ_KEY);
 	} else {
-		cdr = ao2_find(active_cdrs_by_channel, peer->name, OBJ_KEY);
+		cdr = ao2_find(active_cdrs_by_channel, peer->uniqueid, OBJ_KEY);
 	}
 
 	if (!cdr) {
@@ -1986,6 +2023,7 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
 	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;
 
@@ -1994,17 +2032,13 @@ static void handle_channel_cache_message(void *data, struct stasis_subscription
 
 	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) {
@@ -2015,7 +2049,7 @@ 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);
@@ -2027,7 +2061,6 @@ 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)) {
@@ -2121,7 +2154,7 @@ 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 bridge_leave_data leave_data = {
@@ -2171,163 +2204,6 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
 	}
 }
 
-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);
-		}
-	}
-}
-
-/*!
- * \brief Create new \ref bridge_candidate objects for each party currently
- * in a bridge
- * \param bridge The \param ast_bridge_snapshot for the bridge we're processing
- *
- * Note that we use two passes here instead of one so that we only create a
- * candidate for a party B if they are never a party A in the bridge. Otherwise,
- * we don't care about them.
- */
-static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snapshot *bridge)
-{
-	struct ao2_container *candidates = ao2_container_alloc(61, 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_channel, OBJ_MULTIPLE,
-			cdr_object_bridge_cmp_fn, bridge_id);
-	if (!it_cdrs) {
-		/* No one in the bridge yet! */
-		ao2_cleanup(candidates);
-		return NULL;
-	}
-	for (; (cand_cdr_master = ao2_iterator_next(it_cdrs)); ao2_cleanup(cand_cdr_master)) {
-		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_channel, OBJ_MULTIPLE,
-			cdr_object_bridge_cmp_fn, bridge_id);
-	if (!it_cdrs) {
-		/* Now it's just an error. */
-		ao2_cleanup(candidates);
-		return NULL;
-	}
-	for (; (cand_cdr_master = ao2_iterator_next(it_cdrs)); ao2_cleanup(cand_cdr_master)) {
-		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
@@ -2335,9 +2211,10 @@ static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snap
  * \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);
@@ -2348,76 +2225,70 @@ static void bridge_candidate_add_to_cdr(struct cdr_object *cdr,
 	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 (!strcasecmp(cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)
-		|| (cdr->party_b.snapshot
-			&& !strcasecmp(cdr->party_b.snapshot->name, bcand->candidate.snapshot->name))) {
-		return 0;
-	}
-	party_a = cdr_object_pick_party_a(&cdr->party_a, &bcand->candidate);
-	/* We're party A - make a new CDR, append it to us, and set the candidate as
-	 * Party B */
-	if (!strcasecmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
-		bridge_candidate_add_to_cdr(cdr, cdr->bridge, &bcand->candidate);
-		return 0;
-	}
+	SCOPED_AO2LOCK(lock, base_cand_cdr);
+
+	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;
+		}
+
+		/* 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;
+		}
 
-	/* 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 (!strcasecmp(bcand->cdr->party_a.snapshot->name, bcand->candidate.snapshot->name)) {
-		if (bcand->cdr->party_b.snapshot
-			&& strcasecmp(bcand->cdr->party_b.snapshot->name, cdr->party_a.snapshot->name)) {
-			bridge_candidate_add_to_cdr(bcand->cdr, cdr->bridge, &cdr->party_a);
+		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));
-		}
-	} 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);
+			memset(&cand_cdr->end, 0, sizeof(cand_cdr->end));
 		}
-		ao2_ref(b_party, -1);
 	}
-
 	return 0;
 }
 
@@ -2429,14 +2300,25 @@ 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;
+		}
+
+		bridge_candidate_process(cdr, cand_cdr);
+
+		ao2_ref(channel_id, -1);
 	}
-	ao2_callback(candidates, OBJ_NODATA, bridge_candidate_process, cdr);
+	ao2_iterator_destroy(&it_channels);
 }
 
 /*! \brief Handle entering into a parking bridge
@@ -2556,6 +2438,7 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
 }
 
 /*!
+ * \internal
  * \brief Handler for Stasis-Core bridge enter messages
  * \param data Passed on
  * \param sub The stasis subscription for this message callback
@@ -2569,7 +2452,7 @@ static void handle_bridge_enter_message(void *data, struct stasis_subscription *
 	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_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);
@@ -2631,7 +2514,7 @@ static void handle_parked_call_message(void *data, struct stasis_subscription *s
 			(unsigned int)stasis_message_timestamp(message)->tv_sec,
 			(unsigned int)stasis_message_timestamp(message)->tv_usec);
 
-	cdr = ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY);
+	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);
 		return;
@@ -2850,9 +2733,9 @@ 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;
@@ -2864,6 +2747,21 @@ 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",
@@ -2904,7 +2802,7 @@ int ast_cdr_setvar(const char *channel_name, const char *name, const char *value
 		}
 	}
 
-	it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE, 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;
@@ -3016,11 +2914,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) {
@@ -3047,9 +2962,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;
@@ -3167,9 +3080,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,
@@ -3222,9 +3133,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) {
@@ -3249,9 +3158,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) {
@@ -3272,9 +3179,7 @@ int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option
 
 int ast_cdr_reset(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 ast_var_t *vardata;
 	struct cdr_object *it_cdr;
 
@@ -3310,9 +3215,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;
diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c
index 0ff9ccaab9da340f985fe8d36f467c17e476c5f6..7d078f9d0aad16b32f27e9b11f766bd43413333c 100644
--- a/main/stasis_bridges.c
+++ b/main/stasis_bridges.c
@@ -40,7 +40,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/bridge.h"
 #include "asterisk/bridge_technology.h"
 
-#define SNAPSHOT_CHANNELS_BUCKETS 13
+/* The container of channel snapshots in a bridge snapshot should always be
+   equivalent to a linked list; otherwise things (like CDRs) that depend on some
+   consistency in the ordering of channels in a bridge will break. */
+#define SNAPSHOT_CHANNELS_BUCKETS 1
 
 /*** DOCUMENTATION
 	<managerEvent language="en_US" name="BlindTransfer">
diff --git a/tests/test_cdr.c b/tests/test_cdr.c
index f1e577e76ec87ea8d2cb1fdb6aa1f1cc54c8a808..57d5b2e32dfb0a8cbd7dbc8dfdbf57ab73e53b4b 100644
--- a/tests/test_cdr.c
+++ b/tests/test_cdr.c
@@ -312,7 +312,8 @@ static enum ast_test_result_state verify_mock_cdr_record(struct ast_test *test,
 			ast_test_status_update(test, "CDRs recorded where no record expected\n");
 			return AST_TEST_FAIL;
 		}
-
+		ast_test_debug(test, "Verifying expected record %s, %s\n",
+			expected->channel, S_OR(expected->dstchannel, "<none>"));
 		VERIFY_STRING_FIELD(accountcode, actual, expected);
 		VERIFY_NUMERIC_FIELD(amaflags, actual, expected);
 		VERIFY_STRING_FIELD(channel, actual, expected);
@@ -1802,21 +1803,37 @@ AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
 		.peeraccount = "400",
 		.next = &charlie_expected_two,
 	};
+	struct ast_cdr bob_expected_one = {
+		.clid = "\"Bob\" <200>",
+		.src = "200",
+		.dst = "200",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Bob",
+		.dstchannel = CHANNEL_TECH_NAME "/David",
+		.lastapp = "AppDial",
+		.lastdata = "(Outgoing Line)",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.billsec = 1,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "200",
+		.peeraccount = "400",
+		.next = &charlie_expected_one,
+	};
 	struct ast_cdr alice_expected_three = {
 		.clid = "\"Alice\" <100>",
 		.src = "100",
 		.dst = "100",
 		.dcontext = "default",
 		.channel = CHANNEL_TECH_NAME "/Alice",
-		.dstchannel = CHANNEL_TECH_NAME "/Charlie",
+		.dstchannel = CHANNEL_TECH_NAME "/David",
 		.lastapp = "Dial",
 		.lastdata = CHANNEL_TECH_NAME "/Bob",
 		.amaflags = AST_AMA_DOCUMENTATION,
 		.billsec = 1,
 		.disposition = AST_CDR_ANSWERED,
 		.accountcode = "100",
-		.peeraccount = "300",
-		.next = &charlie_expected_one,
+		.peeraccount = "400",
+		.next = &bob_expected_one,
 	};
 	struct ast_cdr alice_expected_two = {
 		.clid = "\"Alice\" <100>",
@@ -1824,14 +1841,14 @@ AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
 		.dst = "100",
 		.dcontext = "default",
 		.channel = CHANNEL_TECH_NAME "/Alice",
-		.dstchannel = CHANNEL_TECH_NAME "/David",
+		.dstchannel = CHANNEL_TECH_NAME "/Charlie",
 		.lastapp = "Dial",
 		.lastdata = CHANNEL_TECH_NAME "/Bob",
 		.amaflags = AST_AMA_DOCUMENTATION,
 		.billsec = 1,
 		.disposition = AST_CDR_ANSWERED,
 		.accountcode = "100",
-		.peeraccount = "400",
+		.peeraccount = "300",
 		.next = &alice_expected_three,
 	};
 	struct ast_cdr alice_expected_one = {
@@ -1874,6 +1891,8 @@ AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
 	chan_bob = ast_channel_alloc(0, AST_STATE_DOWN, "200", "Bob", "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob");
 	ast_set_flag(ast_channel_flags(chan_bob), AST_FLAG_OUTGOING);
 	EMULATE_APP_DATA(chan_bob, 0, "AppDial", "(Outgoing Line)");
+	ast_copy_string(bob_expected_one.uniqueid, ast_channel_uniqueid(chan_bob), sizeof(bob_expected_one.uniqueid));
+	ast_copy_string(bob_expected_one.linkedid, ast_channel_linkedid(chan_alice), sizeof(bob_expected_one.linkedid));
 
 	CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller, &charlie_expected_one);
 	EMULATE_APP_DATA(chan_charlie, 1, "Dial", CHANNEL_TECH_NAME "/David");
@@ -1920,7 +1939,7 @@ AST_TEST_DEFINE(test_cdr_dial_answer_multiparty)
 	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL);
 	HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL);
 
-	result = verify_mock_cdr_record(test, &alice_expected_one, 5);
+	result = verify_mock_cdr_record(test, &alice_expected_one, 6);
 
 	return result;
 }