[svn-commits] mjordan: branch mjordan/cdrs-of-doom r386588 - in /team/mjordan/cdrs-of-doom:...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Thu Apr 25 16:55:35 CDT 2013


Author: mjordan
Date: Thu Apr 25 16:55:31 2013
New Revision: 386588

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=386588
Log:
Commit initial bridging support in CDRs

This is only the beginning, but it's better to get it in before
I change my mind, delete it all, and start over again.

Modified:
    team/mjordan/cdrs-of-doom/include/asterisk/cdr.h
    team/mjordan/cdrs-of-doom/main/bridging.c
    team/mjordan/cdrs-of-doom/main/cdr.c
    team/mjordan/cdrs-of-doom/tests/test_cdr.c

Modified: team/mjordan/cdrs-of-doom/include/asterisk/cdr.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/include/asterisk/cdr.h?view=diff&rev=386588&r1=386587&r2=386588
==============================================================================
--- team/mjordan/cdrs-of-doom/include/asterisk/cdr.h (original)
+++ team/mjordan/cdrs-of-doom/include/asterisk/cdr.h Thu Apr 25 16:55:31 2013
@@ -40,7 +40,7 @@
 	CDR_CONGESTION = 1 << 3,			/*< Treat congestion as if it were a failed call */
 	CDR_END_BEFORE_H_EXTEN = 1 << 4,	/*< End the CDR before the 'h' extension runs */
 	CDR_INITIATED_SECONDS = 1 << 5,		/*< Include microseconds into the billing time */
-	CDR_DEBUG = 1 << 6,
+	CDR_DEBUG = 1 << 6,					/*< Enables extra debug statements */
 };
 
 /*! \brief CDR Batch Mode settings */
@@ -61,6 +61,7 @@
 	AST_CDR_FLAG_FINALIZE = (1 << 4),			/*< Finalize the current CDRs */
 	AST_CDR_FLAG_SET_ANSWER = (1 << 5),			/*< If the channel is answered, set the answer time to now */
 	AST_CDR_FLAG_RESET = (1 << 6),				/*< If set, set the start and answer time to now */
+	AST_CDR_FLAG_LAST = AST_CDR_FLAG_RESET,		/*< Marker. Denotes the last flag in the set. */
 };
 
 /*!

Modified: team/mjordan/cdrs-of-doom/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/bridging.c?view=diff&rev=386588&r1=386587&r2=386588
==============================================================================
--- team/mjordan/cdrs-of-doom/main/bridging.c (original)
+++ team/mjordan/cdrs-of-doom/main/bridging.c Thu Apr 25 16:55:31 2013
@@ -513,6 +513,10 @@
 			bridge->technology->leave(bridge, bridge_channel);
 		}
 	}
+
+	/* BUGBUG If this was an outbound channel and it is not hung up, clear the
+	 * outbound flag
+	 */
 
 	/* Remove channel from the bridge */
 	if (!bridge_channel->suspended) {

Modified: team/mjordan/cdrs-of-doom/main/cdr.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/cdr.c?view=diff&rev=386588&r1=386587&r2=386588
==============================================================================
--- team/mjordan/cdrs-of-doom/main/cdr.c (original)
+++ team/mjordan/cdrs-of-doom/main/cdr.c Thu Apr 25 16:55:31 2013
@@ -66,6 +66,7 @@
 #include "asterisk/json.h"
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
 #include "asterisk/stasis_message_router.h"
 #include "asterisk/astobj2.h"
 
@@ -197,6 +198,10 @@
 		ast_verb(1, (fmt), ##__VA_ARGS__); \
 	} } while (0)
 
+enum cdr_flag_internal {
+	INTERNAL_CDR_FLAG_SKIP = AST_CDR_FLAG_LAST,	/*< Set when we need to temporarily skip a CDR for some reason */
+	INTERNAL_CDR_FLAG_FORKED = (INTERNAL_CDR_FLAG_SKIP << 1),
+};
 
 /*! \brief The configuration settings for this module */
 struct module_config {
@@ -296,11 +301,17 @@
 static ast_cond_t cdr_pending_cond;
 
 
-
-
+/*! \brief A container of the active CDRs indexed by Party A channel name */
 static struct ao2_container *active_cdrs_by_channel;
 
-static struct stasis_message_router *stasis_router;
+/*! \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_channel_router;
+
+/*! \brief Message router for stasis messages regarding bridge state */
+static struct stasis_message_router *stasis_bridge_router;
 
 struct cdr_object;
 
@@ -310,14 +321,18 @@
 	void (* const process_party_a_update)(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 	void (* const process_party_b_update)(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 	int (* const process_dial_message)(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
+	int (* const process_bridge_enter)(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
+	int (* const process_bridge_leave)(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 };
 AST_MUTEX_DEFINE_STATIC(cdr_sched_lock);
 
 static void base_process_party_a_update(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static int base_process_bridge_leave_message(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 static void single_state_init_function(struct cdr_object *cdr);
 static void single_state_process_party_b_update(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
 static int single_state_process_dial_message(struct cdr_object *cdr, struct ast_channel_snapshot *caller, struct ast_channel_snapshot *peer, const char *dial_status);
+static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel);
 
 struct cdr_object_fn_table single_state_fn_table = {
 	.name = "Single",
@@ -325,6 +340,8 @@
 	.process_party_a_update = base_process_party_a_update,
 	.process_party_b_update = single_state_process_party_b_update,
 	.process_dial_message = single_state_process_dial_message,
+	.process_bridge_enter = single_state_process_bridge_enter,
+	.process_bridge_leave = base_process_bridge_leave_message,
 };
 
 static void dial_state_process_party_b_update(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
@@ -336,6 +353,19 @@
 	.process_party_a_update = base_process_party_a_update,
 	.process_party_b_update = dial_state_process_party_b_update,
 	.process_dial_message = dial_state_process_dial_message,
+	.process_bridge_leave = base_process_bridge_leave_message,
+};
+
+static void bridge_state_init_function(struct cdr_object *cdr);
+
+struct cdr_object_fn_table bridge_state_fn_table = {
+	.name = "Bridged",
+	.init_function = bridge_state_init_function,
+	.process_party_a_update = base_process_party_a_update,
+	.process_party_b_update = NULL,
+	.process_dial_message = NULL,
+	.process_bridge_enter = NULL,
+	.process_bridge_leave = NULL,
 };
 
 static void finalized_state_init_function(struct cdr_object *cdr);
@@ -347,14 +377,8 @@
 	.process_party_a_update = finalized_state_process_party_a_update,
 	.process_party_b_update = NULL,
 	.process_dial_message = NULL,
-};
-
-struct cdr_object_fn_table hangup_state_fn_table = {
-	.name = "Hangup",
-	.init_function = NULL,
-	.process_party_a_update = NULL,
-	.process_party_b_update = NULL,
-	.process_dial_message = NULL,
+	.process_bridge_enter = NULL,
+	.process_bridge_leave = NULL,
 };
 
 struct cdr_object_snapshot {
@@ -381,83 +405,9 @@
 		AST_STRING_FIELD(bridge);		/*< The bridge the party A happens to be in. */
 	);
 	struct cdr_object *next;			/*< The next CDR object in the chain */
+	struct cdr_object *prev;			/*< The previous CDR object in the chain */
 	struct cdr_object *last;			/*< The last CDR object in the chain */
 };
-
-
-
-/* NON-VIRTUAL FUNCTIONS */
-
-static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table);
-
-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);
-}
-
-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);
-}
-
-static void cdr_object_dtor(void *obj)
-{
-	struct cdr_object *cdr = obj;
-	struct ast_var_t *it_var;
-
-	if (!cdr) {
-		return;
-	}
-
-	ao2_cleanup(cdr->party_a.snapshot);
-	ao2_cleanup(cdr->party_b.snapshot);
-	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a.variables, entries))) {
-		ast_var_delete(it_var);
-	}
-	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_b.variables, entries))) {
-		ast_var_delete(it_var);
-	}
-	ast_string_field_free_memory(cdr);
-
-	if (cdr->next) {
-		ao2_cleanup(cdr->next);
-	}
-}
-
-static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
-{
-	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
-	struct cdr_object *cdr;
-
-	ast_assert(chan != NULL);
-
-	cdr = ao2_alloc(sizeof(*cdr), cdr_object_dtor);
-	if (!cdr) {
-		return NULL;
-	}
-	cdr->last = cdr;
-	if (ast_string_field_init(cdr, 64)) {
-		return NULL;
-	}
-	ast_string_field_set(cdr, name, chan->name);
-	ast_string_field_set(cdr, linkedid, chan->linkedid);
-	cdr->disposition = AST_CDR_NULL;
-	cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1);
-
-	cdr->party_a.snapshot = chan;
-	ao2_ref(cdr->party_a.snapshot, +1);
-
-	CDR_DEBUG(mod_cfg, "%p - created CDR for channel %s\n", cdr, chan->name);
-
-	cdr_object_transition_state(cdr, &single_state_fn_table);
-
-	return cdr;
-}
 
 static int copy_vars(struct varshead *to_list, struct varshead *from_list)
 {
@@ -478,6 +428,119 @@
 	return x;
 }
 
+static void cdr_object_snapshot_copy(struct cdr_object_snapshot *dst, struct cdr_object_snapshot *src)
+{
+	if (dst->snapshot) {
+		ao2_ref(dst->snapshot, -1);
+	}
+	dst->snapshot = src->snapshot;
+	ao2_ref(dst->snapshot, +1);
+	strcpy(dst->userfield, src->userfield);
+	dst->flags = src->flags;
+	copy_vars(&dst->variables, &src->variables);
+}
+
+/* NON-VIRTUAL FUNCTIONS */
+
+static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table);
+
+/*! \internal
+ * \brief Hash function for containers of CDRs indexing by Party A name */
+static int cdr_object_channel_hash_fn(const void *obj, const int flags)
+{
+	const struct cdr_object *cdr = obj;
+	const char *name = (flags & OBJ_KEY) ? obj : cdr->name;
+	return ast_str_case_hash(name);
+}
+
+/*! \internal
+ * \brief Comparison function for containers of CDRs indexing by Party A name
+ */
+static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
+{
+	struct cdr_object *left = obj;
+	struct cdr_object *right = arg;
+	const char *match = (flags & OBJ_KEY) ? arg : right->name;
+	return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
+}
+
+/*! \internal
+ * \brief Hash function for containers of CDRs indexing by bridge ID
+ */
+static int cdr_object_bridge_hash_fn(const void *obj, const int flags)
+{
+	const struct cdr_object *cdr = obj;
+	const char *id = (flags & OBJ_KEY) ? obj : cdr->bridge;
+	return ast_str_case_hash(id);
+}
+
+/*! \internal
+ * \brief Comparison function for containers of CDRs indexing by bridge. Note
+ * that we expect there to be collisions, as a single bridge may have multiple
+ * CDRs active at one point in time
+ */
+static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
+{
+	struct cdr_object *left = obj;
+	struct cdr_object *right = arg;
+	const char *match = (flags & OBJ_KEY) ? arg : right->bridge;
+	return strcasecmp(left->bridge, match) ? 0 : (CMP_MATCH);
+}
+
+static void cdr_object_dtor(void *obj)
+{
+	struct cdr_object *cdr = obj;
+	struct ast_var_t *it_var;
+
+	if (!cdr) {
+		return;
+	}
+
+	ao2_cleanup(cdr->party_a.snapshot);
+	ao2_cleanup(cdr->party_b.snapshot);
+	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a.variables, entries))) {
+		ast_var_delete(it_var);
+	}
+	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_b.variables, entries))) {
+		ast_var_delete(it_var);
+	}
+	ast_string_field_free_memory(cdr);
+
+	if (cdr->next) {
+		ao2_cleanup(cdr->next);
+	}
+}
+
+static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
+{
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct cdr_object *cdr;
+
+	ast_assert(chan != NULL);
+
+	cdr = ao2_alloc(sizeof(*cdr), cdr_object_dtor);
+	if (!cdr) {
+		return NULL;
+	}
+	cdr->last = cdr;
+	if (ast_string_field_init(cdr, 64)) {
+		return NULL;
+	}
+	ast_string_field_set(cdr, name, chan->name);
+	ast_string_field_set(cdr, linkedid, chan->linkedid);
+	cdr->disposition = AST_CDR_NULL;
+	cdr->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1);
+
+	cdr->party_a.snapshot = chan;
+	ao2_ref(cdr->party_a.snapshot, +1);
+
+	CDR_DEBUG(mod_cfg, "%p - created CDR for channel %s\n", cdr, chan->name);
+
+	cdr_object_transition_state(cdr, &single_state_fn_table);
+
+	return cdr;
+}
+
 static struct cdr_object *cdr_object_create_and_append_cdr(struct cdr_object *cdr)
 {
 	struct cdr_object *new_cdr;
@@ -492,9 +555,7 @@
 	/* Copy over the linkedid, as it may have changed */
 	ast_string_field_set(new_cdr, linkedid, cdr_last->linkedid);
 	/* Copy over other Party A information */
-	strcpy(new_cdr->party_a.userfield, cdr_last->party_a.userfield);
-	new_cdr->party_a.flags = cdr_last->party_a.flags;
-	copy_vars(&new_cdr->party_a.variables, &cdr_last->party_a.variables);
+	cdr_object_snapshot_copy(&new_cdr->party_a, &cdr_last->party_a);
 
 	/* Append the CDR to the end of the list */
 	for (it_cdr = cdr; it_cdr->next; it_cdr = it_cdr->next) {
@@ -502,8 +563,61 @@
 	}
 	it_cdr->last = new_cdr;
 	it_cdr->next = new_cdr;
+	new_cdr->prev = it_cdr;
 
 	return new_cdr;
+}
+
+/*static void cdr_object_remove_cdr(struct cdr_object *master, struct cdr_object *record)
+{
+	struct cdr_object *it_cdr;
+	struct cdr_object *prev = NULL;
+	struct cdr_object *next = NULL;
+
+	for (it_cdr = master; it_cdr; prev = it_cdr, it_cdr = it_cdr->next) {
+		if (it_cdr != record) {
+			continue;
+		}
+		if (it_cdr->next) {
+			next = it_cdr->next;
+		}
+		if (it_cdr == master) {*/
+			/* Remove root */
+/*			ao2_unlink(active_cdrs_by_channel, master);
+			if (!ast_strlen_zero(master->bridge)) {
+				ao2_unlink(active_cdrs_by_bridge, master);
+			}
+		}
+		it_cdr->next = NULL;
+		it_cdr->last = NULL;
+		if (next && prev) {
+			prev->next = next;
+			prev->last = next->last;
+		}
+		if (next && it_cdr == master) {
+			ao2_link(active_cdrs_by_channel, next);
+			if (!ast_strlen_zero(next->bridge)) {
+				ao2_link(active_cdrs_by_bridge, next);
+			}
+		}
+		ao2_ref(it_cdr, -1);
+	}
+}*/
+
+static struct ast_channel_snapshot *cdr_object_determine_party_a(struct ast_channel_snapshot *left, struct ast_channel_snapshot *right)
+{
+	/* TODO: check party a flag */
+
+	if (left->creationtime.tv_sec < right->creationtime.tv_sec) {
+		return left;
+	} else if (left->creationtime.tv_sec > right->creationtime.tv_sec) {
+		return right;
+	} else {
+		if (left->creationtime.tv_usec > right->creationtime.tv_usec) {
+			return right;
+		}
+		return left;
+	}
 }
 
 static long cdr_object_get_duration(struct cdr_object *cdr)
@@ -543,6 +657,15 @@
 
 	while (cdr) {
 		struct ast_cdr *cdr_copy;
+
+		/* Don't create records for CDRs where the party A is an outgoing,
+		 * non-originated channel.
+		 */
+		if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_OUTGOING)
+			&& !ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ORIGINATED)) {
+			continue;
+		}
+
 		cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
 		if (!cdr_copy) {
 			ast_free(pub_cdr);
@@ -687,10 +810,6 @@
 			cdr->end.tv_sec,
 			cdr->end.tv_usec,
 			cdr->disposition);
-
-	if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)) {
-		cdr_object_transition_state(cdr, &hangup_state_fn_table);
-	}
 }
 
 static void cdr_object_transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table)
@@ -739,8 +858,6 @@
 {
 	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 
-	ast_assert(cdr != NULL);
-	ast_assert(snapshot != NULL);
 	ast_assert(strcmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
 
 	cdr_object_swap_snapshot(&cdr->party_a, snapshot);
@@ -749,6 +866,14 @@
 	cdr_object_check_party_a_answer(cdr);
 	cdr_object_check_party_a_hangup(cdr);
 }
+
+static int base_process_bridge_leave_message(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	/* Assume we shouldn't get a bridge leave message for ourselves */
+	ast_assert(strcmp(channel->name, cdr->party_a.snapshot->name) != 0);
+	return 0;
+}
+
 
 
 /* SINGLE STATE */
@@ -791,6 +916,87 @@
 	return 0;
 }
 
+static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
+		struct cdr_object *cand_cdr)
+{
+	struct ast_channel_snapshot *party_a;
+
+	party_a = cdr_object_determine_party_a(cdr->party_a.snapshot, cand_cdr->party_a.snapshot);
+	if (party_a == cdr->party_a.snapshot) {
+		cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
+		if (!cand_cdr->party_b.snapshot) {
+			/* We just stole them - finalize the CDR. Note that this won't
+			 * transition their state, it just sets the end time and the
+			 * disposition - if we need to re-activate them later, we can.
+			 */
+			cdr_object_finalize(cand_cdr);
+		}
+		return 0;
+	}
+
+	if (!cand_cdr->party_b.snapshot) {
+		return 1;
+	}
+
+	party_a = cdr_object_determine_party_a(cdr->party_a.snapshot, cand_cdr->party_b.snapshot);
+	if (party_a == cdr->party_a.snapshot) {
+		cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_b);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	struct ao2_iterator *it_cdrs;
+	struct cdr_object *cand_cdr_master;
+	char *bridge_id = ast_strdupa(bridge->uniqueid);
+	int success = 1;
+
+	ast_string_field_set(cdr, bridge, bridge->uniqueid);
+
+	/* Get parties in the bridge */
+	it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
+			cdr_object_bridge_cmp_fn, bridge_id);
+	if (!it_cdrs) {
+		/* No one in the bridge yet! */
+		cdr_object_transition_state(cdr, &bridge_state_fn_table);
+		return 0;
+	}
+
+	while ((cand_cdr_master = ao2_iterator_next(it_cdrs))) {
+		struct cdr_object *cand_cdr;
+
+		ao2_lock(cand_cdr_master);
+		for (cand_cdr = cand_cdr_master; cand_cdr; cand_cdr = cand_cdr->next) {
+			/* Skip any records that are not in a bridge or in this bridge.
+			 * I'm not sure how that would happen, but it pays to be careful. */
+			if (cand_cdr->fn_table != &bridge_state_fn_table || strcmp(cdr->bridge, cand_cdr->bridge)) {
+				continue;
+			}
+
+			if (single_state_bridge_enter_comparison(cdr, cand_cdr)) {
+				/* Keep looking */
+				continue;
+			}
+			/* We successfully got a party B - break out */
+			success = 0;
+			break;
+		}
+		ao2_unlock(cand_cdr_master);
+		ao2_ref(cand_cdr_master, -1);
+	}
+	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;
+}
+
+
 /* FINALIZED STATE */
 
 static void finalized_state_init_function(struct cdr_object *cdr)
@@ -807,7 +1013,7 @@
 static void finalized_state_process_party_a_update(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
 {
 	if (ast_test_flag(&cdr->party_a.snapshot->flags, AST_FLAG_ZOMBIE)) {
-		cdr_object_transition_state(cdr, &hangup_state_fn_table);
+		cdr_object_finalize(cdr);
 	}
 }
 
@@ -872,6 +1078,15 @@
 }
 
 
+/* BRIDGE STATE */
+
+static void bridge_state_init_function(struct cdr_object *cdr)
+{
+
+}
+
+
+
 
 
 static struct cdr_object *cdr_object_find_or_create_cdr_by_channel(struct ast_channel_snapshot *snapshot)
@@ -890,22 +1105,6 @@
 	ao2_link(active_cdrs_by_channel, cdr);
 
 	return cdr;
-}
-
-static struct ast_channel_snapshot *cdr_object_determine_party_a(struct ast_channel_snapshot *left, struct ast_channel_snapshot *right)
-{
-	/* TODO: check party a flag */
-
-	if (left->creationtime.tv_sec < right->creationtime.tv_sec) {
-		return left;
-	} else if (left->creationtime.tv_sec > right->creationtime.tv_sec) {
-		return right;
-	} else {
-		if (left->creationtime.tv_usec > right->creationtime.tv_usec) {
-			return right;
-		}
-		return left;
-	}
 }
 
 /* TOPIC ROUTER CALLBACKS */
@@ -952,32 +1151,24 @@
 		return;
 	}
 
-	if (ast_test_flag(&party_a->flags, AST_FLAG_OUTGOING)
-		&& !ast_test_flag(&party_a->flags, AST_FLAG_ORIGINATED)) {
-		CDR_DEBUG(mod_cfg, "%p - removing CDR for outbound channel %s\n", cdr, party_a->name);
-		ao2_unlink(active_cdrs_by_channel, cdr);
-		return;
-	}
-
 	CDR_DEBUG(mod_cfg, "%p - processing dial message for channel %s, peer %s\n",
 			cdr, party_a->name, party_b ? party_b->name : "(none)");
-	{
-		SCOPED_AO2LOCK(lock, cdr);
-		for (it_cdr = cdr; res && it_cdr; it_cdr = it_cdr->next) {
-			if (it_cdr->fn_table->process_dial_message) {
-				res = it_cdr->fn_table->process_dial_message(it_cdr, party_a, party_b, dial_status);
-			}
-		}
-		/* If no CDR handled the dial message, make a new one */
-		if (res) {
-			struct cdr_object *new_cdr;
-
-			new_cdr = cdr_object_create_and_append_cdr(cdr);
-			if (!new_cdr) {
-				return;
-			}
-			new_cdr->fn_table->process_dial_message(new_cdr, party_a, party_b, dial_status);
-		}
+	ao2_lock(cdr);
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table->process_dial_message) {
+			res &= it_cdr->fn_table->process_dial_message(it_cdr, party_a, party_b, dial_status);
+		}
+	}
+
+	/* If no CDR handled the dial message, make a new one */
+	if (res) {
+		struct cdr_object *new_cdr;
+
+		new_cdr = cdr_object_create_and_append_cdr(cdr);
+		if (!new_cdr) {
+			return;
+		}
+		new_cdr->fn_table->process_dial_message(new_cdr, party_a, party_b, dial_status);
 	}
 }
 
@@ -1010,7 +1201,7 @@
 	return 0;
 }
 
-static void handle_cache_update_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_topic *topic, struct stasis_message *message)
 {
 	RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
 	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
@@ -1036,18 +1227,6 @@
 				return;
 			}
 			ao2_link(active_cdrs_by_channel, cdr);
-		}
-
-		/* We won't know this is a dialed channel until after its creation. */
-		if (ast_test_flag(&new_snapshot->flags, AST_FLAG_OUTGOING)
-				&& !ast_test_flag(&new_snapshot->flags, AST_FLAG_ORIGINATED)) {
-			cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
-			if (cdr) {
-				CDR_DEBUG(mod_cfg, "%p - removing CDR for outbound channel %s\n", cdr, name);
-				ao2_unlink(active_cdrs_by_channel, cdr);
-			}
-			ao2_cleanup(cdr);
-			cdr = NULL;
 		}
 	}
 
@@ -1085,7 +1264,294 @@
 
 }
 
-
+static void handle_bridge_leave_message(struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+
+}
+
+struct bridge_candidate {
+	struct cdr_object *cdr;					/*!< The actual CDR this candidate belongs to, either as A or B */
+	struct ast_channel_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->name;
+	return strcasecmp(left->candidate->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->name;
+	return ast_str_case_hash(id);
+}
+
+static void bridge_candidate_dtor(void *obj)
+{
+	struct bridge_candidate *bcand = obj;
+	ao2_cleanup(bcand->cdr);
+	ao2_cleanup(bcand->candidate);
+}
+
+static struct bridge_candidate *bridge_candidate_alloc(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	struct bridge_candidate *bcand;
+
+	bcand = ao2_alloc(sizeof(*bcand), bridge_candidate_dtor);
+	if (!bcand) {
+		return NULL;
+	}
+	bcand->cdr = cdr;
+	bcand->candidate = snapshot;
+	ao2_ref(bcand->cdr, +1);
+	ao2_ref(bcand->candidate, +1);
+
+	return bcand;
+}
+
+/*!
+ * 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.
+ */
+static struct ao2_container *handle_bridge_create_candidates(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. Don't add a party twice.
+	 */
+	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))) {
+		struct cdr_object *it_cdr;
+
+		ao2_lock(cand_cdr_master);
+		for (it_cdr = cand_cdr_master; it_cdr; it_cdr = it_cdr->next) {
+			struct bridge_candidate *bcand = NULL;
+
+			if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge->uniqueid, it_cdr->bridge)) {
+				continue;
+			}
+			bcand = ao2_find(candidates, it_cdr->party_a.snapshot->name, OBJ_KEY);
+			if (!bcand) {
+				bcand = bridge_candidate_alloc(it_cdr, it_cdr->party_a.snapshot);
+				if (bcand) {
+					ao2_link(candidates, bcand);
+				}
+			}
+			ao2_cleanup(bcand);
+		}
+	}
+	ao2_iterator_destroy(it_cdrs);
+
+	/* For each CDR that has a record in the bridge, get their Party B and
+	 * make them a candidate. Don't add a party twice.
+	 */
+	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))) {
+		struct cdr_object *it_cdr;
+
+		ao2_lock(cand_cdr_master);
+		for (it_cdr = cand_cdr_master; it_cdr; it_cdr = it_cdr->next) {
+			struct bridge_candidate *bcand = NULL;
+
+			if (it_cdr->fn_table != &bridge_state_fn_table || strcmp(bridge->uniqueid, it_cdr->bridge)) {
+				continue;
+			}
+			if (!it_cdr->party_b.snapshot) {
+				continue;
+			}
+			bcand = ao2_find(candidates, it_cdr->party_b.snapshot->name, OBJ_KEY);
+			if (!bcand) {
+				bcand = bridge_candidate_alloc(it_cdr, it_cdr->party_b.snapshot);
+				if (bcand) {
+					ao2_link(candidates, bcand);
+				}
+			}
+			ao2_cleanup(bcand);
+		}
+		ao2_unlock(cand_cdr_master);
+	}
+	ao2_iterator_destroy(it_cdrs);
+
+	return candidates;
+}
+
+static int bridge_candidate_process(void *obj, void *arg, int flags)
+{
+	struct bridge_candidate *bcand = obj;
+	struct cdr_object *cdr = arg;
+	struct cdr_object *new_cdr;
+	struct ast_channel_snapshot *party_a;
+
+	/* If the candidate is us or someone we've taken on, pass on by */
+	if (!strcmp(cdr->party_a.snapshot->name, bcand->candidate->name)
+		|| (cdr->party_b.snapshot && !(strcmp(cdr->party_b.snapshot->name, bcand->candidate->name)))) {
+		return 0;
+	}
+
+	party_a = cdr_object_determine_party_a(cdr->party_a.snapshot, bcand->candidate);
+	if (party_a == cdr->party_a.snapshot) {
+		new_cdr = cdr_object_create_and_append_cdr(cdr);
+		if (bcand->candidate == bcand->cdr->party_a.snapshot) {
+			cdr_object_snapshot_copy(&new_cdr->party_b, &bcand->cdr->party_a);
+		} else if (bcand->candidate == bcand->cdr->party_b.snapshot) {
+			cdr_object_snapshot_copy(&new_cdr->party_b, &bcand->cdr->party_b);
+		}
+		ast_string_field_set(new_cdr, bridge, cdr->bridge);
+		cdr_object_transition_state(new_cdr, &bridge_state_fn_table);
+	} else {
+		/* The candidate may be 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 (bcand->cdr->party_a.snapshot == bcand->candidate) {
+			if (bcand->cdr->party_b.snapshot) {
+				new_cdr = cdr_object_create_and_append_cdr(bcand->cdr);
+				cdr_object_snapshot_copy(&new_cdr->party_b, &cdr->party_a);
+				ast_string_field_set(new_cdr, bridge, cdr->bridge);
+				cdr_object_transition_state(new_cdr, &bridge_state_fn_table);
+			} else {
+				cdr_object_snapshot_copy(&bcand->cdr->party_b, &cdr->party_a);
+				/* It's possible that this joined at one point and was never chosen
+				 * as party A. Clear their end time, as it would be set in such a
+				 * case.
+				 */
+				memset(&bcand->cdr->end, 0, sizeof(bcand->cdr->end));
+			}
+		} else {
+			struct cdr_object *b_party = ao2_find(active_cdrs_by_channel, bcand->candidate->name, OBJ_KEY);
+			if (!b_party) {
+				b_party = cdr_object_alloc(bcand->candidate);
+				cdr_object_transition_state(b_party, &bridge_state_fn_table);
+				cdr_object_snapshot_copy(&b_party->party_b, &cdr->party_a);
+				ast_string_field_set(b_party, bridge, cdr->bridge);
+				ao2_link(active_cdrs_by_channel, b_party);
+				ao2_link(active_cdrs_by_bridge, b_party);
+				ao2_ref(b_party, -1);
+				return 0;
+			}
+			new_cdr = cdr_object_create_and_append_cdr(b_party);
+			cdr_object_transition_state(b_party, &bridge_state_fn_table);
+			cdr_object_snapshot_copy(&b_party->party_b, &cdr->party_a);
+			ast_string_field_set(b_party, bridge, cdr->bridge);
+			ao2_link(active_cdrs_by_bridge, b_party);
+		}
+	}
+
+	return 0;
+}
+
+static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_snapshot *bridge)
+{
+	RAII_VAR(struct ao2_container *, candidates,
+			handle_bridge_create_candidates(bridge),
+			ao2_cleanup);
+
+	if (!candidates) {
+		return;
+	}
+
+	ao2_callback(candidates, OBJ_NODATA,
+			bridge_candidate_process,
+			cdr);
+
+	return;
+}
+
+
+static void handle_bridge_enter_message(struct ast_bridge_snapshot *bridge, struct ast_channel_snapshot *channel)
+{
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel->name, OBJ_KEY),
+			ao2_cleanup);
+	int res = 1;
+	struct cdr_object *it_cdr;
+	struct cdr_object *handled_cdr = NULL;
+
+	ast_assert(cdr != NULL);
+
+	ao2_lock(cdr);
+
+	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table->process_party_a_update) {
+			it_cdr->fn_table->process_party_a_update(it_cdr, channel);
+		}
+
+		/* Notify all states that they have entered a bridge */
+		if (it_cdr->fn_table->process_bridge_enter) {
+			res &= it_cdr->fn_table->process_bridge_enter(it_cdr, bridge, channel);
+			if (!res && !handled_cdr) {
+				handled_cdr = it_cdr;
+			}
+		}
+	}
+
+	if (res) {
+		/* We didn't win on any - end this CDR. If someone else comes in later
+		 * that is Party B to this CDR, it can re-activate this CDR.
+		 */
+		cdr_object_finalize(cdr);
+	}
+
+	/* Create the new matchings, but only for either:
+	 *  * The first CDR in the chain that handled it. This avoids issues with
+	 *    forked CDRs.
+	 *  * If no one handled it, the last CDR in the chain. This would occur if
+	 *    a CDR joined a bridge and it wasn't Party A for anyone. We still need
+	 *    to make pairings with everyone in the bridge.
+	 */
+	if (!handled_cdr) {
+		handled_cdr = cdr->last;
+	}
+	handle_bridge_pairings(handled_cdr, bridge);
+
+	ao2_link(active_cdrs_by_bridge, cdr);
+	ao2_unlock(cdr);
+}
+
+static void handle_bridge_message(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+	struct ast_bridge_blob *update = stasis_message_data(message);
+	const char *type = ast_bridge_blob_json_type(update);
+
+	ast_assert(update->bridge != NULL);
+	ast_assert(update->channel != NULL);
+
+	if (!strcmp(type, "leave")) {
+		handle_bridge_leave_message(update->bridge, update->channel);
+	} else if (!strcmp(type, "enter")) {
+		handle_bridge_enter_message(update->bridge, update->channel);
+	}
+}
 
 
 
@@ -1880,6 +2346,12 @@
 				}
 				cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
 			}
+		} else {
+			/* Denote that this CDR is a fork of a previous, and the previous
+			 * is still active. Operations on CDRs will use this flag to force
+			 * this CDR record to mirror the state of the previous.
+			 */
+			ast_set_flag(&new_cdr->flags, INTERNAL_CDR_FLAG_FORKED);
 		}
 	}
 
@@ -2293,13 +2765,23 @@
 		return -1;
 	}
 
-	stasis_router = stasis_message_router_create(stasis_caching_get_topic(ast_channel_topic_all_cached()));
-	if (!stasis_router) {
+	active_cdrs_by_bridge = ao2_container_alloc(51, cdr_object_bridge_hash_fn, cdr_object_bridge_cmp_fn);
+	if (!active_cdrs_by_bridge) {
 		return -1;
 	}
-	stasis_message_router_add(stasis_router, stasis_cache_update_type(), handle_cache_update_message, NULL);
-	stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
-
+
+	stasis_channel_router = stasis_message_router_create(stasis_caching_get_topic(ast_channel_topic_all_cached()));
+	if (!stasis_channel_router) {
+		return -1;
+	}
+	stasis_message_router_add(stasis_channel_router, stasis_cache_update_type(), handle_channel_cache_message, NULL);
+	stasis_message_router_add(stasis_channel_router, ast_channel_dial_type(), handle_dial_message, NULL);
+
+	stasis_bridge_router = stasis_message_router_create(stasis_caching_get_topic(ast_bridge_topic_all_cached()));
+	if (!stasis_bridge_router) {
+		return -1;
+	}
+	stasis_message_router_add(stasis_bridge_router, ast_bridge_blob_type(), handle_bridge_message, NULL);
 
 	sched = ast_sched_context_create();
 	if (!sched) {

Modified: team/mjordan/cdrs-of-doom/tests/test_cdr.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/tests/test_cdr.c?view=diff&rev=386588&r1=386587&r2=386588
==============================================================================
--- team/mjordan/cdrs-of-doom/tests/test_cdr.c (original)
+++ team/mjordan/cdrs-of-doom/tests/test_cdr.c Thu Apr 25 16:55:31 2013
@@ -42,7 +42,10 @@
 #include "asterisk/utils.h"
 #include "asterisk/causes.h"
 #include "asterisk/time.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
 
 #define EPSILON 0.001
 
@@ -70,15 +73,22 @@
 	.settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG | CDR_CONGESTION,
 };
 
+/*! \brief Macro to swap a configuration out from the CDR engine. This should be
+ * used at the beginning of each test to set the needed configuration for that
+ * test.
+ */
 #define SWAP_CONFIG(ao2_config, template) do { \
 	*(ao2_config) = (template); \
 	ast_cdr_set_config((ao2_config)); \
 	} while (0)
 
+/*! \brief A linked list of received CDR entries from the engine */
 static AST_LIST_HEAD(, test_cdr_entry) actual_cdr_entries = AST_LIST_HEAD_INIT_VALUE;
 
+/*! \brief The Mock CDR backend condition wait */
 static ast_cond_t mock_cdr_cond;
 
+/*! \brief A channel technology used for the unit tests */
 static struct ast_channel_tech test_cdr_chan_tech = {
 	.type = CHANNEL_TECH_NAME,
 	.description = "Mock channel technology for CDR tests",
@@ -89,9 +99,22 @@
 	AST_LIST_ENTRY(test_cdr_entry) list;
 };
 
-static int mock_cdr_count;
-
-static int mock_cdr_backend(struct ast_cdr *cdr)
+/*! \brief The number of CDRs the mock backend has received */
+static int global_mock_cdr_count;
+
+/*! \internal
+ * \brief Callback function for the mock CDR backend
+ *
+ * This function 'processes' a dispatched CDR record by adding it to the
+ * \ref actual_cdr_entries list. When a test completes, it can verify the
+ * expected records against this list of actual CDRs created by the engine.
+ *
+ * \param cdr The public CDR object created by the engine
+ *
+ * \retval -1 on error
+ * \retval 0 on success
+ */
+static int mock_cdr_backend_cb(struct ast_cdr *cdr)
 {
 	struct ast_cdr *cdr_copy, *cdr_prev = NULL;
 	struct ast_cdr *mock_cdr = NULL;
@@ -134,13 +157,16 @@
 
 	AST_LIST_LOCK(&actual_cdr_entries);
 	AST_LIST_INSERT_TAIL(&actual_cdr_entries, cdr_wrapper, list);
-	mock_cdr_count++;
+	global_mock_cdr_count++;
 	ast_cond_signal(&mock_cdr_cond);
 	AST_LIST_UNLOCK(&actual_cdr_entries);
 
 	return 0;
 }
 
+/*! \internal
+ * \brief Remove all entries from \ref actual_cdr_entries
+ */
 static void clear_mock_cdr_backend(void)
 {
 	struct test_cdr_entry *cdr_wrapper;
@@ -150,10 +176,14 @@
 		ast_cdr_free(cdr_wrapper->cdr);
 		ast_free(cdr_wrapper);
 	}
-	mock_cdr_count = 0;
+	global_mock_cdr_count = 0;
 	AST_LIST_UNLOCK(&actual_cdr_entries);
 }
 
+/*! \brief Verify a string field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
 #define VERIFY_STRING_FIELD(field, actual, expected) do { \
 	if (strcmp((actual)->field, (expected)->field)) { \
 		ast_test_status_update(test, "Field %s failed: actual %s, expected %s\n", #field, (actual)->field, (expected)->field); \
@@ -161,6 +191,10 @@
 		res = AST_TEST_FAIL; \
 	} } while (0)
 
+/*! \brief Verify a numeric field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
 #define VERIFY_NUMERIC_FIELD(field, actual, expected) do { \
 	if ((actual)->field != (expected)->field) { \
 		ast_test_status_update(test, "Field %s failed: actual %ld, expected %ld\n", #field, (long)(actual)->field, (long)(expected)->field); \
@@ -168,6 +202,10 @@
 		res = AST_TEST_FAIL; \
 	} } while (0)
 
+/*! \brief Verify a time field. This will set the test status result to fail;
+ * as such, it assumes that (a) test is the test object variable, and (b) that
+ * a return variable res exists.
+ */
 #define VERIFY_TIME_VALUE(field, actual) do { \
 	if (ast_tvzero((actual)->field)) { \
 		ast_test_status_update(test, "Field %s failed: should not be 0\n", #field); \
@@ -175,7 +213,37 @@
 		res = AST_TEST_FAIL; \
 	} } while (0)
 
+/*! \brief Alice's Caller ID */
 #define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, }
+
+/*! \brief Copy the linkedid and uniqueid from a channel to an expected CDR */
+#define COPY_IDS(channel_var, expected_record) do { \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Create a \ref test_cdr_chan_tech for Alice, and set the expected
+ * CDR records (if available) to her linkedid and uniqueid. */
+#define CREATE_ALICE_CHANNEL(channel_var, caller_id, expected_record) do { \
+	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice"); \
+	ast_channel_set_caller((channel_var), (caller_id), NULL); \
+	ast_copy_string((expected_record)->uniqueid, ast_channel_uniqueid((channel_var)), sizeof((expected_record)->uniqueid)); \
+	ast_copy_string((expected_record)->linkedid, ast_channel_linkedid((channel_var)), sizeof((expected_record)->linkedid)); \
+	} while (0)
+
+/*! \brief Emulate a channel entering into an application */
+#define EMULATE_APP_DATA(channel, application, data) do { \
+	ast_channel_appl_set((channel), (application)); \
+	ast_channel_data_set((channel), (data)); \
+	publish_channel_snapshot((channel)); \
+	} while (0)
+

[... 715 lines stripped ...]



More information about the svn-commits mailing list