[asterisk-commits] kmoore: branch kmoore/stasis-bridging-channel_events r385525 - in /team/kmoor...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Apr 12 16:17:31 CDT 2013


Author: kmoore
Date: Fri Apr 12 16:17:27 2013
New Revision: 385525

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=385525
Log:
Multiple revisions 385475,385505,385523

........
  r385475 | root | 2013-04-12 10:17:48 -0500 (Fri, 12 Apr 2013) | 43 lines
  
  Fix One-Way Audio With auto_* NAT Settings When SIP Calls Initiated By PBX
  
  When we reload Asterisk or chan_sip, the flags force_rport and comedia that are
  turned on and off when using the auto_force_rport and auto_comedia nat settings
  go back to the default setting off.  These flags are turned on when needed or
  off when not needed at the time that a peer registers, re-registers or initiates
  a call.  This would apply even when only the default global setting
  "nat=auto_force_rport" is being used, which in this case would only affect the
  force_rport flag.
  
  Everything is good except for the following:  The nat setting is set to
  auto_force_rport and auto_comedia.  We reload Asterisk and the peer's
  registration has not expired.  We load in the settings for the peer which turns
  force_rport and comedia back to off.  Since the peer has not re-registered or
  placed a call yet, those flags remain off.  We then initiate a call to the peer
  from the PBX.  The force_rport and comedia flags stay off.  If NAT is involved,
  we end up with one-way audio since we never checked to see if the peer is behind
  NAT or not.
  
  This patch does the following:
  
  * Moves the checking of whether a peer is behind NAT into its own function
  
  * Create a function to set the peer's NAT flags if they are using the auto_* NAT
    settings
  
  * Adds calls in sip_request_call() to these new functions in order to setup the
    dialog according to the peer's settings
  
  (closes issue ASTERISK-21374)
  Reported by: Michael L. Young
  Tested by: Michael L. Young
  Patches:
      asterisk-21374-auto-nat-outgoing-fix_v2.diff Michael L. Young (license 5026)
  
  Review: https://reviewboard.asterisk.org/r/2421/
  ........
  
  Merged revisions 385473 from http://svn.asterisk.org/svn/asterisk/branches/11
  ........
  
  Merged revisions 385474 from file:///srv/subversion/repos/asterisk/trunk
........
  r385505 | rmudgett | 2013-04-12 11:38:28 -0500 (Fri, 12 Apr 2013) | 20 lines
  
  Add bridge CLI commands.
  
  * Added bridge CLI commands:
  bridge destroy <bridge-id>
  bridge kick <bridge-id> <channel>
  bridge show all
  bridge show <bridge-id>
  bridge technology show
  bridge technology {suspend|unsuspend} <technology>
  
  * Created global bridges container for manipulation by bridge CLI
  commands.
  
  * Add ast_bridge_register() for ast_bridge_xxx_new() calls to register the
  bridge for access by the CLI commands.
  
  (closes issue ASTERISK-21331)
  
  Review: https://reviewboard.asterisk.org/r/2425/
........
  r385523 | kmoore | 2013-04-12 16:16:40 -0500 (Fri, 12 Apr 2013) | 8 lines
  
  Expose channel snapshot manager blob generation
  
  These functions are already used in one branch (jrose's parking branch)
  and will soon be used in other branches as well.
  ........
  
  Merged revisions 385522 from http://svn.asterisk.org/svn/asterisk/trunk
........

Merged revisions 385475,385505,385523 from http://svn.asterisk.org/svn/asterisk/team/group/bridge_construction

Modified:
    team/kmoore/stasis-bridging-channel_events/   (props changed)
    team/kmoore/stasis-bridging-channel_events/channels/chan_sip.c
    team/kmoore/stasis-bridging-channel_events/include/asterisk/bridging.h
    team/kmoore/stasis-bridging-channel_events/include/asterisk/manager.h
    team/kmoore/stasis-bridging-channel_events/main/bridging.c
    team/kmoore/stasis-bridging-channel_events/main/features.c
    team/kmoore/stasis-bridging-channel_events/main/manager_channels.c

Propchange: team/kmoore/stasis-bridging-channel_events/
------------------------------------------------------------------------------
Binary property 'branch-11-merged' - no diff available.

Propchange: team/kmoore/stasis-bridging-channel_events/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Fri Apr 12 16:17:27 2013
@@ -1,1 +1,1 @@
-/team/group/bridge_construction:1-385468
+/team/group/bridge_construction:1-385523

Modified: team/kmoore/stasis-bridging-channel_events/channels/chan_sip.c
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/stasis-bridging-channel_events/channels/chan_sip.c?view=diff&rev=385525&r1=385524&r2=385525
==============================================================================
--- team/kmoore/stasis-bridging-channel_events/channels/chan_sip.c (original)
+++ team/kmoore/stasis-bridging-channel_events/channels/chan_sip.c Fri Apr 12 16:17:27 2013
@@ -1273,6 +1273,8 @@
 static void ast_quiet_chan(struct ast_channel *chan);
 static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target);
 static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
+static void set_peer_nat(const struct sip_pvt *p, struct sip_peer *peer);
+static void check_for_nat(const struct ast_sockaddr *them, struct sip_pvt *p);
 
 /*--- Device monitoring and Device/extension state/event handling */
 static int extensionstate_update(const char *context, const char *exten, struct state_notify_data *data, struct sip_pvt *p, int force);
@@ -17107,22 +17109,12 @@
 			ast_log(LOG_ERROR, "Peer '%s' is trying to register, but not configured as host=dynamic\n", peer->name);
 			res = AUTH_PEER_NOT_DYNAMIC;
 		} else {
-			if (ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
-				if (p->natdetected) {
-					ast_set_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT);
-				} else {
-					ast_clear_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT);
-				}
-			}
-			if (ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
-				if (p->natdetected) {
-					ast_set_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP);
-				} else {
-					ast_clear_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP);
-				}
-			}
-
-			ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_NAT_FORCE_RPORT);
+
+			set_peer_nat(p, peer);
+			if (p->natdetected && ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
+				ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_NAT_FORCE_RPORT);
+			}
+
 			if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, SIP_REGISTER, uri2, XMIT_UNRELIABLE))) {
 				if (sip_cancel_destroy(p))
 					ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
@@ -18151,6 +18143,67 @@
 	return -1;
 }
 
+/*! \brief Set the peers nat flags if they are using auto_* settings */
+static void set_peer_nat(const struct sip_pvt *p, struct sip_peer *peer)
+{
+
+	if (!p || !peer) {
+		return;
+	}
+
+	if (ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
+		if (p->natdetected) {
+			ast_set_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT);
+		} else {
+			ast_clear_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT);
+		}
+	}
+
+	if (ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
+		if (p->natdetected) {
+			ast_set_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP);
+		} else {
+			ast_clear_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP);
+		}
+	}
+}
+
+/*! \brief Check and see if the requesting UA is likely to be behind a NAT.
+ *
+ * If the requesting NAT is behind NAT, set the * natdetected flag so that
+ * later, peers with nat=auto_* can use the value. Also, set the flags so
+ * that Asterisk responds identically whether or not a peer exists so as
+ * not to leak peer name information.
+ */
+static void check_for_nat(const struct ast_sockaddr *addr, struct sip_pvt *p)
+{
+
+	if (!addr || !p) {
+		return;
+	}
+
+	if (ast_sockaddr_cmp(addr, &p->recv)) {
+		char *tmp_str = ast_strdupa(ast_sockaddr_stringify(addr));
+		ast_debug(3, "NAT detected for %s / %s\n", tmp_str, ast_sockaddr_stringify(&p->recv));
+		p->natdetected = 1;
+		if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
+			ast_set_flag(&p->flags[0], SIP_NAT_FORCE_RPORT);
+		}
+		if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
+			ast_set_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
+		}
+	} else {
+		p->natdetected = 0;
+		if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
+			ast_clear_flag(&p->flags[0], SIP_NAT_FORCE_RPORT);
+		}
+		if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
+			ast_clear_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
+		}
+	}
+
+}
+
 /*! \brief check Via: header for hostname, port and rport request/answer */
 static void check_via(struct sip_pvt *p, const struct sip_request *req)
 {
@@ -18214,29 +18267,7 @@
 
 		ast_sockaddr_set_port(&p->sa, port);
 
-		/* Check and see if the requesting UA is likely to be behind a NAT. If they are, set the
-		 * natdetected flag so that later, peers with nat=auto_* can use the value. Also
-		 * set the flags so that Asterisk responds identically whether or not a peer exists
-		 * so as not to leak peer name information. */
-		if (ast_sockaddr_cmp(&tmp, &p->recv)) {
-			char *tmp_str = ast_strdupa(ast_sockaddr_stringify(&tmp));
-			ast_debug(3, "NAT detected for %s / %s\n", tmp_str, ast_sockaddr_stringify(&p->recv));
-			p->natdetected = 1;
-			if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
-				ast_set_flag(&p->flags[0], SIP_NAT_FORCE_RPORT);
-			}
-			if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
-				ast_set_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
-			}
-		} else {
-			p->natdetected = 0;
-			if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
-				ast_clear_flag(&p->flags[0], SIP_NAT_FORCE_RPORT);
-			}
-			if (ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
-				ast_clear_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
-			}
-		}
+		check_for_nat(&tmp, p);
 
 		if (sip_debug_test_pvt(p)) {
 			ast_verbose("Sending to %s (%s)\n",
@@ -18304,13 +18335,10 @@
 	 *  are set on the peer.  So we check for that here and set the peer's
 	 *  address accordingly.
 	 */
+	set_peer_nat(p, peer);
+
 	if (p->natdetected && ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
-		ast_set_flag(&peer->flags[0], SIP_NAT_FORCE_RPORT);
 		ast_sockaddr_copy(&peer->addr, &p->recv);
-	}
-
-	if (p->natdetected && ast_test_flag(&peer->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
-		ast_set_flag(&peer->flags[1], SIP_PAGE2_SYMMETRICRTP);
 	}
 
 	if (!ast_apply_acl(peer->acl, addr, "SIP Peer ACL: ")) {
@@ -30011,6 +30039,22 @@
 		ast_string_field_set(p, peername, ext);
 	/* Recalculate our side, and recalculate Call ID */
 	ast_sip_ouraddrfor(&p->sa, &p->ourip, p);
+	/* When chan_sip is first loaded, we may have a peer entry but it hasn't re-registered yet.
+	   If the peer hasn't re-registered, we have not checked for NAT yet.  With the new
+	   auto_* settings, we need to check for NAT so we do not have one-way audio. */
+	check_for_nat(&p->ourip, p);
+	set_peer_nat(p, p->relatedpeer);
+
+	if (p->natdetected && ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_RPORT)) {
+		ast_copy_flags(&p->flags[0], &p->relatedpeer->flags[0], SIP_NAT_FORCE_RPORT);
+	}
+
+	if (p->natdetected && ast_test_flag(&p->flags[2], SIP_PAGE3_NAT_AUTO_COMEDIA)) {
+		ast_copy_flags(&p->flags[1], &p->relatedpeer->flags[1], SIP_PAGE2_SYMMETRICRTP);
+	}
+
+	do_setnat(p);
+
 	build_via(p);
 
 	/* Change the dialog callid. */

Modified: team/kmoore/stasis-bridging-channel_events/include/asterisk/bridging.h
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/stasis-bridging-channel_events/include/asterisk/bridging.h?view=diff&rev=385525&r1=385524&r2=385525
==============================================================================
--- team/kmoore/stasis-bridging-channel_events/include/asterisk/bridging.h (original)
+++ team/kmoore/stasis-bridging-channel_events/include/asterisk/bridging.h Fri Apr 12 16:17:27 2013
@@ -254,6 +254,19 @@
 typedef void (*ast_bridge_destructor_fn)(struct ast_bridge *self);
 
 /*!
+ * \brief The bridge is being dissolved.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \details
+ * The bridge is being dissolved.  Remove any external
+ * references to the bridge so it can be destroyed.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_dissolving_fn)(struct ast_bridge *self);
+
+/*!
  * \brief Can this channel be pushed into the bridge.
  *
  * \param self Bridge to operate upon.
@@ -330,6 +343,8 @@
 	const char *name;
 	/*! Destroy the bridge. */
 	ast_bridge_destructor_fn destroy;
+	/*! The bridge is being dissolved.  Remove any references to the bridge. */
+	ast_bridge_dissolving_fn dissolving;
 	/*! TRUE if can push the bridge channel into the bridge. */
 	ast_bridge_can_push_channel_fn can_push;
 	/*! Push the bridge channel into the bridge. */
@@ -387,6 +402,37 @@
 };
 
 /*!
+ * \brief Register the new bridge with the system.
+ * \since 12.0.0
+ *
+ * \param bridge What to register. (Tolerates a NULL pointer)
+ *
+ * \code
+ * struct ast_bridge *ast_bridge_basic_new(uint32_t capabilities, int flags, uint32 dtmf_features)
+ * {
+ *     void *bridge;
+ *
+ *     bridge = ast_bridge_alloc(sizeof(struct ast_bridge_basic), &ast_bridge_basic_v_table);
+ *     bridge = ast_bridge_base_init(bridge, capabilities, flags);
+ *     bridge = ast_bridge_basic_init(bridge, dtmf_features);
+ *     bridge = ast_bridge_register(bridge);
+ *     return bridge;
+ * }
+ * \endcode
+ *
+ * \note This must be done after a bridge constructor has
+ * completed setting up the new bridge but before it returns.
+ *
+ * \note After a bridge is registered, the bridge must be
+ * explicitly destroyed by ast_bridge_destroy() to get rid of
+ * the bridge.
+ *
+ * \retval bridge on success.
+ * \retval NULL on error.
+ */
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge);
+
+/*!
  * \internal
  * \brief Allocate the bridge class object memory.
  * \since 12.0.0
@@ -405,7 +451,7 @@
 /*!
  * \brief Initialize the base class of the bridge.
  *
- * \param self Bridge to operate upon. (Tollerates a NULL pointer)
+ * \param self Bridge to operate upon. (Tolerates a NULL pointer)
  * \param capabilities The capabilities that we require to be used on the bridge
  * \param flags Flags that will alter the behavior of the bridge
  *

Modified: team/kmoore/stasis-bridging-channel_events/include/asterisk/manager.h
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/stasis-bridging-channel_events/include/asterisk/manager.h?view=diff&rev=385525&r1=385524&r2=385525
==============================================================================
--- team/kmoore/stasis-bridging-channel_events/include/asterisk/manager.h (original)
+++ team/kmoore/stasis-bridging-channel_events/include/asterisk/manager.h Fri Apr 12 16:17:27 2013
@@ -316,6 +316,37 @@
  */
 struct ast_datastore *astman_datastore_find(struct mansession *s, const struct ast_datastore_info *info, const char *uid);
 
+/*! \brief Struct representing a snapshot of channel state */
+struct ast_channel_snapshot;
+
+/*!
+ * \brief Generate the AMI message body from a channel snapshot
+ * \since 12
+ *
+ * \param snapshot the channel snapshot for which to generate an AMI message
+ *                 body
+ * \param suffix the suffix to append to the channel fields
+ *
+ * \retval NULL on error
+ * \retval ast_str* on success (must be ast_freed by caller)
+ */
+struct ast_str *ast_manager_build_channel_state_string_suffix(
+		const struct ast_channel_snapshot *snapshot,
+		const char *suffix);
+
+/*!
+ * \brief Generate the AMI message body from a channel snapshot
+ * \since 12
+ *
+ * \param snapshot the channel snapshot for which to generate an AMI message
+ *                 body
+ *
+ * \retval NULL on error
+ * \retval ast_str* on success (must be ast_freed by caller)
+ */
+struct ast_str *ast_manager_build_channel_state_string(
+		const struct ast_channel_snapshot *snapshot);
+
 /*! \brief Struct containing info for an AMI event to send out. */
 struct ast_manager_event_blob {
 	int event_flags;		/*!< Flags the event should be raised with. */

Modified: team/kmoore/stasis-bridging-channel_events/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/stasis-bridging-channel_events/main/bridging.c?view=diff&rev=385525&r1=385524&r2=385525
==============================================================================
--- team/kmoore/stasis-bridging-channel_events/main/bridging.c (original)
+++ team/kmoore/stasis-bridging-channel_events/main/bridging.c Fri Apr 12 16:17:27 2013
@@ -56,6 +56,10 @@
 #include "asterisk/stringfields.h"
 #include "asterisk/musiconhold.h"
 #include "asterisk/features.h"
+#include "asterisk/cli.h"
+
+/*! All bridges container. */
+static struct ao2_container *bridges;
 
 static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
 
@@ -398,6 +402,82 @@
 
 /*!
  * \internal
+ * \brief Dissolve the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Bridge to eject all channels
+ *
+ * \details
+ * Force out all channels that are not already going out of the
+ * bridge.  Any new channels joining will leave immediately.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_dissolve(struct ast_bridge *bridge)
+{
+	struct ast_bridge_channel *bridge_channel;
+
+	if (bridge->dissolved) {
+		return;
+	}
+	bridge->dissolved = 1;
+
+	ast_debug(1, "Bridge %s: dissolving bridge\n", bridge->uniqueid);
+
+/* BUGBUG need a cause code on the bridge for the later ejected channels. */
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+	}
+	bridge->v_table->dissolving(bridge);
+}
+
+/*!
+ * \internal
+ * \brief Check if a bridge should dissolve and do it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel causing the check.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_dissolve_check(struct ast_bridge_channel *bridge_channel)
+{
+	struct ast_bridge *bridge = bridge_channel->bridge;
+
+	if (bridge->dissolved) {
+		return;
+	}
+
+	if (!bridge->num_channels
+		&& ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) {
+		/* Last channel leaving the bridge turns off the lights. */
+		bridge_dissolve(bridge);
+		return;
+	}
+
+	switch (bridge_channel->state) {
+	case AST_BRIDGE_CHANNEL_STATE_END:
+		/* Do we need to dissolve the bridge because this channel hung up? */
+		if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP)
+			|| (bridge_channel->features
+				&& bridge_channel->features->usable
+				&& ast_test_flag(&bridge_channel->features->feature_flags,
+					AST_BRIDGE_FLAG_DISSOLVE_HANGUP))) {
+			bridge_dissolve(bridge);
+			return;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/*!
+ * \internal
  * \brief Pull the bridge channel out of its current bridge.
  * \since 12.0.0
  *
@@ -439,6 +519,8 @@
 	AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
 	bridge->v_table->pull(bridge, bridge_channel);
 
+	bridge_dissolve_check(bridge_channel);
+
 	bridge->reconfigured = 1;
 	ast_bridge_publish_leave(bridge, bridge_channel->chan);
 }
@@ -513,53 +595,6 @@
 
 	bridge->reconfigured = 1;
 	ast_bridge_publish_enter(bridge, bridge_channel->chan);
-}
-
-/*!
- * \internal
- * \brief Force out all channels that are not already going out of the bridge.
- * \since 12.0.0
- *
- * \param bridge Bridge to eject all channels
- *
- * \note On entry, bridge is already locked.
- *
- * \return Nothing
- */
-static void bridge_force_out_all(struct ast_bridge *bridge)
-{
-	struct ast_bridge_channel *bridge_channel;
-
-	bridge->dissolved = 1;
-
-/* BUGBUG need a cause code on the bridge for the later ejected channels. */
-	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-	}
-}
-
-/*!
- * \internal
- * \brief Check if a bridge should dissolve and then do it.
- * \since 12.0.0
- *
- * \param bridge_channel Channel causing the check.
- *
- * \note On entry, bridge_channel->bridge is already locked.
- *
- * \return Nothing
- */
-static void bridge_check_dissolve(struct ast_bridge_channel *bridge_channel)
-{
-	if (!ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP)
-		&& (!bridge_channel->features
-			|| !bridge_channel->features->usable
-			|| !ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP))) {
-		return;
-	}
-
-	ast_debug(1, "Bridge %s: dissolving bridge\n", bridge_channel->bridge->uniqueid);
-	bridge_force_out_all(bridge_channel->bridge);
 }
 
 /*! \brief Internal function to handle DTMF from a channel */
@@ -1134,13 +1169,13 @@
 	struct ast_bridge *bridge = obj;
 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 
+	ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
+		bridge->uniqueid, bridge->v_table->name);
+
 	msg = stasis_cache_clear_create(ast_bridge_snapshot_type(), bridge->uniqueid);
 	if (msg) {
 		stasis_publish(ast_bridge_topic(bridge), msg);
 	}
-
-	ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
-		bridge->uniqueid, bridge->v_table->name);
 
 	/* Do any pending actions in the context of destruction. */
 	ast_bridge_lock(bridge);
@@ -1172,6 +1207,18 @@
 	cleanup_video_mode(bridge);
 }
 
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge)
+{
+	if (bridge) {
+		ast_bridge_publish_state(bridge);
+		if (!ao2_link(bridges, bridge)) {
+			ast_bridge_destroy(bridge);
+			bridge = NULL;
+		}
+	}
+	return bridge;
+}
+
 struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table)
 {
 	struct ast_bridge *bridge;
@@ -1180,6 +1227,7 @@
 	if (!v_table
 		|| !v_table->name
 		|| !v_table->destroy
+		|| !v_table->dissolving
 		|| !v_table->can_push
 		|| !v_table->push
 		|| !v_table->pull
@@ -1263,6 +1311,20 @@
 
 /*!
  * \internal
+ * \brief The bridge is being dissolved.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \return Nothing
+ */
+static void bridge_base_dissolving(struct ast_bridge *self)
+{
+	ao2_unlink(bridges, self);
+}
+
+/*!
+ * \internal
  * \brief ast_bridge base can_push method.
  * \since 12.0.0
  *
@@ -1337,6 +1399,7 @@
 struct ast_bridge_methods ast_bridge_base_v_table = {
 	.name = "base",
 	.destroy = bridge_base_destroy,
+	.dissolving = bridge_base_dissolving,
 	.can_push = bridge_base_can_push,
 	.push = bridge_base_push,
 	.pull = bridge_base_pull,
@@ -1349,9 +1412,7 @@
 
 	bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_base_v_table);
 	bridge = ast_bridge_base_init(bridge, capabilities, flags);
-	if (bridge) {
-		ast_bridge_publish_state(bridge);
-	}
+	bridge = ast_bridge_register(bridge);
 	return bridge;
 }
 
@@ -1372,7 +1433,7 @@
 {
 	ast_debug(1, "Bridge %s: telling all channels to leave the party\n", bridge->uniqueid);
 	ast_bridge_lock(bridge);
-	bridge_force_out_all(bridge);
+	bridge_dissolve(bridge);
 	ast_bridge_unlock(bridge);
 
 	ao2_ref(bridge, -1);
@@ -1647,8 +1708,8 @@
 	bridge->reconfigured = 0;
 	if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_SMART)
 		&& smart_bridge_operation(bridge)) {
-		/* Smart bridge failed.  Dissolve the bridge. */
-		bridge_force_out_all(bridge);
+		/* Smart bridge failed. */
+		bridge_dissolve(bridge);
 		return;
 	}
 	bridge_complete_join(bridge);
@@ -2314,15 +2375,6 @@
 	bridge_channel_pull(bridge_channel);
 	bridge_reconfigured(bridge_channel->bridge);
 
-	/* See if we need to dissolve the bridge itself if they hung up */
-	switch (bridge_channel->state) {
-	case AST_BRIDGE_CHANNEL_STATE_END:
-		bridge_check_dissolve(bridge_channel);
-		break;
-	default:
-		break;
-	}
-
 	ast_bridge_unlock(bridge_channel->bridge);
 
 	/* Indicate a source change since this channel is leaving the bridge system. */
@@ -4059,6 +4111,446 @@
 
 /*!
  * \internal
+ * \brief Bridge ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct ast_bridge *bridge_left = obj_left;
+	const struct ast_bridge *bridge_right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_key = bridge_right->uniqueid;
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcmp(bridge_left->uniqueid, right_key);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncmp(bridge_left->uniqueid, right_key, strlen(right_key));
+		break;
+	}
+	return cmp;
+}
+
+struct bridge_complete {
+	/*! Nth match to return. */
+	int state;
+	/*! Which match currently on. */
+	int which;
+};
+
+static int complete_bridge_search(void *obj, void *arg, void *data, int flags)
+{
+	struct bridge_complete *search = data;
+
+	if (++search->which > search->state) {
+		return CMP_MATCH;
+	}
+	return 0;
+}
+
+static char *complete_bridge(const char *word, int state)
+{
+	char *ret;
+	struct ast_bridge *bridge;
+	struct bridge_complete search = {
+		.state = state,
+		};
+
+	bridge = ao2_callback_data(bridges, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+		complete_bridge_search, (char *) word, &search);
+	if (!bridge) {
+		return NULL;
+	}
+	ret = ast_strdup(bridge->uniqueid);
+	ao2_ref(bridge, -1);
+	return ret;
+}
+
+static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-36s %5s %-15s %s\n"
+#define FORMAT_ROW "%-36s %5u %-15s %s\n"
+
+	struct ao2_iterator iter;
+	struct ast_bridge *bridge;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge show all";
+		e->usage =
+			"Usage: bridge show all\n"
+			"       List all bridges\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+	ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology");
+	iter = ao2_iterator_init(bridges, 0);
+	for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) {
+		ast_bridge_lock(bridge);
+		ast_cli(a->fd, FORMAT_ROW,
+			bridge->uniqueid,
+			bridge->num_channels,
+			bridge->v_table ? bridge->v_table->name : "<unknown>",
+			bridge->technology ? bridge->technology->name : "<unknown>");
+		ast_bridge_unlock(bridge);
+	}
+	ao2_iterator_destroy(&iter);
+	return CLI_SUCCESS;
+
+#undef FORMAT_HDR
+#undef FORMAT_ROW
+}
+
+static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_bridge *bridge;
+	struct ast_bridge_channel *bridge_channel;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge show";
+		e->usage =
+			"Usage: bridge show <bridge-id>\n"
+			"       Show information about the <bridge-id> bridge\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_bridge(a->word, a->n);
+		}
+		return NULL;
+	}
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+	if (!bridge) {
+		ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+		return CLI_SUCCESS;
+	}
+
+	ast_bridge_lock(bridge);
+	ast_cli(a->fd, "Id: %s\n", bridge->uniqueid);
+	ast_cli(a->fd, "Type: %s\n", bridge->v_table ? bridge->v_table->name : "<unknown>");
+	ast_cli(a->fd, "Technology: %s\n",
+		bridge->technology ? bridge->technology->name : "<unknown>");
+	ast_cli(a->fd, "Num-Channels: %u\n", bridge->num_channels);
+	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+		ast_cli(a->fd, "Channel: %s\n", ast_channel_name(bridge_channel->chan));
+	}
+	ast_bridge_unlock(bridge);
+	ao2_ref(bridge, -1);
+
+	return CLI_SUCCESS;
+}
+
+static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_bridge *bridge;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge destroy";
+		e->usage =
+			"Usage: bridge destroy <bridge-id>\n"
+			"       Destroy the <bridge-id> bridge\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_bridge(a->word, a->n);
+		}
+		return NULL;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+	if (!bridge) {
+		ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+		return CLI_SUCCESS;
+	}
+
+	ast_cli(a->fd, "Destroying bridge '%s'\n", a->argv[2]);
+	ast_bridge_destroy(bridge);
+
+	return CLI_SUCCESS;
+}
+
+static char *complete_bridge_participant(const char *bridge_name, const char *line, const char *word, int pos, int state)
+{
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	struct ast_bridge_channel *bridge_channel;
+	int which;
+	int wordlen;
+
+	bridge = ao2_find(bridges, bridge_name, OBJ_KEY);
+	if (!bridge) {
+		return NULL;
+	}
+
+	{
+		SCOPED_LOCK(bridge_lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+
+		which = 0;
+		wordlen = strlen(word);
+		AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+			if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen)
+				&& ++which > state) {
+				return ast_strdup(ast_channel_name(bridge_channel->chan));
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge kick";
+		e->usage =
+			"Usage: bridge kick <bridge-id> <channel-name>\n"
+			"       Kick the <channel-name> channel out of the <bridge-id> bridge\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 2) {
+			return complete_bridge(a->word, a->n);
+		}
+		if (a->pos == 3) {
+			return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
+		}
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+	if (!bridge) {
+		ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+		return CLI_SUCCESS;
+	}
+
+	chan = ast_channel_get_by_name_prefix(a->argv[3], strlen(a->argv[3]));
+	if (!chan) {
+		ast_cli(a->fd, "Channel '%s' not found\n", a->argv[3]);
+		return CLI_SUCCESS;
+	}
+
+/*
+ * BUGBUG the CLI kick needs to get the bridge to decide if it should dissolve.
+ *
+ * Likely the best way to do this is to add a kick method.  The
+ * basic bridge class can then decide to dissolve the bridge if
+ * one of two channels is kicked.
+ *
+ * SIP/foo -- Local;1==Local;2 -- .... -- Local;1==Local;2 -- SIP/bar
+ * Kick a ;1 channel and the chain toward SIP/foo goes away.
+ * Kick a ;2 channel and the chain toward SIP/bar goes away.
+ *
+ * This can leave a local channel chain between the kicked ;1
+ * and ;2 channels that are orphaned until you manually request
+ * one of those channels to hangup or request the bridge to
+ * dissolve.
+ */
+	ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
+		ast_channel_name(chan), a->argv[2]);
+	ast_bridge_remove(bridge, chan);
+
+	return CLI_SUCCESS;
+}
+
+/*! Bridge technology capabilities to string. */
+static const char *tech_capability2str(uint32_t capabilities)
+{
+	const char *type;
+
+	if (capabilities & AST_BRIDGE_CAPABILITY_HOLDING) {
+		type = "Holding";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_EARLY) {
+		type = "Early";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_NATIVE) {
+		type = "Native";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
+		type = "1to1Mix";
+	} else if (capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+		type = "MultiMix";
+	} else {
+		type = "<Unknown>";
+	}
+	return type;
+}
+
+/*! Bridge technology priority preference to string. */
+static const char *tech_preference2str(enum ast_bridge_preference preference)
+{
+	const char *priority;
+
+	priority = "<Unknown>";
+	switch (preference) {
+	case AST_BRIDGE_PREFERENCE_HIGH:
+		priority = "High";
+		break;
+	case AST_BRIDGE_PREFERENCE_MEDIUM:
+		priority = "Medium";
+		break;
+	case AST_BRIDGE_PREFERENCE_LOW:
+		priority = "Low";
+		break;
+	}
+	return priority;
+}
+
+static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT "%-20s %-20s %-10s %s\n"
+
+	struct ast_bridge_technology *cur;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge technology show";
+		e->usage =
+			"Usage: bridge technology show\n"
+			"       List registered bridge technologies\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_cli(a->fd, FORMAT, "Name", "Type", "Priority", "Suspended");
+	AST_RWLIST_RDLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+		const char *type;
+		const char *priority;
+
+		/* Decode type for display */
+		type = tech_capability2str(cur->capabilities);
+
+		/* Decode priority preference for display */
+		priority = tech_preference2str(cur->preference);
+
+		ast_cli(a->fd, FORMAT, cur->name, type, priority, AST_CLI_YESNO(cur->suspended));
+	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
+	return CLI_SUCCESS;
+
+#undef FORMAT
+}
+
+static char *complete_bridge_technology(const char *word, int state)
+{
+	struct ast_bridge_technology *cur;
+	char *res;
+	int which;
+	int wordlen;
+
+	which = 0;
+	wordlen = strlen(word);
+	AST_RWLIST_RDLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+		if (!strncasecmp(cur->name, word, wordlen) && ++which > state) {
+			res = ast_strdup(cur->name);
+			AST_RWLIST_UNLOCK(&bridge_technologies);
+			return res;
+		}
+	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
+	return NULL;
+}
+
+static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_bridge_technology *cur;
+	int suspend;
+	int successful;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "bridge technology {suspend|unsuspend}";
+		e->usage =
+			"Usage: bridge technology {suspend|unsuspend} <technology-name>\n"
+			"       Suspend or unsuspend a bridge technology.\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 3) {
+			return complete_bridge_technology(a->word, a->n);
+		}
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	suspend = !strcasecmp(a->argv[2], "suspend");
+	successful = 0;
+	AST_RWLIST_WRLOCK(&bridge_technologies);
+	AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+		if (!strcasecmp(cur->name, a->argv[3])) {
+			successful = 1;
+			if (suspend) {
+				ast_bridge_technology_suspend(cur);
+			} else {
+				ast_bridge_technology_unsuspend(cur);
+			}
+			break;
+		}
+	}
+	AST_RWLIST_UNLOCK(&bridge_technologies);
+
+	if (successful) {
+		if (suspend) {
+			ast_cli(a->fd, "Suspended bridge technology '%s'\n", a->argv[3]);
+		} else {
+			ast_cli(a->fd, "Unsuspended bridge technology '%s'\n", a->argv[3]);
+		}
+	} else {
+		ast_cli(a->fd, "Bridge technology '%s' not found\n", a->argv[3]);
+	}
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry bridge_cli[] = {
+	AST_CLI_DEFINE(handle_bridge_show_all, "List all bridges"),
+	AST_CLI_DEFINE(handle_bridge_show_specific, "Show information about a bridge"),
+	AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"),
+	AST_CLI_DEFINE(handle_bridge_kick_channel, "Kick a channel from a bridge"),
+	AST_CLI_DEFINE(handle_bridge_technology_show, "List registered bridge technologies"),
+	AST_CLI_DEFINE(handle_bridge_technology_suspend, "Suspend/unsuspend a bridge technology"),
+};
+
+/*!
+ * \internal
  * \brief Shutdown the bridging system.
  * \since 12.0.0
  *
@@ -4066,6 +4558,9 @@
  */
 static void bridge_shutdown(void)
 {
+	ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+	ao2_cleanup(bridges);
+	bridges = NULL;
 	ao2_cleanup(bridge_manager);
 	bridge_manager = NULL;
 	ast_stasis_bridging_shutdown();
@@ -4080,8 +4575,19 @@
 
 	bridge_manager = bridge_manager_create();
 	if (!bridge_manager) {
+		bridge_shutdown();
 		return -1;
 	}
+
+	bridges = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_sort_cmp, NULL);
+	if (!bridges) {
+		bridge_shutdown();
+		return -1;
+	}
+
+/* BUGBUG need AMI action equivalents to the CLI commands. */
+	ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
 
 	ast_register_atexit(bridge_shutdown);
 	return 0;

Modified: team/kmoore/stasis-bridging-channel_events/main/features.c
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/stasis-bridging-channel_events/main/features.c?view=diff&rev=385525&r1=385524&r2=385525
==============================================================================
--- team/kmoore/stasis-bridging-channel_events/main/features.c (original)
+++ team/kmoore/stasis-bridging-channel_events/main/features.c Fri Apr 12 16:17:27 2013
@@ -4576,7 +4576,7 @@
 /* BUGBUG need to create the basic bridge class that will manage the DTMF feature hooks. */
 	bridge = ast_bridge_base_new(
 		AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_MULTIMIX,
-		AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_SMART);
+		AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_DISSOLVE_EMPTY | AST_BRIDGE_FLAG_SMART);
 	if (!bridge) {
 		ast_bridge_features_destroy(peer_features);
 		ast_bridge_features_cleanup(&chan_features);

Modified: team/kmoore/stasis-bridging-channel_events/main/manager_channels.c
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/stasis-bridging-channel_events/main/manager_channels.c?view=diff&rev=385525&r1=385524&r2=385525
==============================================================================
--- team/kmoore/stasis-bridging-channel_events/main/manager_channels.c (original)
+++ team/kmoore/stasis-bridging-channel_events/main/manager_channels.c Fri Apr 12 16:17:27 2013
@@ -232,18 +232,7 @@
 	</managerEvent>
  ***/
 
-/*!
- * \brief Generate the AMI message body from a channel snapshot
- * \internal
- *
- * \param snapshot the channel snapshot for which to generate an AMI message
- *                 body
- * \param suffix the suffix to append to the channel fields
- *
- * \retval NULL on error
- * \retval ast_str* on success (must be ast_freed by caller)
- */
-static struct ast_str *manager_build_channel_state_string_suffix(
+struct ast_str *ast_manager_build_channel_state_string_suffix(
 		const struct ast_channel_snapshot *snapshot,
 		const char *suffix)
 {
@@ -294,20 +283,10 @@
 	return out;
 }
 
-/*!
- * \brief Generate the AMI message body from a channel snapshot
- * \internal
- *
- * \param snapshot the channel snapshot for which to generate an AMI message
- *                 body
- *
- * \retval NULL on error
- * \retval ast_str* on success (must be ast_freed by caller)
- */
-static struct ast_str *manager_build_channel_state_string(
+struct ast_str *ast_manager_build_channel_state_string(
 		const struct ast_channel_snapshot *snapshot)
 {
-	return manager_build_channel_state_string_suffix(snapshot, "");
+	return ast_manager_build_channel_state_string_suffix(snapshot, "");
 }
 
 /*! \brief Typedef for callbacks that get called on channel snapshot updates */
@@ -488,7 +467,7 @@
 		/* If we haven't already, build the channel event string */
 		if (!channel_event_string) {
 			channel_event_string =
-				manager_build_channel_state_string(new_snapshot);

[... 48 lines stripped ...]



More information about the asterisk-commits mailing list