ARI: Add ability to raise arbitrary User Events
[asterisk/asterisk.git] / main / bridge_channel.c
index cec3d1a..2b37f25 100644 (file)
@@ -35,6 +35,7 @@
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include <signal.h>
+#include <semaphore.h>
 
 #include "asterisk/heap.h"
 #include "asterisk/astobj2.h"
@@ -53,6 +54,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/musiconhold.h"
 #include "asterisk/features_config.h"
 #include "asterisk/parking.h"
+#include "asterisk/causes.h"
+#include "asterisk/test.h"
 
 /*!
  * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge.
@@ -68,6 +71,142 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  */
 typedef int (*ast_bridge_channel_post_action_data)(struct ast_bridge_channel *bridge_channel, enum bridge_channel_action_type action, const void *data, size_t datalen);
 
+/*!
+ * \brief Counter used for assigning synchronous bridge action IDs
+ */
+static int sync_ids;
+
+/*!
+ * \brief Frame payload for synchronous bridge actions.
+ *
+ * The payload serves as a wrapper around the actual payload of the
+ * frame, with the addition of an id used to find the associated
+ * bridge_sync object.
+ */
+struct sync_payload {
+       /*! Unique ID for this synchronous action */
+       unsigned int id;
+       /*! Actual frame data to process */
+       unsigned char data[0];
+};
+
+/*!
+ * \brief Synchronous bridge action object.
+ *
+ * Synchronous bridge actions require the ability for one thread to wait
+ * and for another thread to indicate that the action has completed. This
+ * structure facilitates that goal by providing synchronization structures.
+ */
+struct bridge_sync {
+       /*! Unique ID of this synchronization object. Corresponds with ID in synchronous frame payload */
+       unsigned int id;
+       /*! Semaphore used for synchronization */
+       sem_t sem;
+       /*! Pointer to next entry in the list */
+       AST_LIST_ENTRY(bridge_sync) list;
+};
+
+/*!
+ * \brief List holding active synchronous action objects.
+ */
+static AST_RWLIST_HEAD_STATIC(sync_structs, bridge_sync);
+
+/*!
+ * \brief initialize a synchronous bridge object.
+ *
+ * This both initializes the structure and adds it to the list of
+ * synchronization structures.
+ *
+ * \param sync_struct The synchronization object to initialize.
+ * \param id ID to assign to the synchronization object.
+ */
+static void bridge_sync_init(struct bridge_sync *sync_struct, unsigned int id)
+{
+       memset(sync_struct, 0, sizeof(*sync_struct));
+       sync_struct->id = id;
+       sem_init(&sync_struct->sem, 0, 0);
+
+       AST_RWLIST_WRLOCK(&sync_structs);
+       AST_RWLIST_INSERT_TAIL(&sync_structs, sync_struct, list);
+       AST_RWLIST_UNLOCK(&sync_structs);
+}
+
+/*!
+ * \brief Clean up a syncrhonization bridge object.
+ *
+ * This frees fields within the synchronization object and removes
+ * it from the list of active synchronization objects.
+ *
+ * Since synchronization objects are stack-allocated, it is vital
+ * that this is called before the synchronization object goes
+ * out of scope.
+ *
+ * \param sync_struct Synchronization object to clean up.
+ */
+static void bridge_sync_cleanup(struct bridge_sync *sync_struct)
+{
+       struct bridge_sync *iter;
+
+       AST_RWLIST_WRLOCK(&sync_structs);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&sync_structs, iter, list) {
+               if (iter->id == sync_struct->id) {
+                       AST_LIST_REMOVE_CURRENT(list);
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+       AST_RWLIST_UNLOCK(&sync_structs);
+
+       sem_destroy(&sync_struct->sem);
+}
+
+/*!
+ * \brief Failsafe for synchronous bridge action waiting.
+ *
+ * When waiting for a synchronous bridge action to complete,
+ * if there is a frame resource leak somewhere, it is possible
+ * that we will never get notified that the synchronous action
+ * completed.
+ *
+ * If a significant amount of time passes, then we will abandon
+ * waiting for the synchrnous bridge action to complete.
+ *
+ * This constant represents the number of milliseconds we will
+ * wait for the bridge action to complete.
+ */
+#define PLAYBACK_TIMEOUT (600 * 1000)
+
+/*!
+ * \brief Wait for a synchronous bridge action to complete.
+ *
+ * \param sync_struct Synchronization object corresponding to the bridge action.
+ */
+static void bridge_sync_wait(struct bridge_sync *sync_struct)
+{
+       struct timeval timeout_val = ast_tvadd(ast_tvnow(), ast_samp2tv(PLAYBACK_TIMEOUT, 1000));
+       struct timespec timeout_spec = {
+               .tv_sec = timeout_val.tv_sec,
+               .tv_nsec = timeout_val.tv_usec * 1000,
+       };
+
+       sem_timedwait(&sync_struct->sem, &timeout_spec);
+}
+
+/*!
+ * \brief Signal that waiting for a synchronous bridge action is no longer necessary.
+ *
+ * This may occur for several reasons
+ * \li The synchronous bridge action has completed.
+ * \li The bridge channel has been removed from the bridge.
+ * \li The synchronous bridge action could not be queued.
+ *
+ * \param sync_struct Synchronization object corresponding to the bridge action.
+ */
+static void bridge_sync_signal(struct bridge_sync *sync_struct)
+{
+       sem_post(&sync_struct->sem);
+}
+
 void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel)
 {
        struct ast_bridge *bridge;
@@ -101,41 +240,68 @@ int ast_bridge_channel_notify_talking(struct ast_bridge_channel *bridge_channel,
        return ast_bridge_channel_queue_frame(bridge_channel, &action);
 }
 
-void ast_bridge_channel_leave_bridge(struct ast_bridge_channel *bridge_channel, enum bridge_channel_state new_state)
+/*!
+ * \internal
+ * \brief Poke the bridge_channel thread
+ */
+static void bridge_channel_poke(struct ast_bridge_channel *bridge_channel)
 {
-       ast_bridge_channel_lock(bridge_channel);
-       ast_bridge_channel_leave_bridge_nolock(bridge_channel, new_state);
-       ast_bridge_channel_unlock(bridge_channel);
+       if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
+               /* Wake up the bridge channel thread. */
+               ast_queue_frame(bridge_channel->chan, &ast_null_frame);
+       }
 }
 
-/*! \internal \brief Poke the bridge_channel thread */
-static void bridge_channel_poke(struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Set actual cause on channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to set cause.
+ * \param cause Cause to set on channel.
+ *   If cause <= 0 then use cause on channel if cause still <= 0 use AST_CAUSE_NORMAL_CLEARING.
+ *
+ * \return Actual cause set on channel.
+ */
+static int channel_set_cause(struct ast_channel *chan, int cause)
 {
-       if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
-               while (bridge_channel->waiting) {
-                       pthread_kill(bridge_channel->thread, SIGURG);
-                       sched_yield();
+       ast_channel_lock(chan);
+       if (cause <= 0) {
+               cause = ast_channel_hangupcause(chan);
+               if (cause <= 0) {
+                       cause = AST_CAUSE_NORMAL_CLEARING;
                }
        }
+       ast_channel_hangupcause_set(chan, cause);
+       ast_channel_unlock(chan);
+       return cause;
 }
 
-void ast_bridge_channel_leave_bridge_nolock(struct ast_bridge_channel *bridge_channel, enum bridge_channel_state new_state)
+void ast_bridge_channel_leave_bridge_nolock(struct ast_bridge_channel *bridge_channel, enum bridge_channel_state new_state, int cause)
 {
-/* BUGBUG need cause code for the bridge_channel leaving the bridge. */
        if (bridge_channel->state != BRIDGE_CHANNEL_STATE_WAIT) {
                return;
        }
 
-       ast_debug(1, "Setting %p(%s) state from:%d to:%d\n",
+       ast_debug(1, "Setting %p(%s) state from:%u to:%u\n",
                bridge_channel, ast_channel_name(bridge_channel->chan), bridge_channel->state,
                new_state);
 
+       channel_set_cause(bridge_channel->chan, cause);
+
        /* Change the state on the bridge channel */
        bridge_channel->state = new_state;
 
        bridge_channel_poke(bridge_channel);
 }
 
+void ast_bridge_channel_leave_bridge(struct ast_bridge_channel *bridge_channel, enum bridge_channel_state new_state, int cause)
+{
+       ast_bridge_channel_lock(bridge_channel);
+       ast_bridge_channel_leave_bridge_nolock(bridge_channel, new_state, cause);
+       ast_bridge_channel_unlock(bridge_channel);
+}
+
 struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel)
 {
        struct ast_bridge *bridge = bridge_channel->bridge;
@@ -193,25 +359,28 @@ void ast_bridge_channel_update_linkedids(struct ast_bridge_channel *bridge_chann
 {
        struct ast_bridge_channel *other = NULL;
        struct ast_bridge *bridge = bridge_channel->bridge;
-       const char *oldest_linkedid = ast_channel_linkedid(bridge_channel->chan);
+       struct ast_channel *oldest_linkedid_chan = bridge_channel->chan;
 
        AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
                if (other == swap) {
                        continue;
                }
-               oldest_linkedid = ast_channel_oldest_linkedid(oldest_linkedid, ast_channel_linkedid(other->chan));
-       }
-
-       if (ast_strlen_zero(oldest_linkedid)) {
-               return;
+               oldest_linkedid_chan = ast_channel_internal_oldest_linkedid(
+                       oldest_linkedid_chan, other->chan);
        }
 
-       ast_channel_linkedid_set(bridge_channel->chan, oldest_linkedid);
+       ast_channel_lock(bridge_channel->chan);
+       ast_channel_internal_copy_linkedid(bridge_channel->chan,
+               oldest_linkedid_chan);
+       ast_channel_unlock(bridge_channel->chan);
        AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
                if (other == swap) {
                        continue;
                }
-               ast_channel_linkedid_set(other->chan, oldest_linkedid);
+               ast_channel_lock(other->chan);
+               ast_channel_internal_copy_linkedid(other->chan,
+                       oldest_linkedid_chan);
+               ast_channel_unlock(other->chan);
        }
 }
 
@@ -224,6 +393,7 @@ void ast_bridge_channel_update_accountcodes(struct ast_bridge_channel *bridge_ch
                if (other == swap) {
                        continue;
                }
+               ast_channel_lock_both(bridge_channel->chan, other->chan);
 
                if (!ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan)) && ast_strlen_zero(ast_channel_peeraccount(other->chan))) {
                        ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n",
@@ -257,24 +427,24 @@ void ast_bridge_channel_update_accountcodes(struct ast_bridge_channel *bridge_ch
                                ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
                        }
                }
+               ast_channel_unlock(bridge_channel->chan);
+               ast_channel_unlock(other->chan);
        }
 }
 
-/*!
-* \internal
-* \brief Handle bridge hangup event.
-* \since 12.0.0
-*
-* \param bridge_channel Which channel is hanging up.
-*
-* \return Nothing
-*/
-static void bridge_channel_handle_hangup(struct ast_bridge_channel *bridge_channel)
+void ast_bridge_channel_kick(struct ast_bridge_channel *bridge_channel, int cause)
 {
        struct ast_bridge_features *features = bridge_channel->features;
        struct ast_bridge_hook *hook;
        struct ao2_iterator iter;
 
+       ast_bridge_channel_lock(bridge_channel);
+       if (bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) {
+               channel_set_cause(bridge_channel->chan, cause);
+               cause = 0;
+       }
+       ast_bridge_channel_unlock(bridge_channel);
+
        /* Run any hangup hooks. */
        iter = ao2_iterator_init(features->other_hooks, 0);
        for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
@@ -283,7 +453,7 @@ static void bridge_channel_handle_hangup(struct ast_bridge_channel *bridge_chann
                if (hook->type != AST_BRIDGE_HOOK_TYPE_HANGUP) {
                        continue;
                }
-               remove_me = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+               remove_me = hook->callback(bridge_channel, hook->hook_pvt);
                if (remove_me) {
                        ast_debug(1, "Hangup hook %p is being removed from %p(%s)\n",
                                hook, bridge_channel, ast_channel_name(bridge_channel->chan));
@@ -293,7 +463,7 @@ static void bridge_channel_handle_hangup(struct ast_bridge_channel *bridge_chann
        ao2_iterator_destroy(&iter);
 
        /* Default hangup action. */
-       ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END);
+       ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, cause);
 }
 
 /*!
@@ -309,14 +479,35 @@ static void bridge_channel_handle_hangup(struct ast_bridge_channel *bridge_chann
  */
 static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
+       ast_assert(frame->frametype != AST_FRAME_BRIDGE_ACTION_SYNC);
+
        ast_bridge_channel_lock_bridge(bridge_channel);
 /*
- * BUGBUG need to implement a deferred write queue for when there is no peer channel in the bridge (yet or it was kicked).
+ * XXX need to implement a deferred write queue for when there
+ * is no peer channel in the bridge (yet or it was kicked).
  *
  * The tech decides if a frame needs to be pushed back for deferral.
  * simple_bridge/native_bridge are likely the only techs that will do this.
  */
        bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
+
+       /* Remember any owed events to the bridge. */
+       switch (frame->frametype) {
+       case AST_FRAME_DTMF_BEGIN:
+               bridge_channel->owed.dtmf_tv = ast_tvnow();
+               bridge_channel->owed.dtmf_digit = frame->subclass.integer;
+               break;
+       case AST_FRAME_DTMF_END:
+               bridge_channel->owed.dtmf_digit = '\0';
+               break;
+       case AST_FRAME_CONTROL:
+               /*
+                * We explicitly will not remember HOLD/UNHOLD frames because
+                * things like attended transfers will handle them.
+                */
+       default:
+               break;
+       }
        ast_bridge_unlock(bridge_channel->bridge);
 
        /*
@@ -326,6 +517,108 @@ static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel,
        return 0;
 }
 
+void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct ast_bridge_channel *bridge_channel)
+{
+       if (bridge_channel->owed.dtmf_digit) {
+               struct ast_frame frame = {
+                       .frametype = AST_FRAME_DTMF_END,
+                       .subclass.integer = bridge_channel->owed.dtmf_digit,
+                       .src = "Bridge channel owed DTMF",
+               };
+
+               frame.len = ast_tvdiff_ms(ast_tvnow(), bridge_channel->owed.dtmf_tv);
+               if (frame.len < option_dtmfminduration) {
+                       frame.len = option_dtmfminduration;
+               }
+               ast_log(LOG_DTMF, "DTMF end '%c' simulated to bridge %s because %s left.  Duration %ld ms.\n",
+                       bridge_channel->owed.dtmf_digit, orig_bridge->uniqueid,
+                       ast_channel_name(bridge_channel->chan), frame.len);
+               bridge_channel->owed.dtmf_digit = '\0';
+               orig_bridge->technology->write(orig_bridge, NULL, &frame);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \note This function assumes bridge_channel->bridge is locked.
+ *
+ * \return Nothing
+ */
+void bridge_channel_internal_suspend_nolock(struct ast_bridge_channel *bridge_channel)
+{
+       bridge_channel->suspended = 1;
+       if (bridge_channel->in_bridge) {
+               --bridge_channel->bridge->num_active;
+       }
+
+       /* Get technology bridge threads off of the channel. */
+       if (bridge_channel->bridge->technology->suspend) {
+               bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_suspend(struct ast_bridge_channel *bridge_channel)
+{
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       bridge_channel_internal_suspend_nolock(bridge_channel);
+       ast_bridge_unlock(bridge_channel->bridge);
+}
+
+/*!
+ * \internal
+ * \brief Unsuspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to unsuspend.
+ *
+ * \note This function assumes bridge_channel->bridge is locked.
+ *
+ * \return Nothing
+ */
+void bridge_channel_internal_unsuspend_nolock(struct ast_bridge_channel *bridge_channel)
+{
+       bridge_channel->suspended = 0;
+       if (bridge_channel->in_bridge) {
+               ++bridge_channel->bridge->num_active;
+       }
+
+       /* Wake technology bridge threads to take care of channel again. */
+       if (bridge_channel->bridge->technology->unsuspend) {
+               bridge_channel->bridge->technology->unsuspend(bridge_channel->bridge, bridge_channel);
+       }
+
+       /* Wake suspended channel. */
+       ast_bridge_channel_lock(bridge_channel);
+       ast_cond_signal(&bridge_channel->cond);
+       ast_bridge_channel_unlock(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Unsuspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to unsuspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_unsuspend(struct ast_bridge_channel *bridge_channel)
+{
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       bridge_channel_internal_unsuspend_nolock(bridge_channel);
+       ast_bridge_unlock(bridge_channel->bridge);
+}
+
 /*!
  * \internal
  * \brief Queue an action frame onto the bridge channel with data.
@@ -339,7 +632,8 @@ static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel,
  * \retval 0 on success.
  * \retval -1 on error.
  */
-static int bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum bridge_channel_action_type action, const void *data, size_t datalen)
+static int bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel,
+       enum bridge_channel_action_type action, const void *data, size_t datalen)
 {
        struct ast_frame frame = {
                .frametype = AST_FRAME_BRIDGE_ACTION,
@@ -353,6 +647,52 @@ static int bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_ch
 
 /*!
  * \internal
+ * \brief Queue an action frame onto the bridge channel with data synchronously.
+ * \since 12.2.0
+ *
+ * The function will not return until the queued frame is freed.
+ *
+ * \param bridge_channel Which channel to queue the frame onto.
+ * \param action Type of bridge action frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int bridge_channel_queue_action_data_sync(struct ast_bridge_channel *bridge_channel,
+       enum bridge_channel_action_type action, const void *data, size_t datalen)
+{
+       struct sync_payload *sync_payload;
+       int sync_payload_len = sizeof(*sync_payload) + datalen;
+       struct bridge_sync sync_struct;
+       struct ast_frame frame = {
+               .frametype = AST_FRAME_BRIDGE_ACTION_SYNC,
+               .subclass.integer = action,
+       };
+
+       /* Make sure we don't end up trying to wait on ourself to deliver the frame */
+       ast_assert(!pthread_equal(pthread_self(), bridge_channel->thread));
+
+       sync_payload = ast_alloca(sync_payload_len);
+       sync_payload->id = ast_atomic_fetchadd_int(&sync_ids, +1);
+       memcpy(sync_payload->data, data, datalen);
+
+       frame.datalen = sync_payload_len;
+       frame.data.ptr = sync_payload;
+
+       bridge_sync_init(&sync_struct, sync_payload->id);
+       if (ast_bridge_channel_queue_frame(bridge_channel, &frame)) {
+               bridge_sync_cleanup(&sync_struct);
+               return -1;
+       }
+
+       bridge_sync_wait(&sync_struct);
+       bridge_sync_cleanup(&sync_struct);
+       return 0;
+}
+/*!
+ * \internal
  * \brief Write an action frame onto the bridge channel with data.
  * \since 12.0.0
  *
@@ -364,7 +704,8 @@ static int bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_ch
  * \retval 0 on success.
  * \retval -1 on error.
  */
-static int bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum bridge_channel_action_type action, const void *data, size_t datalen)
+static int bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel,
+       enum bridge_channel_action_type action, const void *data, size_t datalen)
 {
        struct ast_frame frame = {
                .frametype = AST_FRAME_BRIDGE_ACTION,
@@ -376,6 +717,27 @@ static int bridge_channel_write_action_data(struct ast_bridge_channel *bridge_ch
        return bridge_channel_write_frame(bridge_channel, &frame);
 }
 
+static void bridge_frame_free(struct ast_frame *frame)
+{
+       if (frame->frametype == AST_FRAME_BRIDGE_ACTION_SYNC) {
+               struct sync_payload *sync_payload = frame->data.ptr;
+               struct bridge_sync *sync;
+
+               AST_RWLIST_RDLOCK(&sync_structs);
+               AST_RWLIST_TRAVERSE(&sync_structs, sync, list) {
+                       if (sync->id == sync_payload->id) {
+                               break;
+                       }
+               }
+               if (sync) {
+                       bridge_sync_signal(sync);
+               }
+               AST_RWLIST_UNLOCK(&sync_structs);
+       }
+
+       ast_frfree(frame);
+}
+
 int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
 {
        struct ast_frame *dup;
@@ -403,7 +765,7 @@ int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, st
        if (bridge_channel->state != BRIDGE_CHANNEL_STATE_WAIT) {
                /* Drop frames on channels leaving the bridge. */
                ast_bridge_channel_unlock(bridge_channel);
-               ast_frfree(dup);
+               bridge_frame_free(dup);
                return 0;
        }
 
@@ -476,18 +838,25 @@ int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, con
                datalen = 0;
        }
 
+       ast_channel_lock(bridge_channel->chan);
        ast_channel_publish_blob(bridge_channel->chan, ast_channel_hold_type(), blob);
+       ast_channel_unlock(bridge_channel->chan);
        return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
                moh_class, datalen);
 }
 
 int ast_bridge_channel_write_unhold(struct ast_bridge_channel *bridge_channel)
 {
+       ast_channel_lock(bridge_channel->chan);
        ast_channel_publish_blob(bridge_channel->chan, ast_channel_unhold_type(), NULL);
+       ast_channel_unlock(bridge_channel->chan);
        return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, NULL, 0);
 }
 
-/*! \internal \brief Helper function to kick off a PBX app on a bridge_channel */
+/*!
+ * \internal
+ * \brief Helper function to kick off a PBX app on a bridge_channel
+ */
 static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args)
 {
        int res = 0;
@@ -516,7 +885,7 @@ void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const
        }
        if (run_app_helper(bridge_channel->chan, app_name, S_OR(app_args, ""))) {
                /* Break the bridge if the app returns non-zero. */
-               bridge_channel_handle_hangup(bridge_channel);
+               ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
        }
        if (moh_class) {
                ast_bridge_channel_write_unhold(bridge_channel);
@@ -606,14 +975,14 @@ void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_
        /*
         * It may be necessary to resume music on hold after we finish
         * playing the announcment.
-        *
-        * XXX We have no idea what MOH class was in use before playing
-        * the file. This method also fails to restore ringing indications.
-        * the proposed solution is to create a resume_entertainment callback
-        * for the bridge technology and execute it here.
         */
        if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
-               ast_moh_start(bridge_channel->chan, NULL, NULL);
+               const char *latest_musicclass;
+
+               ast_channel_lock(bridge_channel->chan);
+               latest_musicclass = ast_strdupa(ast_channel_latest_musicclass(bridge_channel->chan));
+               ast_channel_unlock(bridge_channel->chan);
+               ast_moh_start(bridge_channel->chan, latest_musicclass, NULL);
        }
 }
 
@@ -655,7 +1024,7 @@ static int payload_helper_playfile(ast_bridge_channel_post_action_data post_it,
        size_t len_payload = sizeof(*payload) + len_name + len_moh;
 
        /* Fill in play file frame data. */
-       payload = alloca(len_payload);
+       payload = ast_alloca(len_payload);
        payload->custom_play = custom_play;
        payload->moh_offset = len_moh ? len_name : 0;
        strcpy(payload->playfile, playfile);/* Safe */
@@ -678,11 +1047,20 @@ int ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel,
                bridge_channel, custom_play, playfile, moh_class);
 }
 
+int ast_bridge_channel_queue_playfile_sync(struct ast_bridge_channel *bridge_channel,
+               ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+       return payload_helper_playfile(bridge_channel_queue_action_data_sync,
+               bridge_channel, custom_play, playfile, moh_class);
+}
+
 struct bridge_custom_callback {
        /*! Call this function on the bridge channel thread. */
        ast_bridge_custom_callback_fn callback;
        /*! Size of the payload if it exists.  A number otherwise. */
        size_t payload_size;
+       /*! Option flags determining how callback is called. */
+       unsigned int flags;
        /*! Nonzero if the payload exists. */
        char payload_exists;
        /*! Payload to give to callback. */
@@ -694,14 +1072,22 @@ struct bridge_custom_callback {
  * \brief Handle the do custom callback bridge action.
  * \since 12.0.0
  *
- * \param bridge_channel Which channel to run the application on.
- * \param data Action frame data to run the application.
+ * \param bridge_channel Which channel to call the callback on.
+ * \param data Action frame data to call the callback.
  *
  * \return Nothing
  */
 static void bridge_channel_do_callback(struct ast_bridge_channel *bridge_channel, struct bridge_custom_callback *data)
 {
+       if (ast_test_flag(data, AST_BRIDGE_CHANNEL_CB_OPTION_MEDIA)) {
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+       }
        data->callback(bridge_channel, data->payload_exists ? data->payload : NULL, data->payload_size);
+       if (ast_test_flag(data, AST_BRIDGE_CHANNEL_CB_OPTION_MEDIA)) {
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+       }
 }
 
 /*!
@@ -709,7 +1095,9 @@ static void bridge_channel_do_callback(struct ast_bridge_channel *bridge_channel
  * \brief Marshal a custom callback function to be called on a bridge_channel
  */
 static int payload_helper_cb(ast_bridge_channel_post_action_data post_it,
-       struct ast_bridge_channel *bridge_channel, ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
+       struct ast_bridge_channel *bridge_channel,
+       enum ast_bridge_channel_custom_callback_option flags,
+       ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
 {
        struct bridge_custom_callback *cb_data;
        size_t len_data = sizeof(*cb_data) + (payload ? payload_size : 0);
@@ -724,6 +1112,7 @@ static int payload_helper_cb(ast_bridge_channel_post_action_data post_it,
        cb_data = alloca(len_data);
        cb_data->callback = callback;
        cb_data->payload_size = payload_size;
+       cb_data->flags = flags;
        cb_data->payload_exists = payload && payload_size;
        if (cb_data->payload_exists) {
                memcpy(cb_data->payload, payload, payload_size);/* Safe */
@@ -732,16 +1121,20 @@ static int payload_helper_cb(ast_bridge_channel_post_action_data post_it,
        return post_it(bridge_channel, BRIDGE_CHANNEL_ACTION_CALLBACK, cb_data, len_data);
 }
 
-int ast_bridge_channel_write_callback(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
+int ast_bridge_channel_write_callback(struct ast_bridge_channel *bridge_channel,
+       enum ast_bridge_channel_custom_callback_option flags,
+       ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
 {
        return payload_helper_cb(bridge_channel_write_action_data,
-               bridge_channel, callback, payload, payload_size);
+               bridge_channel, flags, callback, payload, payload_size);
 }
 
-int ast_bridge_channel_queue_callback(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
+int ast_bridge_channel_queue_callback(struct ast_bridge_channel *bridge_channel,
+       enum ast_bridge_channel_custom_callback_option flags,
+       ast_bridge_custom_callback_fn callback, const void *payload, size_t payload_size)
 {
        return payload_helper_cb(bridge_channel_queue_action_data,
-               bridge_channel, callback, payload, payload_size);
+               bridge_channel, flags, callback, payload, payload_size);
 }
 
 struct bridge_park {
@@ -757,9 +1150,18 @@ struct bridge_park {
  */
 static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload)
 {
-       ast_bridge_channel_park(bridge_channel, payload->parkee_uuid,
+       if (!ast_parking_provider_registered()) {
+               ast_log(AST_LOG_WARNING, "Unable to park %s: No parking provider loaded!\n",
+                       ast_channel_name(bridge_channel->chan));
+               return;
+       }
+
+       if (ast_parking_park_bridge_channel(bridge_channel, payload->parkee_uuid,
                &payload->parkee_uuid[payload->parker_uuid_offset],
-               payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL);
+               payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL)) {
+               ast_log(AST_LOG_WARNING, "Error occurred while parking %s\n",
+                       ast_channel_name(bridge_channel->chan));
+       }
 }
 
 /*!
@@ -798,87 +1200,6 @@ int ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, con
 
 /*!
  * \internal
- * \brief Suspend a channel from a bridge.
- *
- * \param bridge_channel Channel to suspend.
- *
- * \note This function assumes bridge_channel->bridge is locked.
- *
- * \return Nothing
- */
-void bridge_channel_internal_suspend_nolock(struct ast_bridge_channel *bridge_channel)
-{
-       bridge_channel->suspended = 1;
-       if (bridge_channel->in_bridge) {
-               --bridge_channel->bridge->num_active;
-       }
-
-       /* Get technology bridge threads off of the channel. */
-       if (bridge_channel->bridge->technology->suspend) {
-               bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel);
-       }
-}
-
-/*!
- * \internal
- * \brief Suspend a channel from a bridge.
- *
- * \param bridge_channel Channel to suspend.
- *
- * \return Nothing
- */
-static void bridge_channel_suspend(struct ast_bridge_channel *bridge_channel)
-{
-       ast_bridge_channel_lock_bridge(bridge_channel);
-       bridge_channel_internal_suspend_nolock(bridge_channel);
-       ast_bridge_unlock(bridge_channel->bridge);
-}
-
-/*!
- * \internal
- * \brief Unsuspend a channel from a bridge.
- *
- * \param bridge_channel Channel to unsuspend.
- *
- * \note This function assumes bridge_channel->bridge is locked.
- *
- * \return Nothing
- */
-void bridge_channel_internal_unsuspend_nolock(struct ast_bridge_channel *bridge_channel)
-{
-       bridge_channel->suspended = 0;
-       if (bridge_channel->in_bridge) {
-               ++bridge_channel->bridge->num_active;
-       }
-
-       /* Wake technology bridge threads to take care of channel again. */
-       if (bridge_channel->bridge->technology->unsuspend) {
-               bridge_channel->bridge->technology->unsuspend(bridge_channel->bridge, bridge_channel);
-       }
-
-       /* Wake suspended channel. */
-       ast_bridge_channel_lock(bridge_channel);
-       ast_cond_signal(&bridge_channel->cond);
-       ast_bridge_channel_unlock(bridge_channel);
-}
-
-/*!
- * \internal
- * \brief Unsuspend a channel from a bridge.
- *
- * \param bridge_channel Channel to unsuspend.
- *
- * \return Nothing
- */
-static void bridge_channel_unsuspend(struct ast_bridge_channel *bridge_channel)
-{
-       ast_bridge_channel_lock_bridge(bridge_channel);
-       bridge_channel_internal_unsuspend_nolock(bridge_channel);
-       ast_bridge_unlock(bridge_channel->bridge);
-}
-
-/*!
- * \internal
  * \brief Handle bridge channel interval expiration.
  * \since 12.0.0
  *
@@ -891,7 +1212,7 @@ static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_cha
        struct ast_heap *interval_hooks;
        struct ast_bridge_hook_timer *hook;
        struct timeval start;
-       int hook_run = 0;
+       int chan_suspended = 0;
 
        interval_hooks = bridge_channel->features->interval_hooks;
        ast_heap_wrlock(interval_hooks);
@@ -908,16 +1229,16 @@ static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_cha
                ao2_ref(hook, +1);
                ast_heap_unlock(interval_hooks);
 
-               if (!hook_run) {
-                       hook_run = 1;
+               if (!chan_suspended
+                       && ast_test_flag(&hook->timer, AST_BRIDGE_HOOK_TIMER_OPTION_MEDIA)) {
+                       chan_suspended = 1;
                        bridge_channel_suspend(bridge_channel);
                        ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                }
 
                ast_debug(1, "Executing hook %p on %p(%s)\n",
                        hook, bridge_channel, ast_channel_name(bridge_channel->chan));
-               interval = hook->generic.callback(bridge_channel->bridge, bridge_channel,
-                       hook->generic.hook_pvt);
+               interval = hook->generic.callback(bridge_channel, hook->generic.hook_pvt);
 
                ast_heap_wrlock(interval_hooks);
                if (ast_heap_peek(interval_hooks, hook->timer.heap_index) != hook
@@ -965,13 +1286,16 @@ static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_cha
        }
        ast_heap_unlock(interval_hooks);
 
-       if (hook_run) {
+       if (chan_suspended) {
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                bridge_channel_unsuspend(bridge_channel);
        }
 }
 
-/*! \internal \brief Write a DTMF stream out to a channel */
+/*!
+ * \internal
+ * \brief Write a DTMF stream out to a channel
+ */
 static int bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
 {
        return bridge_channel_write_action_data(bridge_channel,
@@ -979,16 +1303,58 @@ static int bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_ch
 }
 
 /*!
- * \internal \brief Internal function that executes a feature on a bridge channel
+ * \internal
+ * \brief Indicate to the testsuite a feature was successfully detected.
+ *
+ * Currently, this function only will relay built-in features to the testsuite,
+ * but it could be modified to detect applicationmap items should the need arise.
+ *
+ * \param chan The channel that activated the feature
+ * \param dtmf The DTMF sequence entered to activate the feature
+ */
+static void testsuite_notify_feature_success(struct ast_channel *chan, const char *dtmf)
+{
+#ifdef TEST_FRAMEWORK
+       char *feature = "unknown";
+       struct ast_featuremap_config *featuremap = ast_get_chan_featuremap_config(chan);
+       struct ast_features_xfer_config *xfer = ast_get_chan_features_xfer_config(chan);
+
+       if (featuremap) {
+               if (!strcmp(dtmf, featuremap->blindxfer)) {
+                       feature = "blindxfer";
+               } else if (!strcmp(dtmf, featuremap->atxfer)) {
+                       feature = "atxfer";
+               } else if (!strcmp(dtmf, featuremap->disconnect)) {
+                       feature = "disconnect";
+               } else if (!strcmp(dtmf, featuremap->automon)) {
+                       feature = "automon";
+               } else if (!strcmp(dtmf, featuremap->automixmon)) {
+                       feature = "automixmon";
+               } else if (!strcmp(dtmf, featuremap->parkcall)) {
+                       feature = "parkcall";
+               } else if (!strcmp(dtmf, xfer->atxferthreeway)) {
+                       feature = "atxferthreeway";
+               }
+       }
+
+       ast_test_suite_event_notify("FEATURE_DETECTION",
+                       "Result: success\r\n"
+                       "Feature: %s", feature);
+#endif /* TEST_FRAMEWORK */
+}
+
+/*!
+ * \internal
+ * \brief Internal function that executes a feature on a bridge channel
  * \note Neither the bridge nor the bridge_channel locks should be held when entering
  * this function.
  */
-static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel, const char *starting_dtmf)
 {
        struct ast_bridge_features *features = bridge_channel->features;
        struct ast_bridge_hook_dtmf *hook = NULL;
-       char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
-       size_t dtmf_len = 0;
+       char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
+       size_t dtmf_len;
        unsigned int digit_timeout;
        RAII_VAR(struct ast_features_general_config *, gen_cfg, NULL, ao2_cleanup);
 
@@ -1002,14 +1368,46 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
        digit_timeout = gen_cfg->featuredigittimeout;
        ast_channel_unlock(bridge_channel->chan);
 
-       /* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */
-       ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
+       if (ast_strlen_zero(starting_dtmf)) {
+               dtmf[0] = '\0';
+               dtmf_len = 0;
+       } else {
+               ast_copy_string(dtmf, starting_dtmf, sizeof(dtmf));
+               dtmf_len = strlen(dtmf);
+       }
 
-       /* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */
-       do {
+       /*
+        * Check if any feature DTMF hooks match or could match and
+        * try to collect more DTMF digits.
+        */
+       for (;;) {
                int res;
 
-               /* If the above timed out simply exit */
+               if (dtmf_len) {
+                       ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n",
+                               bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
+
+                       /* See if a DTMF feature hook matches or can match */
+                       hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
+                       if (!hook) {
+                               ast_debug(1, "No DTMF feature hooks on %p(%s) match '%s'\n",
+                                       bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
+                               break;
+                       }
+                       if (strlen(hook->dtmf.code) == dtmf_len) {
+                               ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n",
+                                       hook, dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
+                               break;
+                       }
+                       ao2_ref(hook, -1);
+                       hook = NULL;
+
+                       if (ARRAY_LEN(dtmf) - 1 <= dtmf_len) {
+                               /* We have reached the maximum length of a DTMF feature string. */
+                               break;
+                       }
+               }
+
                res = ast_waitfordigit(bridge_channel->chan, digit_timeout);
                if (!res) {
                        ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
@@ -1022,44 +1420,22 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
                        break;
                }
 
-/* BUGBUG need to record the duration of DTMF digits so when the string is played back, they are reproduced. */
-               /* Add the above DTMF into the DTMF string so we can do our matching */
-               dtmf[dtmf_len++] = res;
-               ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n",
-                       bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
-
-               /* See if a DTMF feature hook matches or can match */
-               hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
-               if (!hook) {
-                       ast_debug(1, "No DTMF feature hooks on %p(%s) match '%s'\n",
-                               bridge_channel, ast_channel_name(bridge_channel->chan), dtmf);
-                       break;
-               }
-               if (strlen(hook->dtmf.code) == dtmf_len) {
-                       ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n",
-                               hook, dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
-                       break;
-               }
-               ao2_ref(hook, -1);
-               hook = NULL;
-
-               /* Stop if we have reached the maximum length of a DTMF feature string. */
-       } while (dtmf_len < ARRAY_LEN(dtmf) - 1);
-
-       /* Since we are done bringing DTMF in return to using both begin and end frames */
-       ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY);
+               /* Add the new DTMF into the DTMF string so we can do our matching */
+               dtmf[dtmf_len] = res;
+               dtmf[++dtmf_len] = '\0';
+       }
 
-       /* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */
        if (hook) {
                int remove_me;
 
-               remove_me = hook->generic.callback(bridge_channel->bridge, bridge_channel,
-                       hook->generic.hook_pvt);
+               /* Execute the matched hook on this channel. */
+               remove_me = hook->generic.callback(bridge_channel, hook->generic.hook_pvt);
                if (remove_me) {
                        ast_debug(1, "DTMF hook %p is being removed from %p(%s)\n",
                                hook, bridge_channel, ast_channel_name(bridge_channel->chan));
                        ao2_unlink(features->dtmf_hooks, hook);
                }
+               testsuite_notify_feature_success(bridge_channel->chan, hook->dtmf.code);
                ao2_ref(hook, -1);
 
                /*
@@ -1069,14 +1445,21 @@ static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel)
                 * here if the hook did not already change the state.
                 */
                if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
-                       bridge_channel_handle_hangup(bridge_channel);
+                       ast_bridge_channel_kick(bridge_channel, 0);
                }
-       } else if (features->dtmf_passthrough) {
-               bridge_channel_write_dtmf_stream(bridge_channel, dtmf);
+       } else {
+               if (features->dtmf_passthrough) {
+                       /* Stream any collected DTMF to the other channels. */
+                       bridge_channel_write_dtmf_stream(bridge_channel, dtmf);
+               }
+               ast_test_suite_event_notify("FEATURE_DETECTION", "Result: fail");
        }
 }
 
-/*! \internal \brief Indicate that a bridge_channel is talking */
+/*!
+ * \internal
+ * \brief Indicate that a bridge_channel is talking
+ */
 static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking)
 {
        struct ast_bridge_features *features = bridge_channel->features;
@@ -1117,7 +1500,10 @@ struct blind_transfer_data {
        char context[AST_MAX_CONTEXT];
 };
 
-/*! \internal \brief Execute after bridge actions on a channel when it leaves a bridge */
+/*!
+ * \internal
+ * \brief Execute after bridge actions on a channel when it leaves a bridge
+ */
 static void after_bridge_move_channel(struct ast_channel *chan_bridged, void *data)
 {
        RAII_VAR(struct ast_channel *, chan_target, data, ao2_cleanup);
@@ -1154,7 +1540,10 @@ static void after_bridge_move_channel(struct ast_channel *chan_bridged, void *da
        ast_party_connected_line_free(&connected_target);
 }
 
-/*! \internal \brief Execute logic to cleanup when after bridge fails */
+/*!
+ * \internal
+ * \brief Execute logic to cleanup when after bridge fails
+ */
 static void after_bridge_move_channel_fail(enum ast_bridge_after_cb_reason reason, void *data)
 {
        RAII_VAR(struct ast_channel *, chan_target, data, ao2_cleanup);
@@ -1164,15 +1553,21 @@ static void after_bridge_move_channel_fail(enum ast_bridge_after_cb_reason reaso
        ast_softhangup(chan_target, AST_SOFTHANGUP_DEV);
 }
 
-/*! \internal \brief Perform a blind transfer on a channel in a bridge */
+/*!
+ * \internal
+ * \brief Perform a blind transfer on a channel in a bridge
+ */
 static void bridge_channel_blind_transfer(struct ast_bridge_channel *bridge_channel,
                struct blind_transfer_data *blind_data)
 {
        ast_async_goto(bridge_channel->chan, blind_data->context, blind_data->exten, 1);
-       bridge_channel_handle_hangup(bridge_channel);
+       ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
 }
 
-/*! \internal \brief Perform an attended transfer on a channel in a bridge */
+/*!
+ * \internal
+ * \brief Perform an attended transfer on a channel in a bridge
+ */
 static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_channel,
                const char *target_chan_name)
 {
@@ -1182,7 +1577,7 @@ static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_c
        chan_target = ast_channel_get_by_name(target_chan_name);
        if (!chan_target) {
                /* Dang, it disappeared somehow */
-               bridge_channel_handle_hangup(bridge_channel);
+               ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
                return;
        }
 
@@ -1199,7 +1594,7 @@ static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_c
                /* Release the ref we tried to pass to ast_bridge_set_after_callback(). */
                ast_channel_unref(chan_target);
        }
-       bridge_channel_handle_hangup(bridge_channel);
+       ast_bridge_channel_kick(bridge_channel, AST_CAUSE_NORMAL_CLEARING);
 }
 
 /*!
@@ -1209,64 +1604,55 @@ static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_c
  *
  * \param bridge_channel Channel to execute the action on.
  * \param action What to do.
+ * \param data data from the action.
  *
  * \return Nothing
  */
-static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel, struct ast_frame *action)
+static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel,
+       enum bridge_channel_action_type action, void *data)
 {
-       switch (action->subclass.integer) {
-       case BRIDGE_CHANNEL_ACTION_FEATURE:
-               bridge_channel_suspend(bridge_channel);
-               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_feature(bridge_channel);
-               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_unsuspend(bridge_channel);
-               break;
+       switch (action) {
        case BRIDGE_CHANNEL_ACTION_DTMF_STREAM:
                bridge_channel_suspend(bridge_channel);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_dtmf_stream(bridge_channel, action->data.ptr);
+               bridge_channel_dtmf_stream(bridge_channel, data);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                bridge_channel_unsuspend(bridge_channel);
                break;
        case BRIDGE_CHANNEL_ACTION_TALKING_START:
        case BRIDGE_CHANNEL_ACTION_TALKING_STOP:
                bridge_channel_talking(bridge_channel,
-                       action->subclass.integer == BRIDGE_CHANNEL_ACTION_TALKING_START);
+                       action == BRIDGE_CHANNEL_ACTION_TALKING_START);
                break;
        case BRIDGE_CHANNEL_ACTION_PLAY_FILE:
                bridge_channel_suspend(bridge_channel);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_playfile(bridge_channel, action->data.ptr);
+               bridge_channel_playfile(bridge_channel, data);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                bridge_channel_unsuspend(bridge_channel);
                break;
        case BRIDGE_CHANNEL_ACTION_RUN_APP:
                bridge_channel_suspend(bridge_channel);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_run_app(bridge_channel, action->data.ptr);
+               bridge_channel_run_app(bridge_channel, data);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                bridge_channel_unsuspend(bridge_channel);
                break;
        case BRIDGE_CHANNEL_ACTION_CALLBACK:
-               bridge_channel_suspend(bridge_channel);
-               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_do_callback(bridge_channel, action->data.ptr);
-               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_unsuspend(bridge_channel);
+               bridge_channel_do_callback(bridge_channel, data);
                break;
        case BRIDGE_CHANNEL_ACTION_PARK:
                bridge_channel_suspend(bridge_channel);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
-               bridge_channel_park(bridge_channel, action->data.ptr);
+               bridge_channel_park(bridge_channel, data);
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                bridge_channel_unsuspend(bridge_channel);
                break;
        case BRIDGE_CHANNEL_ACTION_BLIND_TRANSFER:
-               bridge_channel_blind_transfer(bridge_channel, action->data.ptr);
+               bridge_channel_blind_transfer(bridge_channel, data);
                break;
        case BRIDGE_CHANNEL_ACTION_ATTENDED_TRANSFER:
-               bridge_channel_attended_transfer(bridge_channel, action->data.ptr);
+               bridge_channel_attended_transfer(bridge_channel, data);
                break;
        default:
                break;
@@ -1295,7 +1681,7 @@ static void bridge_channel_dissolve_check(struct ast_bridge_channel *bridge_chan
        if (!bridge->num_channels
                && ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) {
                /* Last channel leaving the bridge turns off the lights. */
-               bridge_dissolve(bridge);
+               bridge_dissolve(bridge, ast_channel_hangupcause(bridge_channel->chan));
                return;
        }
 
@@ -1306,14 +1692,24 @@ static void bridge_channel_dissolve_check(struct ast_bridge_channel *bridge_chan
                        || (bridge_channel->features->usable
                                && ast_test_flag(&bridge_channel->features->feature_flags,
                                        AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP))) {
-                       bridge_dissolve(bridge);
+                       bridge_dissolve(bridge, ast_channel_hangupcause(bridge_channel->chan));
                        return;
                }
                break;
        default:
                break;
        }
-/* BUGBUG need to implement AST_BRIDGE_CHANNEL_FLAG_LONELY support here */
+
+       if (bridge->num_lonely && bridge->num_lonely == bridge->num_channels) {
+               /*
+                * This will start a chain reaction where each channel leaving
+                * enters this function and causes the next to leave as long as
+                * there aren't non-lonely channels in the bridge.
+                */
+               ast_bridge_channel_leave_bridge(AST_LIST_FIRST(&bridge->channels),
+                       BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE,
+                       ast_channel_hangupcause(bridge_channel->chan));
+       }
 }
 
 void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel)
@@ -1334,8 +1730,6 @@ void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel)
                bridge->v_table->name,
                bridge->uniqueid);
 
-/* BUGBUG This is where incoming HOLD/UNHOLD memory should write UNHOLD into bridge. (if not local optimizing) */
-/* BUGBUG This is where incoming DTMF begin/end memory should write DTMF end into bridge. (if not local optimizing) */
        if (!bridge_channel->just_joined) {
                /* Tell the bridge technology we are leaving so they tear us down */
                ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology\n",
@@ -1350,6 +1744,9 @@ void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel)
        if (!bridge_channel->suspended) {
                --bridge->num_active;
        }
+       if (ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
+               --bridge->num_lonely;
+       }
        --bridge->num_channels;
        AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
        bridge->v_table->pull(bridge, bridge_channel);
@@ -1360,8 +1757,9 @@ void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel)
         * outgoing channel, clear the outgoing flag.
         */
        if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING)
-                       && (ast_channel_softhangup_internal_flag(bridge_channel->chan) &
-                               (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))) {
+                       && (ast_channel_softhangup_internal_flag(bridge_channel->chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE)
+                           || bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT)) {
+               ast_debug(2, "Channel %s will survive this bridge; clearing outgoing (dialed) flag\n", ast_channel_name(bridge_channel->chan));
                ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_OUTGOING);
        }
 
@@ -1398,13 +1796,15 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
                || ast_bridge_channel_establish_roles(bridge_channel)) {
                ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n",
                        bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
-               ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
                return -1;
        }
        bridge_channel->in_bridge = 1;
        bridge_channel->just_joined = 1;
        AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry);
        ++bridge->num_channels;
+       if (ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_LONELY)) {
+               ++bridge->num_lonely;
+       }
        if (!bridge_channel->suspended) {
                ++bridge->num_active;
        }
@@ -1418,9 +1818,9 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
                bridge->v_table->name,
                bridge->uniqueid);
 
-       ast_bridge_publish_enter(bridge, bridge_channel->chan);
+       ast_bridge_publish_enter(bridge, bridge_channel->chan, swap ? swap->chan : NULL);
        if (swap) {
-               ast_bridge_channel_leave_bridge(swap, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE);
+               ast_bridge_channel_leave_bridge(swap, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, 0);
                bridge_channel_internal_pull(swap);
        }
 
@@ -1450,46 +1850,23 @@ static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_chan
        struct ast_channel *chan;
        struct ast_option_header *aoh;
        int is_caller;
-       int intercept_failed;
 
        chan = bridge_channel->chan;
        switch (fr->subclass.integer) {
        case AST_CONTROL_REDIRECTING:
                is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
-               bridge_channel_suspend(bridge_channel);
-               intercept_failed = ast_channel_redirecting_sub(NULL, chan, fr, 1)
-                       && ast_channel_redirecting_macro(NULL, chan, fr, is_caller, 1);
-               bridge_channel_unsuspend(bridge_channel);
-               if (intercept_failed) {
+               if (ast_channel_redirecting_sub(NULL, chan, fr, 1) &&
+                       ast_channel_redirecting_macro(NULL, chan, fr, is_caller, 1)) {
                        ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
                }
                break;
        case AST_CONTROL_CONNECTED_LINE:
                is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING);
-               bridge_channel_suspend(bridge_channel);
-               intercept_failed = ast_channel_connected_line_sub(NULL, chan, fr, 1)
-                       && ast_channel_connected_line_macro(NULL, chan, fr, is_caller, 1);
-               bridge_channel_unsuspend(bridge_channel);
-               if (intercept_failed) {
+               if (ast_channel_connected_line_sub(NULL, chan, fr, 1) &&
+                       ast_channel_connected_line_macro(NULL, chan, fr, is_caller, 1)) {
                        ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
                }
                break;
-       case AST_CONTROL_HOLD:
-       case AST_CONTROL_UNHOLD:
-/*
- * BUGBUG bridge_channels should remember sending/receiving an outstanding HOLD to/from the bridge
- *
- * When the sending channel is pulled from the bridge it needs to write into the bridge an UNHOLD before being pulled.
- * When the receiving channel is pulled from the bridge it needs to generate its own UNHOLD.
- * Something similar needs to be done for DTMF begin/end.
- */
-       case AST_CONTROL_VIDUPDATE:
-       case AST_CONTROL_SRCUPDATE:
-       case AST_CONTROL_SRCCHANGE:
-       case AST_CONTROL_T38_PARAMETERS:
-/* BUGBUG may have to do something with a jitter buffer for these. */
-               ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
-               break;
        case AST_CONTROL_OPTION:
                /*
                 * Forward option Requests, but only ones we know are safe These
@@ -1540,6 +1917,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
 {
        struct ast_frame *fr;
        char nudge;
+       struct sync_payload *sync_payload;
 
        ast_bridge_channel_lock(bridge_channel);
        if (read(bridge_channel->alert_pipe[0], &nudge, sizeof(nudge)) < 0) {
@@ -1555,7 +1933,11 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
        }
        switch (fr->frametype) {
        case AST_FRAME_BRIDGE_ACTION:
-               bridge_channel_handle_action(bridge_channel, fr);
+               bridge_channel_handle_action(bridge_channel, fr->subclass.integer, fr->data.ptr);
+               break;
+       case AST_FRAME_BRIDGE_ACTION_SYNC:
+               sync_payload = fr->data.ptr;
+               bridge_channel_handle_action(bridge_channel, fr->subclass.integer, sync_payload->data);
                break;
        case AST_FRAME_CONTROL:
                bridge_channel_handle_control(bridge_channel, fr);
@@ -1568,7 +1950,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
                ast_write(bridge_channel->chan, fr);
                break;
        }
-       ast_frfree(fr);
+       bridge_frame_free(fr);
 }
 
 /*! \brief Internal function to handle DTMF from a channel */
@@ -1578,22 +1960,42 @@ static struct ast_frame *bridge_handle_dtmf(struct ast_bridge_channel *bridge_ch
        struct ast_bridge_hook_dtmf *hook;
        char dtmf[2];
 
-/* BUGBUG the feature hook matching needs to be done here.  Any matching feature hook needs to be queued onto the bridge_channel.  Also the feature hook digit timeout needs to be handled. */
-/* BUGBUG the AMI atxfer action just sends DTMF end events to initiate DTMF atxfer and dial the extension.  Another reason the DTMF hook matching needs rework. */
-       /* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */
+       /* See if this DTMF matches the beginning of any feature hooks. */
        dtmf[0] = frame->subclass.integer;
        dtmf[1] = '\0';
        hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
        if (hook) {
-               struct ast_frame action = {
-                       .frametype = AST_FRAME_BRIDGE_ACTION,
-                       .subclass.integer = BRIDGE_CHANNEL_ACTION_FEATURE,
-               };
+               enum ast_frame_type frametype = frame->frametype;
 
-               ast_frfree(frame);
+               bridge_frame_free(frame);
                frame = NULL;
-               ast_bridge_channel_queue_frame(bridge_channel, &action);
+
                ao2_ref(hook, -1);
+
+               /* Collect any more needed DTMF to execute a hook. */
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               switch (frametype) {
+               case AST_FRAME_DTMF_BEGIN:
+                       bridge_channel_feature(bridge_channel, NULL);
+                       break;
+               case AST_FRAME_DTMF_END:
+                       bridge_channel_feature(bridge_channel, dtmf);
+                       break;
+               default:
+                       /* Unexpected frame type. */
+                       ast_assert(0);
+                       break;
+               }
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+#ifdef TEST_FRAMEWORK
+       } else if (frame->frametype == AST_FRAME_DTMF_END) {
+               /* Only transmit this event on DTMF end or else every DTMF
+                * press will result in the event being broadcast twice
+                */
+               ast_test_suite_event_notify("FEATURE_DETECTION", "Result: fail");
+#endif
        }
 
        return frame;
@@ -1617,43 +2019,38 @@ static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
        }
 
        if (!frame) {
-               bridge_channel_handle_hangup(bridge_channel);
+               ast_bridge_channel_kick(bridge_channel, 0);
                return;
        }
        switch (frame->frametype) {
        case AST_FRAME_CONTROL:
                switch (frame->subclass.integer) {
                case AST_CONTROL_HANGUP:
-                       bridge_channel_handle_hangup(bridge_channel);
-                       ast_frfree(frame);
+                       ast_bridge_channel_kick(bridge_channel, 0);
+                       bridge_frame_free(frame);
                        return;
-/* BUGBUG This is where incoming HOLD/UNHOLD memory should register.  Write UNHOLD into bridge when this channel is pulled. */
                default:
                        break;
                }
                break;
        case AST_FRAME_DTMF_BEGIN:
+       case AST_FRAME_DTMF_END:
                frame = bridge_handle_dtmf(bridge_channel, frame);
                if (!frame) {
                        return;
                }
-               /* Fall through */
-       case AST_FRAME_DTMF_END:
                if (!bridge_channel->features->dtmf_passthrough) {
-                       ast_frfree(frame);
+                       bridge_frame_free(frame);
                        return;
                }
-/* BUGBUG This is where incoming DTMF begin/end memory should register.  Write DTMF end into bridge when this channel is pulled. */
                break;
        default:
                break;
        }
 
        /* Simply write the frame out to the bridge technology. */
-/* BUGBUG The tech is where AST_CONTROL_ANSWER hook should go. (early bridge) */
-/* BUGBUG The tech is where incoming BUSY/CONGESTION hangup should happen? (early bridge) */
        bridge_channel_write_frame(bridge_channel, frame);
-       ast_frfree(frame);
+       bridge_frame_free(frame);
 }
 
 /*!
@@ -1710,22 +2107,17 @@ static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel)
        ast_bridge_channel_lock(bridge_channel);
        if (bridge_channel->state != BRIDGE_CHANNEL_STATE_WAIT) {
        } else if (bridge_channel->suspended) {
-/* BUGBUG the external party use of suspended will go away as will these references because this is the bridge channel thread */
+/* XXX ASTERISK-21271 the external party use of suspended will go away as will these references because this is the bridge channel thread */
                ast_debug(1, "Bridge %s: %p(%s) is going into a signal wait\n",
                        bridge_channel->bridge->uniqueid, bridge_channel,
                        ast_channel_name(bridge_channel->chan));
                ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
        } else {
-               ast_debug(10, "Bridge %s: %p(%s) is going into a waitfor\n",
-                       bridge_channel->bridge->uniqueid, bridge_channel,
-                       ast_channel_name(bridge_channel->chan));
-               bridge_channel->waiting = 1;
                ast_bridge_channel_unlock(bridge_channel);
                outfd = -1;
                ms = bridge_channel_next_interval(bridge_channel);
                chan = ast_waitfor_nandfds(&bridge_channel->chan, 1,
                        &bridge_channel->alert_pipe[0], 1, NULL, &outfd, &ms);
-               bridge_channel->waiting = 0;
                if (ast_channel_softhangup_internal_flag(bridge_channel->chan) & AST_SOFTHANGUP_UNBRIDGE) {
                        ast_channel_clear_softhangup(bridge_channel->chan, AST_SOFTHANGUP_UNBRIDGE);
                        ast_bridge_channel_lock_bridge(bridge_channel);
@@ -1782,7 +2174,7 @@ static void bridge_channel_event_join_leave(struct ast_bridge_channel *bridge_ch
                ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
                do {
                        if (hook->type == type) {
-                               hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+                               hook->callback(bridge_channel, hook->hook_pvt);
                                ao2_unlink(features->other_hooks, hook);
                        }
                        ao2_ref(hook, -1);
@@ -1797,6 +2189,7 @@ static void bridge_channel_event_join_leave(struct ast_bridge_channel *bridge_ch
 int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
 {
        int res = 0;
+
        ast_format_copy(&bridge_channel->read_format, ast_channel_readformat(bridge_channel->chan));
        ast_format_copy(&bridge_channel->write_format, ast_channel_writeformat(bridge_channel->chan));
 
@@ -1805,31 +2198,45 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
                bridge_channel, ast_channel_name(bridge_channel->chan));
 
        /*
-        * Get "in the bridge" before pushing the channel for any
-        * masquerades on the channel to happen before bridging.
+        * Directly locking the bridge is safe here because nobody else
+        * knows about this bridge_channel yet.
         */
+       ast_bridge_lock(bridge_channel->bridge);
+
+       /* Make sure we're still good to be put into a bridge */
        ast_channel_lock(bridge_channel->chan);
+       if (ast_channel_internal_bridge(bridge_channel->chan)
+               || ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_ZOMBIE)) {
+               ast_channel_unlock(bridge_channel->chan);
+               ast_bridge_unlock(bridge_channel->bridge);
+               ast_debug(1, "Bridge %s: %p(%s) failed to join Bridge\n",
+                       bridge_channel->bridge->uniqueid,
+                       bridge_channel,
+                       ast_channel_name(bridge_channel->chan));
+               return -1;
+       }
        ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
        ast_channel_unlock(bridge_channel->chan);
 
        /* Add the jitterbuffer if the channel requires it */
        ast_jb_enable_for_channel(bridge_channel->chan);
 
-       /*
-        * Directly locking the bridge is safe here because nobody else
-        * knows about this bridge_channel yet.
-        */
-       ast_bridge_lock(bridge_channel->bridge);
-
        if (!bridge_channel->bridge->callid) {
                bridge_channel->bridge->callid = ast_read_threadstorage_callid();
        }
 
        if (bridge_channel_internal_push(bridge_channel)) {
-               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE);
+               int cause = bridge_channel->bridge->cause;
+
+               ast_bridge_unlock(bridge_channel->bridge);
+               ast_bridge_channel_kick(bridge_channel, cause);
+               ast_bridge_channel_lock_bridge(bridge_channel);
+               ast_bridge_features_remove(bridge_channel->features,
+                       AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+               bridge_channel_dissolve_check(bridge_channel);
                res = -1;
        }
-       bridge_reconfigured(bridge_channel->bridge, 1);
+       bridge_reconfigured(bridge_channel->bridge, !bridge_channel->inhibit_colp);
 
        if (bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) {
                /*
@@ -1853,15 +2260,18 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
        }
 
        bridge_channel_internal_pull(bridge_channel);
+       bridge_channel_settle_owed_events(bridge_channel->bridge, bridge_channel);
        bridge_reconfigured(bridge_channel->bridge, 1);
 
        ast_bridge_unlock(bridge_channel->bridge);
 
-       /* Indicate a source change since this channel is leaving the bridge system. */
-       ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+       /* Complete any active hold before exiting the bridge. */
+       if (ast_channel_hold_state(bridge_channel->chan) == AST_CONTROL_HOLD) {
+               ast_debug(1, "Channel %s simulating UNHOLD for bridge end.\n",
+                       ast_channel_name(bridge_channel->chan));
+               ast_indicate(bridge_channel->chan, AST_CONTROL_UNHOLD);
+       }
 
-/* BUGBUG Revisit in regards to moving channels between bridges and local channel optimization. */
-/* BUGBUG This is where outgoing HOLD/UNHOLD memory should write UNHOLD to channel. */
        /* Complete any partial DTMF digit before exiting the bridge. */
        if (ast_channel_sending_dtmf_digit(bridge_channel->chan)) {
                ast_channel_end_dtmf(bridge_channel->chan,
@@ -1869,6 +2279,9 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
                        ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end");
        }
 
+       /* Indicate a source change since this channel is leaving the bridge system. */
+       ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+
        /*
         * Wait for any dual redirect to complete.
         *
@@ -2014,7 +2427,7 @@ static void bridge_channel_destroy(void *obj)
 
        /* Flush any unhandled wr_queue frames. */
        while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list))) {
-               ast_frfree(fr);
+               bridge_frame_free(fr);
        }
        pipe_close(bridge_channel->alert_pipe);