Merge "res_calendar: Specialized calendars depend on symbols of general calendar."
[asterisk/asterisk.git] / main / bridge.c
index ddaab0c..4f79852 100644 (file)
        <support_level>core</support_level>
  ***/
 
-#include "asterisk.h"
+/*** DOCUMENTATION
+       <manager name="BridgeTechnologyList" language="en_US">
+               <synopsis>
+                       List available bridging technologies and their statuses.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+               </syntax>
+               <description>
+                       <para>Returns detailed information about the available bridging technologies.</para>
+               </description>
+               <see-also>
+                       <ref type="manager">BridgeTechnologySuspend</ref>
+                       <ref type="manager">BridgeTechnologyUnsuspend</ref>
+               </see-also>
+       </manager>
+       <manager name="BridgeTechnologySuspend" language="en_US">
+               <synopsis>
+                       Suspend a bridging technology.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="BridgeTechnology" required="true">
+                               <para>The name of the bridging technology to suspend.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Marks a bridging technology as suspended, which prevents subsequently created bridges from using it.</para>
+               </description>
+               <see-also>
+                       <ref type="manager">BridgeTechnologySuspend</ref>
+                       <ref type="manager">BridgeTechnologyUnsuspend</ref>
+               </see-also>
+       </manager>
+       <manager name="BridgeTechnologyUnsuspend" language="en_US">
+               <synopsis>
+                       Unsuspend a bridging technology.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="BridgeTechnology" required="true">
+                               <para>The name of the bridging technology to unsuspend.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Clears a previously suspended bridging technology, which allows subsequently created bridges to use it.</para>
+               </description>
+               <see-also>
+                       <ref type="manager">BridgeTechnologyList</ref>
+                       <ref type="manager">BridgeTechnologySuspend</ref>
+               </see-also>
+       </manager>
+***/
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk.h"
 
 #include "asterisk/logger.h"
 #include "asterisk/channel.h"
@@ -80,8 +132,13 @@ static unsigned int optimization_id;
 /* Grow rate of bridge array of channels */
 #define BRIDGE_ARRAY_GROW 32
 
+/* Variable name - stores peer information about the most recent blind transfer */
+#define BLINDTRANSFER "BLINDTRANSFER"
+
+/* Variable name - stores peer information about the most recent attended transfer */
+#define ATTENDEDTRANSFER "ATTENDEDTRANSFER"
+
 static void cleanup_video_mode(struct ast_bridge *bridge);
-static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
 
 /*! Default DTMF keys for built in features */
 static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_STRING];
@@ -177,8 +234,21 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
        /* Copy module pointer so reference counting can keep the module from unloading */
        technology->mod = module;
 
-       /* Insert our new bridge technology into the list and print out a pretty message */
-       AST_RWLIST_INSERT_TAIL(&bridge_technologies, technology, entry);
+       /* Find the correct position to insert the technology. */
+       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&bridge_technologies, current, entry) {
+               /* Put the highest preference tech's first in the list. */
+               if (technology->preference >= current->preference) {
+                       AST_RWLIST_INSERT_BEFORE_CURRENT(technology, entry);
+
+                       break;
+               }
+       }
+       AST_RWLIST_TRAVERSE_SAFE_END;
+
+       if (!current) {
+               /* Insert our new bridge technology to the end of the list. */
+               AST_RWLIST_INSERT_TAIL(&bridge_technologies, technology, entry);
+       }
 
        AST_RWLIST_UNLOCK(&bridge_technologies);
 
@@ -220,7 +290,7 @@ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
  */
 static void bridge_queue_action_nodup(struct ast_bridge *bridge, struct ast_frame *action)
 {
-       ast_debug(1, "Bridge %s: queueing action type:%d sub:%d\n",
+       ast_debug(1, "Bridge %s: queueing action type:%u sub:%d\n",
                bridge->uniqueid, action->frametype, action->subclass.integer);
 
        ast_bridge_lock(bridge);
@@ -367,23 +437,34 @@ static void bridge_reconfigured_connected_line_update(struct ast_bridge *bridge)
  */
 static void bridge_channel_complete_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
-       /* Make the channel compatible with the bridge */
-       bridge_make_compatible(bridge, bridge_channel);
-
        /* Tell the bridge technology we are joining so they set us up */
        ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
                bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
                bridge->technology->name);
        if (bridge->technology->join
                && bridge->technology->join(bridge, bridge_channel)) {
-               ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+               /* We cannot leave the channel partially in the bridge so we must kick it out */
+               ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology (Kicking it out)\n",
                        bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
                        bridge->technology->name);
                bridge_channel->just_joined = 1;
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
                return;
        }
 
        bridge_channel->just_joined = 0;
+
+       /*
+        * When a channel joins the bridge its streams need to be mapped to the bridge's
+        * media types vector. This way all streams map to the same media type index for
+        * a given channel.
+        */
+       if (bridge_channel->bridge->technology->stream_topology_changed) {
+               bridge_channel->bridge->technology->stream_topology_changed(
+                       bridge_channel->bridge, bridge_channel);
+       } else {
+               ast_bridge_channel_stream_map(bridge_channel);
+       }
 }
 
 /*!
@@ -413,6 +494,7 @@ static void bridge_complete_join(struct ast_bridge *bridge)
        }
 
        AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+               bridge_channel_queue_deferred_frames(bridge_channel);
                if (!bridge_channel->just_joined) {
                        continue;
                }
@@ -439,7 +521,7 @@ static struct ast_bridge_technology *find_best_technology(uint32_t capabilities,
                        continue;
                }
                if (best && current->preference <= best->preference) {
-                       ast_debug(1, "Bridge technology %s has less preference than %s (%d <= %d). Skipping.\n",
+                       ast_debug(1, "Bridge technology %s has less preference than %s (%u <= %u). Skipping.\n",
                                current->name, best->name, current->preference, best->preference);
                        continue;
                }
@@ -448,12 +530,17 @@ static struct ast_bridge_technology *find_best_technology(uint32_t capabilities,
                                current->name);
                        continue;
                }
+               if (!ast_module_running_ref(current->mod)) {
+                       ast_debug(1, "Bridge technology %s is not running, skipping.\n", current->name);
+                       continue;
+               }
+               if (best) {
+                       ast_module_unref(best->mod);
+               }
                best = current;
        }
 
        if (best) {
-               /* Increment it's module reference count if present so it does not get unloaded while in use */
-               ast_module_ref(best->mod);
                ast_debug(1, "Chose bridge technology %s\n", best->name);
        }
 
@@ -485,9 +572,11 @@ static void bridge_tech_deferred_destroy(struct ast_bridge *bridge, struct ast_f
        struct ast_bridge dummy_bridge = {
                .technology = deferred->tech,
                .tech_pvt = deferred->tech_pvt,
+               .creator = bridge->creator,
+               .name = bridge->name,
+               .uniqueid = bridge->uniqueid,
                };
 
-       ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
        ast_debug(1, "Bridge %s: calling %s technology destructor (deferred, dummy)\n",
                dummy_bridge.uniqueid, dummy_bridge.technology->name);
        dummy_bridge.technology->destroy(&dummy_bridge);
@@ -565,7 +654,14 @@ static struct stasis_message *create_bridge_snapshot_message(struct ast_bridge *
 {
        RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
 
+       if (!ast_bridge_snapshot_type()) {
+               return NULL;
+       }
+
+       ast_bridge_lock(bridge);
        snapshot = ast_bridge_snapshot_create(bridge);
+       ast_bridge_unlock(bridge);
+
        if (!snapshot) {
                return NULL;
        }
@@ -624,20 +720,24 @@ static void destroy_bridge(void *obj)
                bridge->technology = NULL;
        }
 
-       if (bridge->callid) {
-               bridge->callid = ast_callid_unref(bridge->callid);
-       }
+       AST_VECTOR_FREE(&bridge->media_types);
+
+       bridge->callid = 0;
 
        cleanup_video_mode(bridge);
 
        stasis_cp_single_unsubscribe(bridge->topics);
+
+       ast_string_field_free_memory(bridge);
 }
 
 struct ast_bridge *bridge_register(struct ast_bridge *bridge)
 {
        if (bridge) {
                bridge->construction_completed = 1;
+               ast_bridge_lock(bridge);
                ast_bridge_publish_state(bridge);
+               ast_bridge_unlock(bridge);
                if (!ao2_link(bridges, bridge)) {
                        ast_bridge_destroy(bridge, 0);
                        bridge = NULL;
@@ -666,27 +766,51 @@ struct ast_bridge *bridge_alloc(size_t size, const struct ast_bridge_methods *v_
        }
 
        bridge = ao2_alloc(size, destroy_bridge);
-       if (bridge) {
-               bridge->v_table = v_table;
+       if (!bridge) {
+               return NULL;
        }
+
+       if (ast_string_field_init(bridge, 80)) {
+               ao2_cleanup(bridge);
+               return NULL;
+       }
+
+       bridge->v_table = v_table;
+
+       AST_VECTOR_INIT(&bridge->media_types, AST_MEDIA_TYPE_END);
+
        return bridge;
 }
 
-struct ast_bridge *bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags)
+struct ast_bridge *bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags, const char *creator, const char *name, const char *id)
 {
+       char uuid_hold[AST_UUID_STR_LEN];
+
        if (!self) {
                return NULL;
        }
 
-       ast_uuid_generate_str(self->uniqueid, sizeof(self->uniqueid));
+       if (!ast_strlen_zero(id)) {
+               ast_string_field_set(self, uniqueid, id);
+       } else {
+               ast_uuid_generate_str(uuid_hold, AST_UUID_STR_LEN);
+               ast_string_field_set(self, uniqueid, uuid_hold);
+       }
+       ast_string_field_set(self, creator, creator);
+       if (!ast_strlen_zero(creator)) {
+               ast_string_field_set(self, name, name);
+       }
+
        ast_set_flag(&self->feature_flags, flags);
        self->allowed_capabilities = capabilities;
 
-       if (bridge_topics_init(self) != 0) {
-               ast_log(LOG_WARNING, "Bridge %s: Could not initialize topics\n",
-                       self->uniqueid);
-               ao2_ref(self, -1);
-               return NULL;
+       if (!(flags & AST_BRIDGE_FLAG_INVISIBLE)) {
+               if (bridge_topics_init(self) != 0) {
+                       ast_log(LOG_WARNING, "Bridge %s: Could not initialize topics\n",
+                               self->uniqueid);
+                       ao2_ref(self, -1);
+                       return NULL;
+               }
        }
 
        /* Use our helper function to find the "best" bridge technology. */
@@ -716,9 +840,11 @@ struct ast_bridge *bridge_base_init(struct ast_bridge *self, uint32_t capabiliti
                return NULL;
        }
 
-       if (!ast_bridge_topic(self)) {
-               ao2_ref(self, -1);
-               return NULL;
+       if (!(flags & AST_BRIDGE_FLAG_INVISIBLE)) {
+               if (!ast_bridge_topic(self)) {
+                       ao2_ref(self, -1);
+                       return NULL;
+               }
        }
 
        return self;
@@ -823,6 +949,26 @@ static int bridge_base_get_merge_priority(struct ast_bridge *self)
        return 0;
 }
 
+/*!
+ * \internal
+ * \brief ast_bridge base push_peek method.
+ * \since 13.2.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_base_push_peek(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+       return 0;
+}
+
 struct ast_bridge_methods ast_bridge_base_v_table = {
        .name = "base",
        .destroy = bridge_base_destroy,
@@ -831,14 +977,15 @@ struct ast_bridge_methods ast_bridge_base_v_table = {
        .pull = bridge_base_pull,
        .notify_masquerade = bridge_base_notify_masquerade,
        .get_merge_priority = bridge_base_get_merge_priority,
+       .push_peek = bridge_base_push_peek,
 };
 
-struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags)
+struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags, const char *creator, const char *name, const char *id)
 {
        void *bridge;
 
        bridge = bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_base_v_table);
-       bridge = bridge_base_init(bridge, capabilities, flags);
+       bridge = bridge_base_init(bridge, capabilities, flags, creator, name, id);
        bridge = bridge_register(bridge);
        return bridge;
 }
@@ -855,68 +1002,6 @@ int ast_bridge_destroy(struct ast_bridge *bridge, int cause)
        return 0;
 }
 
-static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       struct ast_format read_format;
-       struct ast_format write_format;
-       struct ast_format best_format;
-       char codec_buf[512];
-
-       ast_format_copy(&read_format, ast_channel_readformat(bridge_channel->chan));
-       ast_format_copy(&write_format, ast_channel_writeformat(bridge_channel->chan));
-
-       /* Are the formats currently in use something this bridge can handle? */
-       if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) {
-               ast_best_codec(bridge->technology->format_capabilities, &best_format);
-
-               /* Read format is a no go... */
-               ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n",
-                       bridge->technology->name,
-                       ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
-                       ast_getformatname(&read_format));
-
-               /* Switch read format to the best one chosen */
-               if (ast_set_read_format(bridge_channel->chan, &best_format)) {
-                       ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n",
-                               ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-                       return -1;
-               }
-               ast_debug(1, "Bridge %s put channel %s into read format %s\n",
-                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
-                       ast_getformatname(&best_format));
-       } else {
-               ast_debug(1, "Bridge %s is happy that channel %s already has read format %s\n",
-                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
-                       ast_getformatname(&read_format));
-       }
-
-       if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &write_format)) {
-               ast_best_codec(bridge->technology->format_capabilities, &best_format);
-
-               /* Write format is a no go... */
-               ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n",
-                       bridge->technology->name,
-                       ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
-                       ast_getformatname(&write_format));
-
-               /* Switch write format to the best one chosen */
-               if (ast_set_write_format(bridge_channel->chan, &best_format)) {
-                       ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n",
-                               ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-                       return -1;
-               }
-               ast_debug(1, "Bridge %s put channel %s into write format %s\n",
-                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
-                       ast_getformatname(&best_format));
-       } else {
-               ast_debug(1, "Bridge %s is happy that channel %s already has write format %s\n",
-                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
-                       ast_getformatname(&write_format));
-       }
-
-       return 0;
-}
-
 /*!
  * \internal
  * \brief Perform the smart bridge operation.
@@ -943,6 +1028,9 @@ static int smart_bridge_operation(struct ast_bridge *bridge)
        struct ast_bridge dummy_bridge = {
                .technology = bridge->technology,
                .tech_pvt = bridge->tech_pvt,
+               .creator = bridge->creator,
+               .name = bridge->name,
+               .uniqueid = bridge->uniqueid,
        };
 
        if (bridge->dissolved) {
@@ -995,8 +1083,6 @@ static int smart_bridge_operation(struct ast_bridge *bridge)
                return 0;
        }
 
-       ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
-
        if (old_technology->destroy) {
                struct tech_deferred_destroy deferred_tech_destroy = {
                        .tech = dummy_bridge.technology,
@@ -1051,28 +1137,55 @@ static int smart_bridge_operation(struct ast_bridge *bridge)
                return -1;
        }
 
+       /* To ensure that things are sane for the old technology move the channels it
+        * expects to the dummy bridge
+        */
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&bridge->channels, bridge_channel, entry) {
+               if (bridge_channel->just_joined) {
+                       continue;
+               }
+               ast_debug(1, "Bridge %s: moving %p(%s) to dummy bridge temporarily\n",
+                       bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+               AST_LIST_REMOVE_CURRENT(entry);
+               AST_LIST_INSERT_TAIL(&dummy_bridge.channels, bridge_channel, entry);
+               dummy_bridge.num_channels++;
+               if (ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
+                       dummy_bridge.num_lonely++;
+               }
+               if (!bridge_channel->suspended) {
+                       dummy_bridge.num_active++;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+
+       /* Take all the channels out of the old technology */
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&dummy_bridge.channels, bridge_channel, entry) {
+               ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology (dummy)\n",
+                       dummy_bridge.uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                       old_technology->name);
+               if (old_technology->leave) {
+                       old_technology->leave(&dummy_bridge, bridge_channel);
+               }
+               AST_LIST_REMOVE_CURRENT(entry);
+               AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry);
+               dummy_bridge.num_channels--;
+               if (ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
+                       dummy_bridge.num_lonely--;
+               }
+               if (!bridge_channel->suspended) {
+                       dummy_bridge.num_active--;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+
        ast_debug(1, "Bridge %s: calling %s technology stop\n",
                dummy_bridge.uniqueid, old_technology->name);
        if (old_technology->stop) {
                old_technology->stop(&dummy_bridge);
        }
 
-       /*
-        * Move existing channels over to the new technology and
-        * complete joining any new channels to the bridge.
-        */
+       /* Add any new channels or re-add existing channels to the bridge. */
        AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-               if (!bridge_channel->just_joined) {
-                       /* Take existing channel from the old technology. */
-                       ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology (dummy)\n",
-                               dummy_bridge.uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
-                               old_technology->name);
-                       if (old_technology->leave) {
-                               old_technology->leave(&dummy_bridge, bridge_channel);
-                       }
-               }
-
-               /* Add any new channels or re-add an existing channel to the bridge. */
                bridge_channel_complete_join(bridge, bridge_channel);
        }
 
@@ -1149,10 +1262,12 @@ static void check_bridge_play_sounds(struct ast_bridge *bridge)
        }
 }
 
-static void update_bridge_vars_set(struct ast_channel *chan, const char *name, const char *pvtid)
+void ast_bridge_vars_set(struct ast_channel *chan, const char *name, const char *pvtid)
 {
+       ast_channel_stage_snapshot(chan);
        pbx_builtin_setvar_helper(chan, "BRIDGEPEER", name);
        pbx_builtin_setvar_helper(chan, "BRIDGEPVTCALLID", pvtid);
+       ast_channel_stage_snapshot_done(chan);
 }
 
 /*!
@@ -1187,12 +1302,12 @@ static void set_bridge_peer_vars_2party(struct ast_channel *c0, struct ast_chann
        ast_channel_unlock(c1);
 
        ast_channel_lock(c0);
-       update_bridge_vars_set(c0, c1_name, c1_pvtid);
+       ast_bridge_vars_set(c0, c1_name, c1_pvtid);
        UPDATE_BRIDGE_VARS_GET(c0, c0_name, c0_pvtid);
        ast_channel_unlock(c0);
 
        ast_channel_lock(c1);
-       update_bridge_vars_set(c1, c0_name, c0_pvtid);
+       ast_bridge_vars_set(c1, c0_name, c0_pvtid);
        ast_channel_unlock(c1);
 }
 
@@ -1293,7 +1408,7 @@ static void set_bridge_peer_vars_multiparty(struct ast_bridge *bridge)
                ++idx;
 
                ast_channel_lock(bridge_channel->chan);
-               update_bridge_vars_set(bridge_channel->chan, buf, NULL);
+               ast_bridge_vars_set(bridge_channel->chan, buf, NULL);
                ast_channel_unlock(bridge_channel->chan);
        }
 }
@@ -1315,7 +1430,7 @@ static void set_bridge_peer_vars_holding(struct ast_bridge *bridge)
 
        AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
                ast_channel_lock(bridge_channel->chan);
-               update_bridge_vars_set(bridge_channel->chan, NULL, NULL);
+               ast_bridge_vars_set(bridge_channel->chan, NULL, NULL);
                ast_channel_unlock(bridge_channel->chan);
        }
 }
@@ -1417,6 +1532,150 @@ void ast_bridge_notify_masquerade(struct ast_channel *chan)
        ao2_ref(bridge_channel, -1);
 }
 
+/*!
+ * \brief Internal bridge impart wait condition and associated conditional.
+ */
+struct bridge_channel_impart_cond {
+       AST_LIST_ENTRY(bridge_channel_impart_cond) node;
+       /*! Lock for the data structure */
+       ast_mutex_t lock;
+       /*! Wait condition */
+       ast_cond_t cond;
+       /*! Wait until done */
+       int done;
+};
+
+AST_LIST_HEAD_NOLOCK(bridge_channel_impart_ds_head, bridge_channel_impart_cond);
+
+/*!
+ * \internal
+ * \brief Signal imparting threads to wake up.
+ * \since 13.9.0
+ *
+ * \param ds_head List of imparting threads to wake up.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_impart_ds_head_signal(struct bridge_channel_impart_ds_head *ds_head)
+{
+       if (ds_head) {
+               struct bridge_channel_impart_cond *cond;
+
+               while ((cond = AST_LIST_REMOVE_HEAD(ds_head, node))) {
+                       ast_mutex_lock(&cond->lock);
+                       cond->done = 1;
+                       ast_cond_signal(&cond->cond);
+                       ast_mutex_unlock(&cond->lock);
+               }
+       }
+}
+
+static void bridge_channel_impart_ds_head_dtor(void *doomed)
+{
+       bridge_channel_impart_ds_head_signal(doomed);
+       ast_free(doomed);
+}
+
+/*!
+ * \internal
+ * \brief Fixup the bridge impart datastore.
+ * \since 13.9.0
+ *
+ * \param data Bridge impart datastore data to fixup from old_chan.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_impart_ds_head_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+       /*
+        * Signal any waiting impart threads.  The masquerade is going to kill
+        * old_chan and we don't need to be waiting on new_chan.
+        */
+       bridge_channel_impart_ds_head_signal(data);
+}
+
+static const struct ast_datastore_info bridge_channel_impart_ds_info = {
+       .type = "bridge-impart-ds",
+       .destroy = bridge_channel_impart_ds_head_dtor,
+       .chan_fixup = bridge_channel_impart_ds_head_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Add impart wait datastore conditional to channel.
+ * \since 13.9.0
+ *
+ * \param chan Channel to add the impart wait conditional.
+ * \param cond Imparting conditional to add.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int bridge_channel_impart_add(struct ast_channel *chan, struct bridge_channel_impart_cond *cond)
+{
+       struct ast_datastore *datastore;
+       struct bridge_channel_impart_ds_head *ds_head;
+
+       ast_channel_lock(chan);
+
+       datastore = ast_channel_datastore_find(chan, &bridge_channel_impart_ds_info, NULL);
+       if (!datastore) {
+               datastore = ast_datastore_alloc(&bridge_channel_impart_ds_info, NULL);
+               if (!datastore) {
+                       ast_channel_unlock(chan);
+                       return -1;
+               }
+               ds_head = ast_calloc(1, sizeof(*ds_head));
+               if (!ds_head) {
+                       ast_channel_unlock(chan);
+                       ast_datastore_free(datastore);
+                       return -1;
+               }
+               datastore->data = ds_head;
+               ast_channel_datastore_add(chan, datastore);
+       } else {
+               ds_head = datastore->data;
+               ast_assert(ds_head != NULL);
+       }
+
+       AST_LIST_INSERT_TAIL(ds_head, cond, node);
+
+       ast_channel_unlock(chan);
+       return 0;
+}
+
+void bridge_channel_impart_signal(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       ast_channel_lock(chan);
+       datastore = ast_channel_datastore_find(chan, &bridge_channel_impart_ds_info, NULL);
+       if (datastore) {
+               bridge_channel_impart_ds_head_signal(datastore->data);
+       }
+       ast_channel_unlock(chan);
+}
+
+/*!
+ * \internal
+ * \brief Block imparting channel thread until signaled.
+ * \since 13.9.0
+ *
+ * \param cond Imparting conditional to wait for.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_impart_wait(struct bridge_channel_impart_cond *cond)
+{
+       ast_mutex_lock(&cond->lock);
+       while (!cond->done) {
+               ast_cond_wait(&cond->cond, &cond->lock);
+       }
+       ast_mutex_unlock(&cond->lock);
+}
+
 /*
  * XXX ASTERISK-21271 make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back.
  *
@@ -1433,16 +1692,17 @@ int ast_bridge_join(struct ast_bridge *bridge,
        struct ast_channel *swap,
        struct ast_bridge_features *features,
        struct ast_bridge_tech_optimizations *tech_args,
-       int pass_reference)
+       enum ast_bridge_join_flags flags)
 {
        struct ast_bridge_channel *bridge_channel;
        int res = 0;
 
        bridge_channel = bridge_channel_internal_alloc(bridge);
-       if (pass_reference) {
+       if (flags & AST_BRIDGE_JOIN_PASS_REFERENCE) {
                ao2_ref(bridge, -1);
        }
        if (!bridge_channel) {
+               ao2_t_cleanup(swap, "Error exit: bridge_channel alloc failed");
                res = -1;
                goto join_exit;
        }
@@ -1450,6 +1710,7 @@ int ast_bridge_join(struct ast_bridge *bridge,
        ast_assert(features != NULL);
        if (!features) {
                ao2_ref(bridge_channel, -1);
+               ao2_t_cleanup(swap, "Error exit: features is NULL");
                res = -1;
                goto join_exit;
        }
@@ -1468,6 +1729,19 @@ int ast_bridge_join(struct ast_bridge *bridge,
        bridge_channel->chan = chan;
        bridge_channel->swap = swap;
        bridge_channel->features = features;
+       bridge_channel->inhibit_colp = !!(flags & AST_BRIDGE_JOIN_INHIBIT_JOIN_COLP);
+
+       /* allow subclass to peek at upcoming push operation */
+       if (bridge->v_table->push_peek && !res) {
+               struct ast_bridge_channel *bcswap = NULL;
+
+               ast_bridge_lock(bridge);
+               if (bridge_channel->swap) {
+                       bcswap = bridge_find_channel(bridge, bridge_channel->swap);
+               }
+               res = bridge->v_table->push_peek(bridge, bridge_channel, bcswap);
+               ast_bridge_unlock(bridge);
+       }
 
        if (!res) {
                res = bridge_channel_internal_join(bridge_channel);
@@ -1478,13 +1752,16 @@ int ast_bridge_join(struct ast_bridge *bridge,
        ast_channel_internal_bridge_channel_set(chan, NULL);
        ast_channel_unlock(chan);
        bridge_channel->chan = NULL;
+       /* If bridge_channel->swap is not NULL then the join failed. */
+       ao2_t_cleanup(bridge_channel->swap, "Bridge complete: join failed");
        bridge_channel->swap = NULL;
        bridge_channel->features = NULL;
 
        ao2_ref(bridge_channel, -1);
 
-join_exit:;
+join_exit:
        ast_bridge_run_after_callback(chan);
+       bridge_channel_impart_signal(chan);
        if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO)
                && !ast_bridge_setup_after_goto(chan)) {
                /* Claim the after bridge goto is an async goto destination. */
@@ -1499,19 +1776,28 @@ join_exit:;
 static void *bridge_channel_depart_thread(void *data)
 {
        struct ast_bridge_channel *bridge_channel = data;
+       int res = 0;
 
        if (bridge_channel->callid) {
                ast_callid_threadassoc_add(bridge_channel->callid);
        }
 
-       bridge_channel_internal_join(bridge_channel);
+       res = bridge_channel_internal_join(bridge_channel);
 
-       /* cleanup */
+       /*
+        * cleanup
+        *
+        * If bridge_channel->swap is not NULL then the join failed.
+        */
+       ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Departable impart join failed");
        bridge_channel->swap = NULL;
        ast_bridge_features_destroy(bridge_channel->features);
        bridge_channel->features = NULL;
 
-       ast_bridge_discard_after_callback(bridge_channel->chan, AST_BRIDGE_AFTER_CB_REASON_DEPART);
+       ast_bridge_discard_after_callback(bridge_channel->chan,
+               res ? AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED : AST_BRIDGE_AFTER_CB_REASON_DEPART);
+       /* If join failed there will be impart threads waiting. */
+       bridge_channel_impart_signal(bridge_channel->chan);
        ast_bridge_discard_after_goto(bridge_channel->chan);
 
        return NULL;
@@ -1535,6 +1821,8 @@ static void *bridge_channel_ind_thread(void *data)
        ast_channel_internal_bridge_channel_set(chan, NULL);
        ast_channel_unlock(chan);
        bridge_channel->chan = NULL;
+       /* If bridge_channel->swap is not NULL then the join failed. */
+       ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Independent impart join failed");
        bridge_channel->swap = NULL;
        ast_bridge_features_destroy(bridge_channel->features);
        bridge_channel->features = NULL;
@@ -1542,11 +1830,18 @@ static void *bridge_channel_ind_thread(void *data)
        ao2_ref(bridge_channel, -1);
 
        ast_bridge_run_after_callback(chan);
+       /* If join failed there will be impart threads waiting. */
+       bridge_channel_impart_signal(chan);
        ast_bridge_run_after_goto(chan);
        return NULL;
 }
 
-int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent)
+static int bridge_impart_internal(struct ast_bridge *bridge,
+       struct ast_channel *chan,
+       struct ast_channel *swap,
+       struct ast_bridge_features *features,
+       enum ast_bridge_impart_flags flags,
+       struct bridge_channel_impart_cond *cond)
 {
        int res = 0;
        struct ast_bridge_channel *bridge_channel;
@@ -1555,6 +1850,7 @@ int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struc
        if (ast_channel_pbx(chan)) {
                ast_log(AST_LOG_WARNING, "Channel %s has a PBX thread and cannot be imparted into bridge %s\n",
                        ast_channel_name(chan), bridge->uniqueid);
+               ast_bridge_features_destroy(features);
                return -1;
        }
 
@@ -1583,20 +1879,41 @@ int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struc
        }
        ast_channel_unlock(chan);
        bridge_channel->chan = chan;
-       bridge_channel->swap = swap;
+       bridge_channel->swap = ao2_t_bump(swap, "Setting up bridge impart");
        bridge_channel->features = features;
-       bridge_channel->depart_wait = independent ? 0 : 1;
+       bridge_channel->inhibit_colp = !!(flags & AST_BRIDGE_IMPART_INHIBIT_JOIN_COLP);
+       bridge_channel->depart_wait =
+               (flags & AST_BRIDGE_IMPART_CHAN_MASK) == AST_BRIDGE_IMPART_CHAN_DEPARTABLE;
        bridge_channel->callid = ast_read_threadstorage_callid();
 
+       /* allow subclass to peek at swap channel before it can hangup */
+       if (bridge->v_table->push_peek && !res) {
+               struct ast_bridge_channel *bcswap = NULL;
+
+               ast_bridge_lock(bridge);
+               if (bridge_channel->swap) {
+                       bcswap = bridge_find_channel(bridge, bridge_channel->swap);
+               }
+               res = bridge->v_table->push_peek(bridge, bridge_channel, bcswap);
+               ast_bridge_unlock(bridge);
+       }
+
        /* Actually create the thread that will handle the channel */
        if (!res) {
-               if (independent) {
+               res = bridge_channel_impart_add(chan, cond);
+       }
+       if (!res) {
+               if ((flags & AST_BRIDGE_IMPART_CHAN_MASK) == AST_BRIDGE_IMPART_CHAN_INDEPENDENT) {
                        res = ast_pthread_create_detached(&bridge_channel->thread, NULL,
                                bridge_channel_ind_thread, bridge_channel);
                } else {
                        res = ast_pthread_create(&bridge_channel->thread, NULL,
                                bridge_channel_depart_thread, bridge_channel);
                }
+
+               if (!res) {
+                       bridge_channel_impart_wait(cond);
+               }
        }
 
        if (res) {
@@ -1605,6 +1922,7 @@ int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struc
                ast_channel_internal_bridge_channel_set(chan, NULL);
                ast_channel_unlock(chan);
                bridge_channel->chan = NULL;
+               ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Impart failed");
                bridge_channel->swap = NULL;
                ast_bridge_features_destroy(bridge_channel->features);
                bridge_channel->features = NULL;
@@ -1616,6 +1934,32 @@ int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struc
        return 0;
 }
 
+int ast_bridge_impart(struct ast_bridge *bridge,
+       struct ast_channel *chan,
+       struct ast_channel *swap,
+       struct ast_bridge_features *features,
+       enum ast_bridge_impart_flags flags)
+{
+       struct bridge_channel_impart_cond cond = {
+               .done = 0,
+       };
+       int res;
+
+       ast_mutex_init(&cond.lock);
+       ast_cond_init(&cond.cond, NULL);
+
+       res = bridge_impart_internal(bridge, chan, swap, features, flags, &cond);
+       if (res) {
+               /* Impart failed.  Signal any other waiting impart threads */
+               bridge_channel_impart_signal(chan);
+       }
+
+       ast_cond_destroy(&cond.cond);
+       ast_mutex_destroy(&cond.lock);
+
+       return res;
+}
+
 int ast_bridge_depart(struct ast_channel *chan)
 {
        struct ast_bridge_channel *bridge_channel;
@@ -1639,8 +1983,8 @@ int ast_bridge_depart(struct ast_channel *chan)
        }
 
        /*
-        * We are claiming the reference held by the depart bridge
-        * channel thread.
+        * We are claiming the bridge_channel reference held by
+        * bridge_channel_depart_thread().
         */
 
        ast_bridge_channel_leave_bridge(bridge_channel,
@@ -1730,6 +2074,32 @@ static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_chann
        ao2_ref(old_bridge, -1);
 }
 
+static void bridge_channel_moving(struct ast_bridge_channel *bridge_channel, struct ast_bridge *src, struct ast_bridge *dst)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook;
+       struct ao2_iterator iter;
+
+       /* Run any moving hooks. */
+       iter = ao2_iterator_init(features->other_hooks, 0);
+       for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
+               int remove_me;
+               ast_bridge_move_indicate_callback move_cb;
+
+               if (hook->type != AST_BRIDGE_HOOK_TYPE_MOVE) {
+                       continue;
+               }
+               move_cb = (ast_bridge_move_indicate_callback) hook->callback;
+               remove_me = move_cb(bridge_channel, hook->hook_pvt, src, dst);
+               if (remove_me) {
+                       ast_debug(1, "Move detection hook %p is being removed from %p(%s)\n",
+                               hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+                       ao2_unlink(features->other_hooks, hook);
+               }
+       }
+       ao2_iterator_destroy(&iter);
+}
+
 void bridge_do_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick,
        unsigned int optimized)
 {
@@ -1778,10 +2148,14 @@ void bridge_do_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridg
                        continue;
                }
 
+               bridge_channel_moving(bridge_channel, bridge_channel->bridge, dst_bridge);
+
                /* Point to new bridge.*/
                bridge_channel_change_bridge(bridge_channel, dst_bridge);
 
                if (bridge_channel_internal_push(bridge_channel)) {
+                       ast_bridge_features_remove(bridge_channel->features,
+                               AST_BRIDGE_HOOK_REMOVE_ON_PULL);
                        ast_bridge_channel_leave_bridge(bridge_channel,
                                BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, bridge_channel->bridge->cause);
                }
@@ -2016,7 +2390,11 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
                /*
                 * The channel died as a result of being pulled.  Leave it
                 * pointing to the original bridge.
+                *
+                * Clear out the swap channel pointer.  A ref is not held
+                * by bridge_channel->swap at this point.
                 */
+               bridge_channel->swap = NULL;
                bridge_reconfigured(orig_bridge, 0);
                return -1;
        }
@@ -2025,16 +2403,21 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
        ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */
        bridge_channel_change_bridge(bridge_channel, dst_bridge);
 
-       if (bridge_channel_internal_push(bridge_channel)) {
+       bridge_channel_moving(bridge_channel, orig_bridge, dst_bridge);
+
+       if (bridge_channel_internal_push_full(bridge_channel, optimized)) {
                /* Try to put the channel back into the original bridge. */
+               ast_bridge_features_remove(bridge_channel->features,
+                       AST_BRIDGE_HOOK_REMOVE_ON_PULL);
                if (attempt_recovery && was_in_bridge) {
                        /* Point back to original bridge. */
                        bridge_channel_change_bridge(bridge_channel, orig_bridge);
 
                        if (bridge_channel_internal_push(bridge_channel)) {
+                               ast_bridge_features_remove(bridge_channel->features,
+                                       AST_BRIDGE_HOOK_REMOVE_ON_PULL);
                                ast_bridge_channel_leave_bridge(bridge_channel,
                                        BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, bridge_channel->bridge->cause);
-                               bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
                        }
                } else {
                        ast_bridge_channel_leave_bridge(bridge_channel,
@@ -2042,7 +2425,7 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
                        bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
                }
                res = -1;
-       } else {
+       } else if (!optimized) {
                bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
        }
 
@@ -2146,6 +2529,8 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
        RAII_VAR(struct ast_bridge *, chan_bridge, NULL, ao2_cleanup);
        RAII_VAR(struct ast_channel *, yanked_chan, NULL, ao2_cleanup);
 
+       ast_moh_stop(chan);
+
        ast_channel_lock(chan);
        chan_bridge = ast_channel_get_bridge(chan);
        ast_channel_unlock(chan);
@@ -2153,6 +2538,9 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
        if (chan_bridge) {
                struct ast_bridge_channel *bridge_channel;
 
+               /* The channel is in a bridge so it is not getting any new features. */
+               ast_bridge_features_destroy(features);
+
                ast_bridge_lock_both(bridge, chan_bridge);
                bridge_channel = bridge_find_channel(chan_bridge, chan);
 
@@ -2175,9 +2563,6 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
                bridge_dissolve_check_stolen(chan_bridge, bridge_channel);
                ast_bridge_unlock(chan_bridge);
                ast_bridge_unlock(bridge);
-
-               /* The channel was in a bridge so it is not getting any new features. */
-               ast_bridge_features_destroy(features);
        } else {
                /* Slightly less easy case. We need to yank channel A from
                 * where he currently is and impart him into our bridge.
@@ -2185,13 +2570,15 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
                yanked_chan = ast_channel_yank(chan);
                if (!yanked_chan) {
                        ast_log(LOG_WARNING, "Could not gain control of channel %s\n", ast_channel_name(chan));
+                       ast_bridge_features_destroy(features);
                        return -1;
                }
                if (ast_channel_state(yanked_chan) != AST_STATE_UP) {
                        ast_answer(yanked_chan);
                }
                ast_channel_ref(yanked_chan);
-               if (ast_bridge_impart(bridge, yanked_chan, NULL, features, 1)) {
+               if (ast_bridge_impart(bridge, yanked_chan, NULL, features,
+                       AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
                        /* It is possible for us to yank a channel and have some other
                         * thread start a PBX on the channl after we yanked it. In particular,
                         * this can theoretically happen on the ;2 of a Local channel if we
@@ -2439,9 +2826,15 @@ static int try_swap_optimize_out(struct ast_bridge *chan_bridge,
 
        other = ast_bridge_channel_peer(src_bridge_channel);
        if (other && other->state == BRIDGE_CHANNEL_STATE_WAIT) {
-               unsigned int id = ast_atomic_fetchadd_int((int *) &optimization_id, +1);
+               unsigned int id;
+
+               if (ast_channel_trylock(other->chan)) {
+                       return 1;
+               }
+
+               id = ast_atomic_fetchadd_int((int *) &optimization_id, +1);
 
-               ast_verb(3, "Move-swap optimizing %s <-- %s.\n",
+               ast_verb(4, "Move-swap optimizing %s <-- %s.\n",
                        ast_channel_name(dst_bridge_channel->chan),
                        ast_channel_name(other->chan));
 
@@ -2461,6 +2854,7 @@ static int try_swap_optimize_out(struct ast_bridge *chan_bridge,
                if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) {
                        pvt->callbacks->optimization_finished(pvt, res == 1, id);
                }
+               ast_channel_unlock(other->chan);
        }
        return res;
 }
@@ -2557,7 +2951,7 @@ static int try_merge_optimize_out(struct ast_bridge *chan_bridge,
                return 0;
        }
 
-       ast_verb(3, "Merge optimizing %s -- %s out.\n",
+       ast_verb(4, "Merge optimizing %s -- %s out.\n",
                ast_channel_name(chan_bridge_channel->chan),
                ast_channel_name(peer_bridge_channel->chan));
 
@@ -2980,8 +3374,20 @@ int ast_bridge_talk_detector_hook(struct ast_bridge_features *features,
                AST_BRIDGE_HOOK_TYPE_TALK);
 }
 
-int ast_bridge_interval_hook(struct ast_bridge_features *features,
-       enum ast_bridge_hook_timer_option flags,
+int ast_bridge_move_hook(struct ast_bridge_features *features,
+       ast_bridge_move_indicate_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       enum ast_bridge_hook_remove_flags remove_flags)
+{
+       ast_bridge_hook_callback hook_cb = (ast_bridge_hook_callback) callback;
+
+       return bridge_other_hook(features, hook_cb, hook_pvt, destructor, remove_flags,
+               AST_BRIDGE_HOOK_TYPE_MOVE);
+}
+
+int ast_bridge_interval_hook(struct ast_bridge_features *features,
+       enum ast_bridge_hook_timer_option flags,
        unsigned int interval,
        ast_bridge_hook_callback callback,
        void *hook_pvt,
@@ -3043,7 +3449,7 @@ int ast_bridge_features_enable(struct ast_bridge_features *features,
                dtmf = builtin_features_dtmf[feature];
                /* If no DTMF is still available (ie: it has been disabled) then error out now */
                if (ast_strlen_zero(dtmf)) {
-                       ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n",
+                       ast_debug(1, "Failed to enable built in feature %u on %p, no DTMF string is available for it.\n",
                                feature, features);
                        return -1;
                }
@@ -3226,6 +3632,64 @@ static int bridge_dtmf_hook_sort(const void *obj_left, const void *obj_right, in
        return cmp;
 }
 
+/*! \brief Callback for merging hook ao2_containers */
+static int merge_container_cb(void *obj, void *data, int flags)
+{
+       ao2_link(data, obj);
+       return 0;
+}
+
+/*! \brief Wrapper for interval hooks that calls into the wrapped hook */
+static int interval_wrapper_cb(struct ast_bridge_channel *bridge_channel, void *obj)
+{
+       struct ast_bridge_hook_timer *hook = obj;
+
+       return hook->generic.callback(bridge_channel, hook->generic.hook_pvt);
+}
+
+/*! \brief Destructor for the hook wrapper */
+static void interval_wrapper_pvt_dtor(void *obj)
+{
+       ao2_cleanup(obj);
+}
+
+/*! \brief Wrap the provided interval hook and add it to features */
+static void wrap_hook(struct ast_bridge_features *features, struct ast_bridge_hook_timer *hook)
+{
+       /* Break out of the current wrapper if it exists to avoid multiple layers */
+       if (hook->generic.callback == interval_wrapper_cb) {
+               hook = hook->generic.hook_pvt;
+       }
+
+       ast_bridge_interval_hook(features, hook->timer.flags, hook->timer.interval,
+               interval_wrapper_cb, ao2_bump(hook), interval_wrapper_pvt_dtor,
+               hook->generic.remove_flags.flags);
+}
+
+void ast_bridge_features_merge(struct ast_bridge_features *into, const struct ast_bridge_features *from)
+{
+       struct ast_bridge_hook_timer *hook;
+       int idx;
+
+       /* Merge hook containers */
+       ao2_callback(from->dtmf_hooks, 0, merge_container_cb, into->dtmf_hooks);
+       ao2_callback(from->other_hooks, 0, merge_container_cb, into->other_hooks);
+
+       /* Merge hook heaps */
+       ast_heap_wrlock(from->interval_hooks);
+       for (idx = 1; (hook = ast_heap_peek(from->interval_hooks, idx)); idx++) {
+               wrap_hook(into, hook);
+       }
+       ast_heap_unlock(from->interval_hooks);
+
+       /* Merge feature flags */
+       into->feature_flags.flags |= from->feature_flags.flags;
+       into->usable |= from->usable;
+
+       into->mute |= from->mute;
+       into->dtmf_passthrough |= from->dtmf_passthrough;
+}
+
 /* XXX ASTERISK-21271 make ast_bridge_features_init() static when make ast_bridge_join() requires features to be allocated. */
 int ast_bridge_features_init(struct ast_bridge_features *features)
 {
@@ -3311,6 +3775,13 @@ void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixi
        ast_bridge_unlock(bridge);
 }
 
+void ast_bridge_set_binaural_active(struct ast_bridge *bridge, unsigned int binaural_active)
+{
+       ast_bridge_lock(bridge);
+       bridge->softmix.binaural_active = binaural_active;
+       ast_bridge_unlock(bridge);
+}
+
 void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
 {
        ast_bridge_lock(bridge);
@@ -3335,6 +3806,8 @@ static void cleanup_video_mode(struct ast_bridge *bridge)
                if (bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc) {
                        ast_channel_unref(bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc);
                }
+       case AST_BRIDGE_VIDEO_MODE_SFU:
+               break;
        }
        memset(&bridge->softmix.video_mode, 0, sizeof(bridge->softmix.video_mode));
 }
@@ -3345,8 +3818,11 @@ void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_
        cleanup_video_mode(bridge);
        bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
        bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
-       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s",
-               bridge->softmix.video_mode.mode, ast_channel_name(video_src_chan));
+       ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n",
+               bridge->name, bridge->uniqueid,
+               ast_channel_name(video_src_chan),
+               ast_channel_uniqueid(video_src_chan));
+       ast_bridge_publish_state(bridge);
        ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
        ast_bridge_unlock(bridge);
 }
@@ -3356,8 +3832,21 @@ void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
        ast_bridge_lock(bridge);
        cleanup_video_mode(bridge);
        bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
-       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d",
-               bridge->softmix.video_mode.mode);
+       ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_set_sfu_video_mode(struct ast_bridge *bridge)
+{
+       ast_bridge_lock(bridge);
+       cleanup_video_mode(bridge);
+       bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_SFU;
+       ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int video_update_discard)
+{
+       ast_bridge_lock(bridge);
+       bridge->softmix.video_mode.video_update_discard = video_update_discard;
        ast_bridge_unlock(bridge);
 }
 
@@ -3366,7 +3855,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
        struct ast_bridge_video_talker_src_data *data;
 
        /* If the channel doesn't support video, we don't care about it */
-       if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) {
+       if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_VIDEO)) {
                return;
        }
 
@@ -3377,7 +3866,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
                data->average_talking_energy = talker_energy;
        } else if ((data->average_talking_energy < talker_energy) && is_keyframe) {
                if (data->chan_old_vsrc) {
-                       ast_channel_unref(data->chan_old_vsrc);
+                       data->chan_old_vsrc = ast_channel_unref(data->chan_old_vsrc);
                }
                if (data->chan_vsrc) {
                        data->chan_old_vsrc = data->chan_vsrc;
@@ -3385,14 +3874,22 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
                }
                data->chan_vsrc = ast_channel_ref(chan);
                data->average_talking_energy = talker_energy;
-               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+               ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n",
+                       bridge->name, bridge->uniqueid,
+                       ast_channel_name(data->chan_vsrc),
+                       ast_channel_uniqueid(data->chan_vsrc));
+               ast_bridge_publish_state(bridge);
                ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
        } else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
                ast_indicate(chan, AST_CONTROL_VIDUPDATE);
        } else if (!data->chan_vsrc && is_keyframe) {
                data->chan_vsrc = ast_channel_ref(chan);
                data->average_talking_energy = talker_energy;
-               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+               ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n",
+                       bridge->name, bridge->uniqueid,
+                       ast_channel_name(data->chan_vsrc),
+                       ast_channel_uniqueid(data->chan_vsrc));
+               ast_bridge_publish_state(bridge);
                ast_indicate(chan, AST_CONTROL_VIDUPDATE);
        } else if (!data->chan_old_vsrc && is_keyframe) {
                data->chan_old_vsrc = ast_channel_ref(chan);
@@ -3421,6 +3918,8 @@ int ast_bridge_number_video_src(struct ast_bridge *bridge)
                if (bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc) {
                        res++;
                }
+       case AST_BRIDGE_VIDEO_MODE_SFU:
+               break;
        }
        ast_bridge_unlock(bridge);
        return res;
@@ -3445,7 +3944,8 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
                } else if (bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
                        res = 2;
                }
-
+       case AST_BRIDGE_VIDEO_MODE_SFU:
+               break;
        }
        ast_bridge_unlock(bridge);
        return res;
@@ -3479,10 +3979,27 @@ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *
                        }
                        bridge->softmix.video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
                }
+       case AST_BRIDGE_VIDEO_MODE_SFU:
+               break;
        }
        ast_bridge_unlock(bridge);
 }
 
+const char *ast_bridge_video_mode_to_string(enum ast_bridge_video_mode_type video_mode)
+{
+       switch (video_mode) {
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               return "talker";
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               return "single";
+       case AST_BRIDGE_VIDEO_MODE_SFU:
+               return "sfu";
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+       default:
+               return "none";
+       }
+}
+
 static int channel_hash(const void *obj, int flags)
 {
        const struct ast_channel *chan = obj;
@@ -3608,148 +4125,97 @@ struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channe
  * bridges, this method is only used for multi-party bridges since this method would
  * be less efficient for two-party bridges.
  *
+ * \param is_external Whether the transfer is externally initiated
  * \param transferer The channel performing a transfer
  * \param bridge The bridge where the transfer is being performed
  * \param exten The destination extension for the blind transfer
  * \param context The destination context for the blind transfer
- * \param hook Framehook to attach to local channel
+ * \param transferee The party being transferred if there is only one
+ * \param new_channel_cb Callback to call on channel that is created to
+ *        facilitate the blind transfer.
+ * \param user_data_wrapper User-provided data needed in new_channel_cb
+ * \param transfer_message The Stasis publication for this transfer.
+ *
  * \return The success or failure of the operation
  */
-static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transferer,
-               struct ast_bridge *bridge, const char *exten, const char *context,
-               transfer_channel_cb new_channel_cb, void *user_data)
+static enum ast_transfer_result blind_transfer_bridge(int is_external,
+               struct ast_channel *transferer, struct ast_bridge *bridge,
+               const char *exten, const char *context, struct ast_channel *transferee,
+               transfer_channel_cb new_channel_cb,
+               struct transfer_channel_data *user_data_wrapper,
+               struct ast_blind_transfer_message *transfer_message)
 {
        struct ast_channel *local;
        char chan_name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
        int cause;
 
        snprintf(chan_name, sizeof(chan_name), "%s@%s", exten, context);
-       local = ast_request("Local", ast_channel_nativeformats(transferer), transferer,
+       local = ast_request("Local", ast_channel_nativeformats(transferer), NULL, transferer,
                        chan_name, &cause);
        if (!local) {
                return AST_BRIDGE_TRANSFER_FAIL;
        }
 
+       ast_channel_lock_both(local, transferer);
+       ast_channel_req_accountcodes(local, transferer, AST_CHANNEL_REQUESTOR_REPLACEMENT);
+
+       transfer_message->replace_channel = ast_channel_snapshot_get_latest(ast_channel_uniqueid(local));
+       if (!transfer_message->replace_channel) {
+               ast_hangup(local);
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
+
+       pbx_builtin_setvar_helper(local, BLINDTRANSFER, ast_channel_name(transferer));
+       ast_channel_unlock(local);
+       ast_channel_unlock(transferer);
+
        if (new_channel_cb) {
-               new_channel_cb(local, user_data, AST_BRIDGE_TRANSFER_MULTI_PARTY);
+               new_channel_cb(local, user_data_wrapper, AST_BRIDGE_TRANSFER_MULTI_PARTY);
        }
 
        if (ast_call(local, chan_name, 0)) {
                ast_hangup(local);
                return AST_BRIDGE_TRANSFER_FAIL;
        }
-       if (ast_bridge_impart(bridge, local, transferer, NULL, 1)) {
+
+       if (ast_bridge_impart(bridge, local, transferer, NULL,
+               AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
                ast_hangup(local);
                return AST_BRIDGE_TRANSFER_FAIL;
        }
-       return AST_BRIDGE_TRANSFER_SUCCESS;
-}
 
-/*!
- * \internal
- * \brief Base data to publish for stasis attended transfer messages
- */
-struct stasis_attended_transfer_publish_data {
-       /* The bridge between the transferer and transferee, and the transferer channel in this bridge */
-       struct ast_bridge_channel_pair to_transferee;
-       /* The bridge between the transferer and transfer target, and the transferer channel in this bridge */
-       struct ast_bridge_channel_pair to_transfer_target;
-};
-
-static void stasis_publish_data_cleanup(struct stasis_attended_transfer_publish_data *publication)
-{
-       ast_channel_unref(publication->to_transferee.channel);
-       ast_channel_unref(publication->to_transfer_target.channel);
-       ao2_cleanup(publication->to_transferee.bridge);
-       ao2_cleanup(publication->to_transfer_target.bridge);
+       return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
 /*!
  * \internal
- * \brief Set up base data for an attended transfer stasis publication
- *
- * \param to_transferee The original transferer channel, which may be bridged to a transferee
- * \param to_transferee_bridge The bridge that to_transferee is in.
- * \param to_transfer_target The second transferer channel, which may be bridged to a transfer target
- * \param to_target_bridge The bridge that to_transfer_target_is in.
- * \param[out] publication A structure to hold the other parameters
- */
-static void stasis_publish_data_init(struct ast_channel *to_transferee,
-               struct ast_bridge *to_transferee_bridge, struct ast_channel *to_transfer_target,
-               struct ast_bridge *to_target_bridge,
-               struct stasis_attended_transfer_publish_data *publication)
-{
-       memset(publication, 0, sizeof(*publication));
-       publication->to_transferee.channel = ast_channel_ref(to_transferee);
-       if (to_transferee_bridge) {
-               ao2_ref(to_transferee_bridge, +1);
-               publication->to_transferee.bridge = to_transferee_bridge;
-       }
-
-       publication->to_transfer_target.channel = ast_channel_ref(to_transfer_target);
-       if (to_target_bridge) {
-               ao2_ref(to_target_bridge, +1);
-               publication->to_transfer_target.bridge = to_target_bridge;
-       }
-}
-
-/*
- * \internal
- * \brief Publish a stasis attended transfer resulting in a bridge merge
- *
- * \param publication Base data about the attended transfer
- * \param final_bridge The surviving bridge of the attended transfer
- */
-static void publish_attended_transfer_bridge_merge(struct stasis_attended_transfer_publish_data *publication,
-               struct ast_bridge *final_bridge)
-{
-       ast_bridge_publish_attended_transfer_bridge_merge(1, AST_BRIDGE_TRANSFER_SUCCESS,
-                       &publication->to_transferee, &publication->to_transfer_target, final_bridge);
-}
-
-/*
- * \internal
- * \brief Publish a stasis attended transfer to an application
+ * \brief Get the transferee channel
  *
- * \param publication Base data about the attended transfer
- * \param app The app that is running at the conclusion of the transfer
- */
-static void publish_attended_transfer_app(struct stasis_attended_transfer_publish_data *publication,
-               const char *app)
-{
-       ast_bridge_publish_attended_transfer_app(1, AST_BRIDGE_TRANSFER_SUCCESS,
-                       &publication->to_transferee, &publication->to_transfer_target, app);
-}
-
-/*
- * \internal
- * \brief Publish a stasis attended transfer showing a link between bridges
+ * This is only applicable to cases where a transfer is occurring on a
+ * two-party bridge. The channels container passed in is expected to only
+ * contain two channels, the transferer and the transferee. The transferer
+ * channel is passed in as a parameter to ensure we don't return it as
+ * the transferee channel.
  *
- * \param publication Base data about the attended transfer
- * \param local_channel1 Local channel in the original bridge
- * \param local_channel2 Local channel in the second bridge
+ * \param channels A two-channel container containing the transferer and transferee
+ * \param transferer The party that is transfering the call
+ * \return The party that is being transferred
  */
-static void publish_attended_transfer_link(struct stasis_attended_transfer_publish_data *publication,
-               struct ast_channel *local_channel1, struct ast_channel *local_channel2)
+static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
 {
-       struct ast_channel *locals[2] = { local_channel1, local_channel2 };
+       struct ao2_iterator channel_iter;
+       struct ast_channel *transferee;
 
-       ast_bridge_publish_attended_transfer_link(1, AST_BRIDGE_TRANSFER_SUCCESS,
-                       &publication->to_transferee, &publication->to_transfer_target, locals);
-}
+       for (channel_iter = ao2_iterator_init(channels, 0);
+                       (transferee = ao2_iterator_next(&channel_iter));
+                       ao2_cleanup(transferee)) {
+               if (transferee != transferer) {
+                       break;
+               }
+       }
 
-/*
- * \internal
- * \brief Publish a stasis attended transfer failure
- *
- * \param publication Base data about the attended transfer
- * \param result The transfer result
- */
-static void publish_attended_transfer_fail(struct stasis_attended_transfer_publish_data *publication,
-               enum ast_transfer_result result)
-{
-       ast_bridge_publish_attended_transfer_fail(1, result, &publication->to_transferee,
-                       &publication->to_transfer_target);
+       ao2_iterator_destroy(&channel_iter);
+       return transferee;
 }
 
 /*!
@@ -3776,21 +4242,35 @@ static void publish_attended_transfer_fail(struct stasis_attended_transfer_publi
  */
 static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *chan1,
                struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2,
-               struct stasis_attended_transfer_publish_data *publication)
-{
+               struct ast_attended_transfer_message *transfer_msg)
+{
+#define BRIDGE_LOCK_ONE_OR_BOTH(b1, b2) \
+       do { \
+               if (b2) { \
+                       ast_bridge_lock_both(b1, b2); \
+               } else { \
+                       ast_bridge_lock(b1); \
+               } \
+       } while (0)
+
        static const char *dest = "_attended@transfer/m";
        struct ast_channel *local_chan;
        int cause;
        int res;
        const char *app = NULL;
 
-       local_chan = ast_request("Local", ast_channel_nativeformats(chan1), chan1,
+       local_chan = ast_request("Local", ast_channel_nativeformats(chan1), NULL, chan1,
                        dest, &cause);
-
        if (!local_chan) {
                return AST_BRIDGE_TRANSFER_FAIL;
        }
 
+       ast_channel_lock_both(local_chan, chan1);
+       ast_channel_req_accountcodes(local_chan, chan1, AST_CHANNEL_REQUESTOR_REPLACEMENT);
+       pbx_builtin_setvar_helper(local_chan, ATTENDEDTRANSFER, ast_channel_name(chan1));
+       ast_channel_unlock(local_chan);
+       ast_channel_unlock(chan1);
+
        if (bridge2) {
                res = ast_local_setup_bridge(local_chan, bridge2, chan2, NULL);
        } else {
@@ -3803,65 +4283,64 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
                return AST_BRIDGE_TRANSFER_FAIL;
        }
 
+       /*
+        * Since bridges need to be unlocked before entering ast_bridge_impart and
+        * core_local may call into it then the bridges need to be unlocked here.
+        */
+       ast_bridge_unlock(bridge1);
+       if (bridge2) {
+               ast_bridge_unlock(bridge2);
+       }
+
        if (ast_call(local_chan, dest, 0)) {
                ast_hangup(local_chan);
+               BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
                return AST_BRIDGE_TRANSFER_FAIL;
        }
 
-       if (ast_bridge_impart(bridge1, local_chan, chan1, NULL, 1)) {
+       /* Get a ref for use later since this one is being stolen */
+       ao2_ref(local_chan, +1);
+       if (ast_bridge_impart(bridge1, local_chan, chan1, NULL,
+               AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
                ast_hangup(local_chan);
+               ao2_cleanup(local_chan);
+               BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
                return AST_BRIDGE_TRANSFER_FAIL;
        }
+       BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
 
        if (bridge2) {
-               RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup);
-
-               ast_channel_lock(local_chan);
-               local_chan2 = ast_local_get_peer(local_chan);
-               ast_channel_unlock(local_chan);
+               void *tech;
+               struct ast_channel *locals[2];
+
+               /* Have to lock everything just in case a hangup comes in early */
+               ast_local_lock_all(local_chan, &tech, &locals[0], &locals[1]);
+               if (!locals[0] || !locals[1]) {
+                       ast_log(LOG_ERROR, "Transfer failed probably due to an early hangup - "
+                               "missing other half of '%s'\n", ast_channel_name(local_chan));
+                       ast_local_unlock_all(tech, locals[0], locals[1]);
+                       ao2_cleanup(local_chan);
+                       return AST_BRIDGE_TRANSFER_FAIL;
+               }
 
-               ast_assert(local_chan2 != NULL);
+               /* Make sure the peer is properly set */
+               if (local_chan != locals[0]) {
+                       SWAP(locals[0], locals[1]);
+               }
 
-               publish_attended_transfer_link(publication,
-                               local_chan, local_chan2);
+               ast_attended_transfer_message_add_link(transfer_msg, locals);
+               ast_local_unlock_all(tech, locals[0], locals[1]);
        } else {
-               publish_attended_transfer_app(publication, app);
-       }
-       return AST_BRIDGE_TRANSFER_SUCCESS;
-}
-
-/*!
- * \internal
- * \brief Get the transferee channel
- *
- * This is only applicable to cases where a transfer is occurring on a
- * two-party bridge. The channels container passed in is expected to only
- * contain two channels, the transferer and the transferee. The transferer
- * channel is passed in as a parameter to ensure we don't return it as
- * the transferee channel.
- *
- * \param channels A two-channel container containing the transferer and transferee
- * \param transferer The party that is transfering the call
- * \return The party that is being transferred
- */
-static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
-{
-       struct ao2_iterator channel_iter;
-       struct ast_channel *transferee;
-
-       for (channel_iter = ao2_iterator_init(channels, 0);
-                       (transferee = ao2_iterator_next(&channel_iter));
-                       ao2_cleanup(transferee)) {
-               if (transferee != transferer) {
-                       break;
-               }
+               ast_attended_transfer_message_add_app(transfer_msg, app, local_chan);
        }
 
-       ao2_iterator_destroy(&channel_iter);
-       return transferee;
+       ao2_cleanup(local_chan);
+       return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
-static enum ast_transfer_result try_parking(struct ast_channel *transferer, const char *context, const char *exten)
+static enum ast_transfer_result try_parking(struct ast_channel *transferer,
+       const char *context, const char *exten, transfer_channel_cb new_channel_cb,
+       struct transfer_channel_data *user_data_wrapper)
 {
        RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup);
 
@@ -3878,26 +4357,45 @@ static enum ast_transfer_result try_parking(struct ast_channel *transferer, cons
        }
 
        if (ast_parking_blind_transfer_park(transferer_bridge_channel,
-               context, exten)) {
+               context, exten, new_channel_cb, user_data_wrapper)) {
                return AST_BRIDGE_TRANSFER_FAIL;
        }
 
        return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
+void ast_bridge_set_transfer_variables(struct ast_channel *chan, const char *value, int attended)
+{
+       char *writevar;
+       char *erasevar;
+
+       if (attended) {
+               writevar = ATTENDEDTRANSFER;
+               erasevar = BLINDTRANSFER;
+       } else {
+               writevar = BLINDTRANSFER;
+               erasevar = ATTENDEDTRANSFER;
+       }
+
+       pbx_builtin_setvar_helper(chan, writevar, value);
+       pbx_builtin_setvar_helper(chan, erasevar, NULL);
+}
+
 /*!
  * \internal
- * \brief Set the BLINDTRANSFER variable as appropriate on channels involved in the transfer
+ * \brief Set the transfer variable as appropriate on channels involved in the transfer
  *
- * The transferer channel will have its BLINDTRANSFER variable set the same as its BRIDGEPEER
+ * The transferer channel will have its variable set the same as its BRIDGEPEER
  * variable. This will account for all channels that it is bridged to. The other channels
- * involved in the transfer will have their BLINDTRANSFER variable set to the transferer
+ * involved in the transfer will have their variable set to the transferer
  * channel's name.
  *
- * \param transferer The channel performing the blind transfer
+ * \param transferer The channel performing the transfer
  * \param channels The channels belonging to the bridge
+ * \param is_attended false  set BLINDTRANSFER and unset ATTENDEDTRANSFER
+ *                    true   set ATTENDEDTRANSFER and unset BLINDTRANSFER
  */
-static void set_blind_transfer_variables(struct ast_channel *transferer, struct ao2_container *channels)
+static void set_transfer_variables_all(struct ast_channel *transferer, struct ao2_container *channels, int is_attended)
 {
        struct ao2_iterator iter;
        struct ast_channel *chan;
@@ -3913,9 +4411,9 @@ static void set_blind_transfer_variables(struct ast_channel *transferer, struct
                        (chan = ao2_iterator_next(&iter));
                        ao2_cleanup(chan)) {
                if (chan == transferer) {
-                       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_bridgepeer);
+                       ast_bridge_set_transfer_variables(chan, transferer_bridgepeer, is_attended);
                } else {
-                       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_name);
+                       ast_bridge_set_transfer_variables(chan, transferer_name, is_attended);
                }
        }
 
@@ -3930,8 +4428,8 @@ static struct ast_bridge *acquire_bridge(struct ast_channel *chan)
        bridge = ast_channel_get_bridge(chan);
        ast_channel_unlock(chan);
 
-       if (bridge
-               && ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+       if (bridge && ast_test_flag(&bridge->feature_flags,
+                       (AST_BRIDGE_FLAG_MASQUERADE_ONLY | AST_BRIDGE_FLAG_INVISIBLE))) {
                ao2_ref(bridge, -1);
                bridge = NULL;
        }
@@ -3939,16 +4437,6 @@ static struct ast_bridge *acquire_bridge(struct ast_channel *chan)
        return bridge;
 }
 
-static void publish_blind_transfer(int is_external, enum ast_transfer_result result,
-               struct ast_channel *transferer, struct ast_bridge *bridge,
-               const char *context, const char *exten)
-{
-       struct ast_bridge_channel_pair pair;
-       pair.channel = transferer;
-       pair.bridge = bridge;
-       ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten);
-}
-
 enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
                struct ast_channel *transferer, const char *exten, const char *context,
                transfer_channel_cb new_channel_cb, void *user_data)
@@ -3957,15 +4445,45 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
        RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
        RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
        RAII_VAR(struct ast_channel *, transferee, NULL, ast_channel_cleanup);
+       RAII_VAR(struct transfer_channel_data *, user_data_wrapper, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_blind_transfer_message *, transfer_message, NULL, ao2_cleanup);
        int do_bridge_transfer;
        int transfer_prohibited;
        enum ast_transfer_result transfer_result;
 
+       transfer_message = ast_blind_transfer_message_create(is_external, transferer, exten, context);
+       if (!transfer_message) {
+               /* Out of memory. Not even possible to publish a Stasis message about the
+                * failure
+                */
+               ast_log(LOG_ERROR, "Unable to allocate memory for blind transfer publication from %s\n",
+                               ast_channel_name(transferer));
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
+
        bridge = acquire_bridge(transferer);
        if (!bridge) {
                transfer_result = AST_BRIDGE_TRANSFER_INVALID;
                goto publish;
        }
+
+       ast_bridge_lock(bridge);
+       transfer_message->bridge = ast_bridge_snapshot_create(bridge);
+       ast_bridge_unlock(bridge);
+       if (!transfer_message->bridge) {
+               transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+               goto publish;
+       }
+
+       transferee = ast_bridge_peer(bridge, transferer);
+       if (transferee) {
+               transfer_message->transferee = ast_channel_snapshot_get_latest(ast_channel_uniqueid(transferee));
+               if (!transfer_message->transferee) {
+                       transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+                       goto publish;
+               }
+       }
+
        ast_channel_lock(transferer);
        bridge_channel = ast_channel_get_bridge_channel(transferer);
        ast_channel_unlock(transferer);
@@ -3974,14 +4492,25 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
                goto publish;
        }
 
+       user_data_wrapper = ao2_alloc(sizeof(*user_data_wrapper), NULL);
+       if (!user_data_wrapper) {
+               transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+               goto publish;
+       }
+
+       user_data_wrapper->data = user_data;
+
        /* Take off hold if they are on hold. */
        ast_bridge_channel_write_unhold(bridge_channel);
 
-       transfer_result = try_parking(transferer, context, exten);
+       transfer_result = try_parking(transferer, context, exten, new_channel_cb, user_data_wrapper);
        if (transfer_result == AST_BRIDGE_TRANSFER_SUCCESS) {
                goto publish;
        }
 
+       /* Since parking didn't take control of the user_data_wrapper, we are just going to raise the completed flag now. */
+       user_data_wrapper->completed = 1;
+
        {
                SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
 
@@ -4006,24 +4535,23 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
                goto publish;
        }
 
-       set_blind_transfer_variables(transferer, channels);
+       set_transfer_variables_all(transferer, channels, 0);
 
        if (do_bridge_transfer) {
-               transfer_result = blind_transfer_bridge(transferer, bridge, exten, context,
-                               new_channel_cb, user_data);
+               transfer_result = blind_transfer_bridge(is_external, transferer, bridge,
+                       exten, context, transferee, new_channel_cb, user_data_wrapper, transfer_message);
                goto publish;
        }
 
        /* Reaching this portion means that we're dealing with a two-party bridge */
 
-       transferee = get_transferee(channels, transferer);
        if (!transferee) {
                transfer_result = AST_BRIDGE_TRANSFER_FAIL;
                goto publish;
        }
 
        if (bridge_channel_internal_queue_blind_transfer(transferee, exten, context,
-                               new_channel_cb, user_data)) {
+                               new_channel_cb, user_data_wrapper)) {
                transfer_result = AST_BRIDGE_TRANSFER_FAIL;
                goto publish;
        }
@@ -4032,7 +4560,8 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
        transfer_result = AST_BRIDGE_TRANSFER_SUCCESS;
 
 publish:
-       publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten);
+       transfer_message->result = transfer_result;
+       ast_bridge_publish_blind_transfer(transfer_message);
        return transfer_result;
 }
 
@@ -4099,7 +4628,7 @@ static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel
                struct ast_channel *to_transfer_target,
                struct ast_bridge_channel *to_target_bridge_channel,
                struct ast_bridge *to_transferee_bridge, struct ast_bridge *to_target_bridge,
-               struct stasis_attended_transfer_publish_data *publication)
+               struct ast_attended_transfer_message *transfer_msg)
 {
        struct ast_bridge_channel *kick_me[] = {
                        to_transferee_bridge_channel,
@@ -4107,6 +4636,16 @@ static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel
        };
        enum ast_transfer_result res;
        struct ast_bridge *final_bridge = NULL;
+       RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+
+       channels = ast_bridge_peers_nolock(to_transferee_bridge);
+
+       if (!channels) {
+               res = AST_BRIDGE_TRANSFER_FAIL;
+               goto end;
+       }
+
+       set_transfer_variables_all(to_transferee, channels, 1);
 
        switch (ast_bridges_allow_optimization(to_transferee_bridge, to_target_bridge)) {
        case AST_BRIDGE_OPTIMIZE_SWAP_TO_CHAN_BRIDGE:
@@ -4135,20 +4674,16 @@ static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel
                 */
                if (to_transferee_bridge->inhibit_merge || to_transferee_bridge->dissolved ||
                                to_target_bridge->inhibit_merge || to_target_bridge->dissolved) {
-                       res = AST_BRIDGE_TRANSFER_INVALID;
-                       goto end;
+                       return AST_BRIDGE_TRANSFER_INVALID;
                }
 
-               /* Don't goto end here. attended_transfer_bridge will publish its own
-                * stasis message if it succeeds
-                */
                return attended_transfer_bridge(to_transferee, to_transfer_target,
-                       to_transferee_bridge, to_target_bridge, publication);
+                       to_transferee_bridge, to_target_bridge, transfer_msg);
        }
 
 end:
        if (res == AST_BRIDGE_TRANSFER_SUCCESS) {
-               publish_attended_transfer_bridge_merge(publication, final_bridge);
+               ast_attended_transfer_message_add_merge(transfer_msg, final_bridge);
        }
 
        return res;
@@ -4163,20 +4698,26 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
        RAII_VAR(struct ast_bridge_channel *, to_target_bridge_channel, NULL, ao2_cleanup);
        RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
        RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup);
-       struct ast_bridge *the_bridge;
+       RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
+       struct ast_bridge *the_bridge = NULL;
        struct ast_channel *chan_bridged;
        struct ast_channel *chan_unbridged;
        int transfer_prohibited;
        int do_bridge_transfer;
        enum ast_transfer_result res;
        const char *app = NULL;
-       struct stasis_attended_transfer_publish_data publication;
+       int hangup_target = 0;
 
        to_transferee_bridge = acquire_bridge(to_transferee);
        to_target_bridge = acquire_bridge(to_transfer_target);
 
-       stasis_publish_data_init(to_transferee, to_transferee_bridge,
-                       to_transfer_target, to_target_bridge, &publication);
+       transfer_msg = ast_attended_transfer_message_create(1, to_transferee, to_transferee_bridge,
+                       to_transfer_target, to_target_bridge, NULL, NULL);
+       if (!transfer_msg) {
+               ast_log(LOG_ERROR, "Unable to create Stasis publication for attended transfer from %s\n",
+                               ast_channel_name(to_transferee));
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
 
        /* They can't both be unbridged, you silly goose! */
        if (!to_transferee_bridge && !to_target_bridge) {
@@ -4241,10 +4782,11 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
                ast_bridge_lock_both(to_transferee_bridge, to_target_bridge);
                res = two_bridge_attended_transfer(to_transferee, to_transferee_bridge_channel,
                                to_transfer_target, to_target_bridge_channel,
-                               to_transferee_bridge, to_target_bridge, &publication);
+                               to_transferee_bridge, to_target_bridge, transfer_msg);
                ast_bridge_unlock(to_transferee_bridge);
                ast_bridge_unlock(to_target_bridge);
 
+               hangup_target = 1;
                goto end;
        }
 
@@ -4252,6 +4794,12 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
        chan_bridged = to_transferee_bridge ? to_transferee : to_transfer_target;
        chan_unbridged = to_transferee_bridge ? to_transfer_target : to_transferee;
 
+       /*
+        * Race condition makes it possible for app to be NULL, so get the app prior to
+        * transferring with a fallback of "unknown".
+        */
+       app = ast_strdupa(ast_channel_appl(chan_unbridged) ?: "unknown");
+
        {
                int chan_count;
                SCOPED_LOCK(lock, the_bridge, ast_bridge_lock, ast_bridge_unlock);
@@ -4278,9 +4826,18 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
                goto end;
        }
 
+       set_transfer_variables_all(to_transferee, channels, 1);
+
        if (do_bridge_transfer) {
-                res = attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL, &publication);
-                goto end;
+               /*
+                * Hang up the target if it was bridged. Note, if it is not bridged
+                * it is hung up during the masquerade.
+                */
+               hangup_target = chan_bridged == to_transfer_target;
+               ast_bridge_lock(the_bridge);
+               res = attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL, transfer_msg);
+               ast_bridge_unlock(the_bridge);
+               goto end;
        }
 
        transferee = get_transferee(channels, chan_bridged);
@@ -4289,7 +4846,6 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
                goto end;
        }
 
-       app = ast_strdupa(ast_channel_appl(chan_unbridged));
        if (bridge_channel_internal_queue_attended_transfer(transferee, chan_unbridged)) {
                res = AST_BRIDGE_TRANSFER_FAIL;
                goto end;
@@ -4297,17 +4853,16 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
 
        ast_bridge_remove(the_bridge, chan_bridged);
 
-       publish_attended_transfer_app(&publication, app);
+       ast_attended_transfer_message_add_app(transfer_msg, app, NULL);
        res = AST_BRIDGE_TRANSFER_SUCCESS;
 
 end:
-       /* All successful transfer paths have published an appropriate stasis message.
-        * All failure paths have deferred publishing a stasis message until this point
-        */
-       if (res != AST_BRIDGE_TRANSFER_SUCCESS) {
-               publish_attended_transfer_fail(&publication, res);
+       if ((res == AST_BRIDGE_TRANSFER_SUCCESS && hangup_target) || res == AST_BRIDGE_TRANSFER_FAIL) {
+               ast_softhangup(to_transfer_target, AST_SOFTHANGUP_DEV);
        }
-       stasis_publish_data_cleanup(&publication);
+
+       transfer_msg->result = res;
+       ast_bridge_publish_attended_transfer(transfer_msg);
        return res;
 }
 
@@ -4467,7 +5022,47 @@ static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flag
        return cmp;
 }
 
-static char *complete_bridge(const char *word, int state)
+struct ast_bridge *ast_bridge_find_by_id(const char *bridge_id)
+{
+       return ao2_find(bridges, bridge_id, OBJ_SEARCH_KEY);
+}
+
+struct bridge_complete {
+       /*! Nth match to return. */
+       int state;
+       /*! Which match currently on. */
+       int which;
+};
+
+static int complete_bridge_live_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_live(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_live_search, (char *) word, &search);
+       if (!bridge) {
+               return NULL;
+       }
+       ret = ast_strdup(bridge->uniqueid);
+       ao2_ref(bridge, -1);
+       return ret;
+}
+
+static char *complete_bridge_stasis(const char *word, int state)
 {
        char *ret = NULL;
        int wordlen = strlen(word), which = 0;
@@ -4475,7 +5070,8 @@ static char *complete_bridge(const char *word, int state)
        struct ao2_iterator iter;
        struct stasis_message *msg;
 
-       if (!(cached_bridges = stasis_cache_dump(ast_bridge_cache(), ast_bridge_snapshot_type()))) {
+       cached_bridges = stasis_cache_dump(ast_bridge_cache(), ast_bridge_snapshot_type());
+       if (!cached_bridges) {
                return NULL;
        }
 
@@ -4485,6 +5081,7 @@ static char *complete_bridge(const char *word, int state)
 
                if (!strncasecmp(word, snapshot->uniqueid, wordlen) && (++which > state)) {
                        ret = ast_strdup(snapshot->uniqueid);
+                       ao2_ref(msg, -1);
                        break;
                }
        }
@@ -4513,7 +5110,8 @@ static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast
                return NULL;
        }
 
-       if (!(cached_bridges = stasis_cache_dump(ast_bridge_cache(), ast_bridge_snapshot_type()))) {
+       cached_bridges = stasis_cache_dump(ast_bridge_cache(), ast_bridge_snapshot_type());
+       if (!cached_bridges) {
                ast_cli(a->fd, "Failed to retrieve cached bridges\n");
                return CLI_SUCCESS;
        }
@@ -4545,7 +5143,8 @@ static int bridge_show_specific_print_channel(void *obj, void *arg, int flags)
        RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
        struct ast_channel_snapshot *snapshot;
 
-       if (!(msg = stasis_cache_get(ast_channel_cache(), ast_channel_snapshot_type(), uniqueid))) {
+       msg = stasis_cache_get(ast_channel_cache(), ast_channel_snapshot_type(), uniqueid);
+       if (!msg) {
                return 0;
        }
        snapshot = stasis_message_data(msg);
@@ -4569,7 +5168,7 @@ static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struc
                return NULL;
        case CLI_GENERATE:
                if (a->pos == 2) {
-                       return complete_bridge(a->word, a->n);
+                       return complete_bridge_stasis(a->word, a->n);
                }
                return NULL;
        }
@@ -4594,6 +5193,7 @@ static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struc
        return CLI_SUCCESS;
 }
 
+#ifdef AST_DEVMODE
 static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_bridge *bridge;
@@ -4607,7 +5207,7 @@ static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, st
                return NULL;
        case CLI_GENERATE:
                if (a->pos == 2) {
-                       return complete_bridge(a->word, a->n);
+                       return complete_bridge_live(a->word, a->n);
                }
                return NULL;
        }
@@ -4616,7 +5216,7 @@ static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, st
                return CLI_SHOWUSAGE;
        }
 
-       bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+       bridge = ast_bridge_find_by_id(a->argv[2]);
        if (!bridge) {
                ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
                return CLI_SUCCESS;
@@ -4627,15 +5227,16 @@ static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, st
 
        return CLI_SUCCESS;
 }
+#endif
 
 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 *bridge;
        struct ast_bridge_channel *bridge_channel;
        int which;
        int wordlen;
 
-       bridge = ao2_find(bridges, bridge_name, OBJ_KEY);
+       bridge = ast_bridge_find_by_id(bridge_name);
        if (!bridge) {
                return NULL;
        }
@@ -4648,32 +5249,42 @@ static char *complete_bridge_participant(const char *bridge_name, const char *li
                AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
                        if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen)
                                && ++which > state) {
+                               ao2_ref(bridge, -1);
                                return ast_strdup(ast_channel_name(bridge_channel->chan));
                        }
                }
        }
 
+       ao2_ref(bridge, -1);
+
        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);
+       static const char * const completions[] = { "all", NULL };
+       char *complete;
+       struct ast_bridge *bridge;
 
        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";
+                       "Usage: bridge kick <bridge-id> <channel-name | all>\n"
+                       "       Kick the <channel-name> channel out of the <bridge-id> bridge\n"
+                       "       If all is specified as the channel name then all channels will be\n"
+                       "       kicked out of the bridge.\n";
                return NULL;
        case CLI_GENERATE:
                if (a->pos == 2) {
-                       return complete_bridge(a->word, a->n);
+                       return complete_bridge_live(a->word, a->n);
                }
                if (a->pos == 3) {
-                       return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
+                       complete = ast_cli_complete(a->word, completions, a->n);
+                       if (!complete) {
+                               complete = complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n - 1);
+                       }
+                       return complete;
                }
                return NULL;
        }
@@ -4682,22 +5293,39 @@ static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct
                return CLI_SHOWUSAGE;
        }
 
-       bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+       bridge = ast_bridge_find_by_id(a->argv[2]);
        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;
-       }
+       if (!strcasecmp(a->argv[3], "all")) {
+               struct ast_bridge_channel *bridge_channel;
 
-       ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
-               ast_channel_name(chan), a->argv[2]);
-       ast_bridge_kick(bridge, chan);
+               ast_cli(a->fd, "Kicking all channels from bridge '%s'\n", a->argv[2]);
 
+               ast_bridge_lock(bridge);
+               AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+                       ast_bridge_channel_queue_callback(bridge_channel, 0, kick_it, NULL, 0);
+               }
+               ast_bridge_unlock(bridge);
+       } else {
+               struct ast_channel *chan;
+
+               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]);
+                       ao2_ref(bridge, -1);
+                       return CLI_SUCCESS;
+               }
+
+               ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
+                       ast_channel_name(chan), a->argv[2]);
+               ast_bridge_kick(bridge, chan);
+               ast_channel_unref(chan);
+       }
+
+       ao2_ref(bridge, -1);
        return CLI_SUCCESS;
 }
 
@@ -4725,7 +5353,7 @@ static const char *tech_capability2str(uint32_t capabilities)
 static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 #define FORMAT_HDR "%-20s %-20s %8s %s\n"
-#define FORMAT_ROW "%-20s %-20s %8d %s\n"
+#define FORMAT_ROW "%-20s %-20s %8u %s\n"
 
        struct ast_bridge_technology *cur;
 
@@ -4834,23 +5462,140 @@ static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd,
 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"),
-/* XXX ASTERISK-22356 need AMI action equivalents to the following CLI commands. */
+#ifdef AST_DEVMODE
        AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"),
+#endif
        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"),
 };
 
+
+static int handle_manager_bridge_tech_suspend(struct mansession *s, const struct message *m, int suspend)
+{
+       const char *name = astman_get_header(m, "BridgeTechnology");
+       struct ast_bridge_technology *cur;
+       int successful = 0;
+
+       if (ast_strlen_zero(name)) {
+               astman_send_error(s, m, "BridgeTechnology must be provided");
+               return 0;
+       }
+
+       AST_RWLIST_RDLOCK(&bridge_technologies);
+       AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+
+               if (!strcasecmp(cur->name, name)) {
+                       successful = 1;
+                       if (suspend) {
+                               ast_bridge_technology_suspend(cur);
+                       } else {
+                               ast_bridge_technology_unsuspend(cur);
+                       }
+                       break;
+               }
+       }
+       AST_RWLIST_UNLOCK(&bridge_technologies);
+       if (!successful) {
+               astman_send_error(s, m, "BridgeTechnology not found");
+               return 0;
+       }
+
+       astman_send_ack(s, m, (suspend ? "Suspended bridge technology" : "Unsuspended bridge technology"));
+       return 0;
+}
+
+static int manager_bridge_tech_suspend(struct mansession *s, const struct message *m)
+{
+       return handle_manager_bridge_tech_suspend(s, m, 1);
+}
+
+static int manager_bridge_tech_unsuspend(struct mansession *s, const struct message *m)
+{
+       return handle_manager_bridge_tech_suspend(s, m, 0);
+}
+
+static int manager_bridge_tech_list(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+       struct ast_bridge_technology *cur;
+       int num_items = 0;
+
+       if (!id_text) {
+               astman_send_error(s, m, "Internal error");
+               return -1;
+       }
+
+       if (!ast_strlen_zero(id)) {
+               ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+       }
+
+       astman_send_listack(s, m, "Bridge technology listing will follow", "start");
+
+       AST_RWLIST_RDLOCK(&bridge_technologies);
+       AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+               const char *type;
+
+               type = tech_capability2str(cur->capabilities);
+
+               astman_append(s,
+                       "Event: BridgeTechnologyListItem\r\n"
+                       "BridgeTechnology: %s\r\n"
+                       "BridgeType: %s\r\n"
+                       "BridgePriority: %u\r\n"
+                       "BridgeSuspended: %s\r\n"
+                       "%s"
+                       "\r\n",
+                       cur->name, type, cur->preference, AST_YESNO(cur->suspended),
+                       ast_str_buffer(id_text));
+               ++num_items;
+       }
+       AST_RWLIST_UNLOCK(&bridge_technologies);
+
+       astman_send_list_complete_start(s, m, "BridgeTechnologyListComplete", num_items);
+       astman_send_list_complete_end(s);
+
+       return 0;
+}
+
 /*!
  * \internal
- * \brief Shutdown the bridging system.
+ * \brief Print bridge object key (name).
  * \since 12.0.0
  *
+ * \param v_obj A pointer to the object we want the key printed.
+ * \param where User data needed by prnt to determine where to put output.
+ * \param prnt Print output callback function to use.
+ *
+ * \return Nothing
+ */
+static void bridge_prnt_obj(void *v_obj, void *where, ao2_prnt_fn *prnt)
+{
+       struct ast_bridge *bridge = v_obj;
+
+       if (!bridge) {
+               return;
+       }
+       prnt(where, "%s %s chans:%u",
+               bridge->uniqueid, bridge->v_table->name, bridge->num_channels);
+}
+
+/*!
+ * \internal
+ * \brief Shutdown the bridging system.  Stuff to do on graceful shutdown.
+ * \since 13.3.0
+ *
  * \return Nothing
  */
-static void bridge_shutdown(void)
+static void bridge_cleanup(void)
 {
+       ast_manager_unregister("BridgeTechnologyList");
+       ast_manager_unregister("BridgeTechnologySuspend");
+       ast_manager_unregister("BridgeTechnologyUnsuspend");
        ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+       ao2_container_unregister("bridges");
+
        ao2_cleanup(bridges);
        bridges = NULL;
        ao2_cleanup(bridge_manager);
@@ -4859,7 +5604,7 @@ static void bridge_shutdown(void)
 
 int ast_bridging_init(void)
 {
-       ast_register_atexit(bridge_shutdown);
+       ast_register_cleanup(bridge_cleanup);
 
        if (ast_stasis_bridging_init()) {
                return -1;
@@ -4875,10 +5620,15 @@ int ast_bridging_init(void)
        if (!bridges) {
                return -1;
        }
+       ao2_container_register("bridges", bridges, bridge_prnt_obj);
 
        ast_bridging_init_basic();
 
        ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
 
+       ast_manager_register_xml_core("BridgeTechnologyList", 0, manager_bridge_tech_list);
+       ast_manager_register_xml_core("BridgeTechnologySuspend", 0, manager_bridge_tech_suspend);
+       ast_manager_register_xml_core("BridgeTechnologyUnsuspend", 0, manager_bridge_tech_unsuspend);
+
        return 0;
 }