git migration: Refactor the ASTERISK_FILE_VERSION macro
[asterisk/asterisk.git] / main / bridge_channel.c
index 96bfb20..9e30068 100644 (file)
@@ -32,7 +32,7 @@
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+ASTERISK_REGISTER_FILE()
 
 #include <signal.h>
 
@@ -55,6 +55,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/parking.h"
 #include "asterisk/causes.h"
 #include "asterisk/test.h"
+#include "asterisk/sem.h"
 
 /*!
  * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge.
@@ -70,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 */
+       struct ast_sem 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;
+       ast_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);
+
+       ast_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,
+       };
+
+       ast_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)
+{
+       ast_sem_post(&sync_struct->sem);
+}
+
 void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel)
 {
        struct ast_bridge *bridge;
@@ -146,7 +283,7 @@ void ast_bridge_channel_leave_bridge_nolock(struct ast_bridge_channel *bridge_ch
                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);
 
@@ -183,27 +320,34 @@ struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *br
 
 void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel)
 {
+       ast_assert(bridge_channel->read_format != NULL);
+       ast_assert(bridge_channel->write_format != NULL);
+
+       ast_channel_lock(bridge_channel->chan);
+
        /* Restore original formats of the channel as they came in */
-       if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &bridge_channel->read_format) == AST_FORMAT_CMP_NOT_EQUAL) {
+       if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), bridge_channel->read_format) == AST_FORMAT_CMP_NOT_EQUAL) {
                ast_debug(1, "Bridge is returning %p(%s) to read format %s\n",
                        bridge_channel, ast_channel_name(bridge_channel->chan),
-                       ast_getformatname(&bridge_channel->read_format));
-               if (ast_set_read_format(bridge_channel->chan, &bridge_channel->read_format)) {
+                       ast_format_get_name(bridge_channel->read_format));
+               if (ast_set_read_format(bridge_channel->chan, bridge_channel->read_format)) {
                        ast_debug(1, "Bridge failed to return %p(%s) to read format %s\n",
                                bridge_channel, ast_channel_name(bridge_channel->chan),
-                               ast_getformatname(&bridge_channel->read_format));
+                               ast_format_get_name(bridge_channel->read_format));
                }
        }
-       if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &bridge_channel->write_format) == AST_FORMAT_CMP_NOT_EQUAL) {
+       if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), bridge_channel->write_format) == AST_FORMAT_CMP_NOT_EQUAL) {
                ast_debug(1, "Bridge is returning %p(%s) to write format %s\n",
                        bridge_channel, ast_channel_name(bridge_channel->chan),
-                       ast_getformatname(&bridge_channel->write_format));
-               if (ast_set_write_format(bridge_channel->chan, &bridge_channel->write_format)) {
+                       ast_format_get_name(bridge_channel->write_format));
+               if (ast_set_write_format(bridge_channel->chan, bridge_channel->write_format)) {
                        ast_debug(1, "Bridge failed to return %p(%s) to write format %s\n",
                                bridge_channel, ast_channel_name(bridge_channel->chan),
-                               ast_getformatname(&bridge_channel->write_format));
+                               ast_format_get_name(bridge_channel->write_format));
                }
        }
+
+       ast_channel_unlock(bridge_channel->chan);
 }
 
 struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request)
@@ -220,72 +364,225 @@ struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *b
 
 void ast_bridge_channel_update_linkedids(struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
 {
-       struct ast_bridge_channel *other = NULL;
+       struct ast_bridge_channel *other;
        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);
        }
 }
 
-void ast_bridge_channel_update_accountcodes(struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+/*!
+ * \internal
+ * \brief Set dest's empty peeraccount with the src's non-empty accountcode.
+ * \since 12.5.0
+ *
+ * \param dest Channel to update peeraccount.
+ * \param src Channel to get accountcode from.
+ *
+ * \note Both channels are already locked.
+ *
+ * \return Nothing
+ */
+static void channel_fill_empty_peeraccount(struct ast_channel *dest, struct ast_channel *src)
 {
-       struct ast_bridge *bridge = bridge_channel->bridge;
-       struct ast_bridge_channel *other = NULL;
+       if (ast_strlen_zero(ast_channel_peeraccount(dest))
+               && !ast_strlen_zero(ast_channel_accountcode(src))) {
+               ast_debug(1, "Setting channel %s peeraccount with channel %s accountcode '%s'.\n",
+                       ast_channel_name(dest),
+                       ast_channel_name(src), ast_channel_accountcode(src));
+               ast_channel_peeraccount_set(dest, ast_channel_accountcode(src));
+       }
+}
+
+/*!
+ * \internal
+ * \brief Set dest's empty accountcode with the src's non-empty peeraccount.
+ * \since 12.5.0
+ *
+ * \param dest Channel to update accountcode.
+ * \param src Channel to get peeraccount from.
+ *
+ * \note Both channels are already locked.
+ *
+ * \return Nothing
+ */
+static void channel_fill_empty_accountcode(struct ast_channel *dest, struct ast_channel *src)
+{
+       if (ast_strlen_zero(ast_channel_accountcode(dest))
+               && !ast_strlen_zero(ast_channel_peeraccount(src))) {
+               ast_debug(1, "Setting channel %s accountcode with channel %s peeraccount '%s'.\n",
+                       ast_channel_name(dest),
+                       ast_channel_name(src), ast_channel_peeraccount(src));
+               ast_channel_accountcode_set(dest, ast_channel_peeraccount(src));
+       }
+}
+
+/*!
+ * \internal
+ * \brief Set empty peeraccount and accountcode in a channel from the other channel.
+ * \since 12.5.0
+ *
+ * \param c0 First bridge channel to update.
+ * \param c1 Second bridge channel to update.
+ *
+ * \note Both channels are already locked.
+ *
+ * \return Nothing
+ */
+static void channel_set_empty_accountcodes(struct ast_channel *c0, struct ast_channel *c1)
+{
+       /* Set empty peeraccount from the other channel's accountcode. */
+       channel_fill_empty_peeraccount(c0, c1);
+       channel_fill_empty_peeraccount(c1, c0);
+
+       /* Set empty accountcode from the other channel's peeraccount. */
+       channel_fill_empty_accountcode(c0, c1);
+       channel_fill_empty_accountcode(c1, c0);
+}
+
+/*!
+ * \internal
+ * \brief Update dest's peeraccount with the src's different accountcode.
+ * \since 12.5.0
+ *
+ * \param dest Channel to update peeraccount.
+ * \param src Channel to get accountcode from.
+ *
+ * \note Both channels are already locked.
+ *
+ * \return Nothing
+ */
+static void channel_update_peeraccount(struct ast_channel *dest, struct ast_channel *src)
+{
+       if (strcmp(ast_channel_accountcode(src), ast_channel_peeraccount(dest))) {
+               ast_debug(1, "Changing channel %s peeraccount '%s' to match channel %s accountcode '%s'.\n",
+                       ast_channel_name(dest), ast_channel_peeraccount(dest),
+                       ast_channel_name(src), ast_channel_accountcode(src));
+               ast_channel_peeraccount_set(dest, ast_channel_accountcode(src));
+       }
+}
+
+/*!
+ * \internal
+ * \brief Update peeraccounts to match the other channel's accountcode.
+ * \since 12.5.0
+ *
+ * \param c0 First channel to update.
+ * \param c1 Second channel to update.
+ *
+ * \note Both channels are already locked.
+ *
+ * \return Nothing
+ */
+static void channel_update_peeraccounts(struct ast_channel *c0, struct ast_channel *c1)
+{
+       channel_update_peeraccount(c0, c1);
+       channel_update_peeraccount(c1, c0);
+}
+
+/*!
+ * \internal
+ * \brief Update channel accountcodes because a channel is joining a bridge.
+ * \since 12.5.0
+ *
+ * \param joining Channel joining the bridge.
+ * \param swap Channel being replaced by the joining channel.  May be NULL.
+ *
+ * \note The bridge must be locked prior to calling this function.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_update_accountcodes_joining(struct ast_bridge_channel *joining, struct ast_bridge_channel *swap)
+{
+       struct ast_bridge *bridge = joining->bridge;
+       struct ast_bridge_channel *other;
+       unsigned int swap_in_bridge = 0;
+       unsigned int will_be_two_party;
+
+       /*
+        * Only update the peeraccount to match if the joining channel
+        * will make it a two party bridge.
+        */
+       if (bridge->num_channels <= 2 && swap) {
+               AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+                       if (other == swap) {
+                               swap_in_bridge = 1;
+                               break;
+                       }
+               }
+       }
+       will_be_two_party = (1 == bridge->num_channels - swap_in_bridge);
 
        AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
                if (other == swap) {
                        continue;
                }
-
-               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",
-                                       ast_channel_accountcode(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
-                       ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan));
-               }
-               if (!ast_strlen_zero(ast_channel_accountcode(other->chan)) && ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan))) {
-                       ast_debug(1, "Setting peeraccount to %s for %s from data on channel %s\n",
-                                       ast_channel_accountcode(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
-                       ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
-               }
-               if (!ast_strlen_zero(ast_channel_peeraccount(bridge_channel->chan)) && ast_strlen_zero(ast_channel_accountcode(other->chan))) {
-                       ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n",
-                                       ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
-                       ast_channel_accountcode_set(other->chan, ast_channel_peeraccount(bridge_channel->chan));
-               }
-               if (!ast_strlen_zero(ast_channel_peeraccount(other->chan)) && ast_strlen_zero(ast_channel_accountcode(bridge_channel->chan))) {
-                       ast_debug(1, "Setting accountcode to %s for %s from data on channel %s\n",
-                                       ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
-                       ast_channel_accountcode_set(bridge_channel->chan, ast_channel_peeraccount(other->chan));
-               }
-               if (bridge->num_channels == 2) {
-                       if (strcmp(ast_channel_accountcode(bridge_channel->chan), ast_channel_peeraccount(other->chan))) {
-                               ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n",
-                                               ast_channel_peeraccount(other->chan), ast_channel_peeraccount(bridge_channel->chan), ast_channel_name(other->chan), ast_channel_name(bridge_channel->chan));
-                               ast_channel_peeraccount_set(other->chan, ast_channel_accountcode(bridge_channel->chan));
-                       }
-                       if (strcmp(ast_channel_accountcode(other->chan), ast_channel_peeraccount(bridge_channel->chan))) {
-                               ast_debug(1, "Changing peeraccount from %s to %s on %s to match channel %s\n",
-                                               ast_channel_peeraccount(bridge_channel->chan), ast_channel_peeraccount(other->chan), ast_channel_name(bridge_channel->chan), ast_channel_name(other->chan));
-                               ast_channel_peeraccount_set(bridge_channel->chan, ast_channel_accountcode(other->chan));
-                       }
+               ast_assert(joining != other);
+               ast_channel_lock_both(joining->chan, other->chan);
+               channel_set_empty_accountcodes(joining->chan, other->chan);
+               if (will_be_two_party) {
+                       channel_update_peeraccounts(joining->chan, other->chan);
                }
+               ast_channel_unlock(joining->chan);
+               ast_channel_unlock(other->chan);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Update channel peeraccount codes because a channel has left a bridge.
+ * \since 12.5.0
+ *
+ * \param leaving Channel leaving the bridge. (Has already been removed actually)
+ *
+ * \note The bridge must be locked prior to calling this function.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_update_accountcodes_leaving(struct ast_bridge_channel *leaving)
+{
+       struct ast_bridge *bridge = leaving->bridge;
+       struct ast_bridge_channel *first;
+       struct ast_bridge_channel *second;
+
+       if (bridge->num_channels != 2 || bridge->dissolved) {
+               return;
+       }
+
+       first = AST_LIST_FIRST(&bridge->channels);
+       second = AST_LIST_LAST(&bridge->channels);
+       ast_assert(first && first != second);
+       ast_channel_lock_both(first->chan, second->chan);
+       channel_set_empty_accountcodes(first->chan, second->chan);
+       channel_update_peeraccounts(first->chan, second->chan);
+       ast_channel_unlock(second->chan);
+       ast_channel_unlock(first->chan);
+}
+
+void ast_bridge_channel_update_accountcodes(struct ast_bridge_channel *joining, struct ast_bridge_channel *leaving)
+{
+       if (joining) {
+               bridge_channel_update_accountcodes_joining(joining, leaving);
+       } else {
+               bridge_channel_update_accountcodes_leaving(leaving);
        }
 }
 
@@ -336,6 +633,8 @@ void ast_bridge_channel_kick(struct ast_bridge_channel *bridge_channel, int caus
  */
 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);
 /*
  * XXX need to implement a deferred write queue for when there
@@ -487,7 +786,8 @@ static void bridge_channel_unsuspend(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,
@@ -501,6 +801,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
  *
@@ -512,7 +858,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,
@@ -524,6 +871,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;
@@ -551,7 +919,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;
        }
 
@@ -611,7 +979,8 @@ int ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_chan
 
 int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, const char *moh_class)
 {
-       RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+       struct ast_json *blob;
+       int res;
        size_t datalen;
 
        if (!ast_strlen_zero(moh_class)) {
@@ -622,16 +991,22 @@ int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, con
        } else {
                moh_class = NULL;
                datalen = 0;
+               blob = NULL;
        }
 
-       ast_channel_publish_blob(bridge_channel->chan, ast_channel_hold_type(), blob);
-       return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+       ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_hold_type(), blob);
+
+       res = ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
                moh_class, datalen);
+
+       ast_json_unref(blob);
+       return res;
 }
 
 int ast_bridge_channel_write_unhold(struct ast_bridge_channel *bridge_channel)
 {
-       ast_channel_publish_blob(bridge_channel->chan, ast_channel_unhold_type(), NULL);
+       ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_unhold_type(), NULL);
+
        return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, NULL, 0);
 }
 
@@ -654,7 +1029,16 @@ static int run_app_helper(struct ast_channel *chan, const char *app_name, const
                if (!app) {
                        ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
                } else {
-                       res = pbx_exec(chan, app, app_args);
+                       struct ast_str *substituted_args = ast_str_create(16);
+
+                       if (substituted_args) {
+                               ast_str_substitute_variables(&substituted_args, 0, chan, app_args);
+                               res = pbx_exec(chan, app, ast_str_buffer(substituted_args));
+                               ast_free(substituted_args);
+                       } else {
+                               ast_log(LOG_WARNING, "Could not substitute application argument variables for %s\n", app_name);
+                               res = pbx_exec(chan, app, app_args);
+                       }
                }
        }
        return res;
@@ -806,7 +1190,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 */
@@ -829,6 +1213,13 @@ 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;
@@ -1107,128 +1498,199 @@ static void testsuite_notify_feature_success(struct ast_channel *chan, const cha
                        feature = "automixmon";
                } else if (!strcmp(dtmf, featuremap->parkcall)) {
                        feature = "parkcall";
-               } else if (!strcmp(dtmf, xfer->atxferthreeway)) {
+               }
+       }
+       if (xfer) {
+               if (!strcmp(dtmf, xfer->atxferthreeway)) {
                        feature = "atxferthreeway";
                }
        }
 
+       ao2_cleanup(featuremap);
+       ao2_cleanup(xfer);
+
        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, const char *starting_dtmf)
+static int bridge_channel_feature_digit_add(
+       struct ast_bridge_channel *bridge_channel, int digit, size_t dtmf_len)
+{
+       if (dtmf_len < ARRAY_LEN(bridge_channel->dtmf_hook_state.collected) - 1) {
+               /* Add the new digit to the DTMF string so we can do our matching */
+               bridge_channel->dtmf_hook_state.collected[dtmf_len++] = digit;
+               bridge_channel->dtmf_hook_state.collected[dtmf_len] = '\0';
+
+               ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n",
+                         bridge_channel, ast_channel_name(bridge_channel->chan),
+                         bridge_channel->dtmf_hook_state.collected);
+       }
+
+       return dtmf_len;
+}
+
+static unsigned int bridge_channel_feature_digit_timeout(struct ast_bridge_channel *bridge_channel)
 {
-       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;
        unsigned int digit_timeout;
-       RAII_VAR(struct ast_features_general_config *, gen_cfg, NULL, ao2_cleanup);
+       struct ast_features_general_config *gen_cfg;
 
+       /* Determine interdigit timeout */
        ast_channel_lock(bridge_channel->chan);
        gen_cfg = ast_get_chan_features_general_config(bridge_channel->chan);
+       ast_channel_unlock(bridge_channel->chan);
+
        if (!gen_cfg) {
                ast_log(LOG_ERROR, "Unable to retrieve features configuration.\n");
-               ast_channel_unlock(bridge_channel->chan);
-               return;
+               return 3000; /* Pick a reasonable failsafe timeout in ms */
        }
+
        digit_timeout = gen_cfg->featuredigittimeout;
-       ast_channel_unlock(bridge_channel->chan);
+       ao2_ref(gen_cfg, -1);
 
-       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);
+       return digit_timeout;
+}
+
+void ast_bridge_channel_feature_digit_add(struct ast_bridge_channel *bridge_channel, int digit)
+{
+       if (digit) {
+               bridge_channel_feature_digit_add(
+                       bridge_channel, digit, strlen(bridge_channel->dtmf_hook_state.collected));
        }
+}
 
-       /*
-        * Check if any feature DTMF hooks match or could match and
-        * try to collect more DTMF digits.
-        */
-       for (;;) {
-               int res;
+void ast_bridge_channel_feature_digit(struct ast_bridge_channel *bridge_channel, int digit)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook_dtmf *hook = NULL;
+       size_t dtmf_len;
 
-               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);
+       struct sanity_check_of_dtmf_size {
+               char check[1 / (ARRAY_LEN(bridge_channel->dtmf_hook_state.collected) == ARRAY_LEN(hook->dtmf.code))];
+       };
 
-                       /* 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;
+       dtmf_len = strlen(bridge_channel->dtmf_hook_state.collected);
+       if (!dtmf_len && !digit) {
+               /* Nothing to do */
+               return;
+       }
+
+       if (digit) {
+               dtmf_len = bridge_channel_feature_digit_add(bridge_channel, digit, dtmf_len);
+       }
+
+       while (digit) {
+               /* See if a DTMF feature hook matches or can match */
+               hook = ao2_find(features->dtmf_hooks, bridge_channel->dtmf_hook_state.collected,
+                       OBJ_SEARCH_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),
+                               bridge_channel->dtmf_hook_state.collected);
+                       break;
+               } else if (dtmf_len != strlen(hook->dtmf.code)) {
+                       unsigned int digit_timeout;
+                       /* Need more digits to match */
+                       ao2_ref(hook, -1);
+                       digit_timeout = bridge_channel_feature_digit_timeout(bridge_channel);
+                       bridge_channel->dtmf_hook_state.interdigit_timeout =
+                               ast_tvadd(ast_tvnow(), ast_samp2tv(digit_timeout, 1000));
+                       return;
+               } else {
+                       int remove_me;
+                       int already_suspended;
+
+                       ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n",
+                               hook, bridge_channel->dtmf_hook_state.collected, bridge_channel,
+                               ast_channel_name(bridge_channel->chan));
+
+                       /*
+                        * Clear the collected digits before executing the hook
+                        * in case the hook starts another sequence.
+                        */
+                       bridge_channel->dtmf_hook_state.collected[0] = '\0';
+
+                       ast_bridge_channel_lock_bridge(bridge_channel);
+                       already_suspended = bridge_channel->suspended;
+                       if (!already_suspended) {
+                               bridge_channel_internal_suspend_nolock(bridge_channel);
                        }
-                       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;
+                       ast_bridge_unlock(bridge_channel->bridge);
+                       ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+
+                       /* 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);
-                       hook = NULL;
 
-                       if (ARRAY_LEN(dtmf) - 1 <= dtmf_len) {
-                               /* We have reached the maximum length of a DTMF feature string. */
-                               break;
+                       ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+                       if (!already_suspended) {
+                               bridge_channel_unsuspend(bridge_channel);
                        }
-               }
 
-               res = ast_waitfordigit(bridge_channel->chan, digit_timeout);
-               if (!res) {
-                       ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
-                               bridge_channel, ast_channel_name(bridge_channel->chan));
-                       break;
-               }
-               if (res < 0) {
-                       ast_debug(1, "DTMF feature string collection failed on %p(%s) for some reason\n",
-                               bridge_channel, ast_channel_name(bridge_channel->chan));
-                       break;
+                       /*
+                        * If we are handing the channel off to an external hook for
+                        * ownership, we are not guaranteed what kind of state it will
+                        * come back in.  If the channel hungup, we need to detect that
+                        * here if the hook did not already change the state.
+                        */
+                       if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
+                               ast_bridge_channel_kick(bridge_channel, 0);
+                               bridge_channel->dtmf_hook_state.collected[0] = '\0';
+                               return;
+                       }
+
+                       /* if there is dtmf that has been collected then loop back through,
+                          but set digit to -1 so it doesn't try to do an add since the dtmf
+                          is already in the buffer */
+                       dtmf_len = strlen(bridge_channel->dtmf_hook_state.collected);
+                       if (!dtmf_len) {
+                               return;
+                       }
                }
+       }
 
-               /* Add the new DTMF into the DTMF string so we can do our matching */
-               dtmf[dtmf_len] = res;
-               dtmf[++dtmf_len] = '\0';
+       if (!digit) {
+               ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
+                       bridge_channel, ast_channel_name(bridge_channel->chan));
        }
 
-       if (hook) {
-               int remove_me;
+       /* Timeout or DTMF digit didn't allow a match with any hooks. */
+       if (features->dtmf_passthrough) {
+               /* Stream the collected DTMF to the other channels. */
+               bridge_channel_write_dtmf_stream(bridge_channel,
+                       bridge_channel->dtmf_hook_state.collected);
+       }
+       bridge_channel->dtmf_hook_state.collected[0] = '\0';
 
-               /* 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);
+       ast_test_suite_event_notify("FEATURE_DETECTION", "Result: fail");
+}
 
-               /*
-                * If we are handing the channel off to an external hook for
-                * ownership, we are not guaranteed what kind of state it will
-                * come back in.  If the channel hungup, we need to detect that
-                * here if the hook did not already change the state.
-                */
-               if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
-                       ast_bridge_channel_kick(bridge_channel, 0);
-               }
-       } 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 Handle bridge channel DTMF feature timeout expiration.
+ * \since 12.8.0
+ *
+ * \param bridge_channel Channel to check expired interdigit timer on.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_feature_timeout(struct ast_bridge_channel *bridge_channel)
+{
+       if (!bridge_channel->dtmf_hook_state.collected[0]
+               || 0 < ast_tvdiff_ms(bridge_channel->dtmf_hook_state.interdigit_timeout,
+                       ast_tvnow())) {
+               /* Not within a sequence or not timed out. */
+               return;
        }
+
+       ast_bridge_channel_feature_digit(bridge_channel, 0);
 }
 
 /*!
@@ -1379,53 +1841,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) {
+       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_do_callback(bridge_channel, action->data.ptr);
+               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;
@@ -1522,6 +1986,8 @@ void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel)
        }
        --bridge->num_channels;
        AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
+
+       bridge_channel_dissolve_check(bridge_channel);
        bridge->v_table->pull(bridge, bridge_channel);
 
        ast_bridge_channel_clear_roles(bridge_channel);
@@ -1530,14 +1996,12 @@ 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)
-                           || bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT)) {
+           && (ast_channel_is_leaving_bridge(bridge_channel->chan)
+               || 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);
        }
 
-       bridge_channel_dissolve_check(bridge_channel);
-
        bridge->reconfigured = 1;
        ast_bridge_publish_leave(bridge, bridge_channel->chan);
 }
@@ -1571,6 +2035,19 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
                        bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
                return -1;
        }
+
+       if (swap) {
+               int dissolve = ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY);
+
+               /* This flag is cleared so the act of this channel leaving does not cause it to dissolve if need be */
+               ast_clear_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY);
+
+               ast_bridge_channel_leave_bridge(swap, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, 0);
+               bridge_channel_internal_pull(swap);
+
+               ast_set2_flag(&bridge->feature_flags, dissolve, AST_BRIDGE_FLAG_DISSOLVE_EMPTY);
+       }
+
        bridge_channel->in_bridge = 1;
        bridge_channel->just_joined = 1;
        AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry);
@@ -1592,10 +2069,6 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
                bridge->uniqueid);
 
        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, 0);
-               bridge_channel_internal_pull(swap);
-       }
 
        /* Clear any BLINDTRANSFER and ATTENDEDTRANSFER since the transfer has completed. */
        pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", NULL);
@@ -1671,6 +2144,10 @@ static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_chan
                        ast_indicate(chan, -1);
                }
                break;
+       case AST_CONTROL_MASQUERADE_NOTIFY:
+               /* Should never happen. */
+               ast_assert(0);
+               break;
        default:
                ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
                break;
@@ -1679,6 +2156,25 @@ static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_chan
 
 /*!
  * \internal
+ * \param bridge_channel Channel to read wr_queue alert pipe.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_read_wr_queue_alert(struct ast_bridge_channel *bridge_channel)
+{
+       char nudge;
+
+       if (read(bridge_channel->alert_pipe[0], &nudge, sizeof(nudge)) < 0) {
+               if (errno != EINTR && errno != EAGAIN) {
+                       ast_log(LOG_WARNING, "read() failed for alert pipe on %p(%s): %s\n",
+                               bridge_channel, ast_channel_name(bridge_channel->chan),
+                               strerror(errno));
+               }
+       }
+}
+
+/*!
+ * \internal
  * \brief Handle bridge channel write frame to channel.
  * \since 12.0.0
  *
@@ -1689,23 +2185,56 @@ static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_chan
 static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channel)
 {
        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) {
-               if (errno != EINTR && errno != EAGAIN) {
-                       ast_log(LOG_WARNING, "read() failed for alert pipe on %p(%s): %s\n",
-                               bridge_channel, ast_channel_name(bridge_channel->chan), strerror(errno));
+
+       /* It's not good to have unbalanced frames and alert_pipe alerts. */
+       ast_assert(!AST_LIST_EMPTY(&bridge_channel->wr_queue));
+       if (AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+               /* No frame, flush the alert pipe of excess alerts. */
+               ast_log(LOG_WARNING, "Weird.  No frame from bridge for %s to process?\n",
+                       ast_channel_name(bridge_channel->chan));
+               bridge_channel_read_wr_queue_alert(bridge_channel);
+               ast_bridge_channel_unlock(bridge_channel);
+               return;
+       }
+
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&bridge_channel->wr_queue, fr, frame_list) {
+               if (bridge_channel->dtmf_hook_state.collected[0]) {
+                       switch (fr->frametype) {
+                       case AST_FRAME_BRIDGE_ACTION:
+                       case AST_FRAME_BRIDGE_ACTION_SYNC:
+                               /* Defer processing these frames while DTMF is collected. */
+                               continue;
+                       default:
+                               break;
+                       }
                }
+               bridge_channel_read_wr_queue_alert(bridge_channel);
+               AST_LIST_REMOVE_CURRENT(frame_list);
+               break;
        }
-       fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list);
+       AST_LIST_TRAVERSE_SAFE_END;
+
        ast_bridge_channel_unlock(bridge_channel);
        if (!fr) {
+               /*
+                * Wait some to reduce CPU usage from a tight loop
+                * without any wait because we only have deferred
+                * frames in the wr_queue.
+                */
+               usleep(1);
                return;
        }
+
        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);
@@ -1718,45 +2247,43 @@ 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 */
 static struct ast_frame *bridge_handle_dtmf(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
        struct ast_bridge_features *features = bridge_channel->features;
-       struct ast_bridge_hook_dtmf *hook;
+       struct ast_bridge_hook_dtmf *hook = NULL;
        char dtmf[2];
 
-       /* See if this DTMF matches the beginning of any feature hooks. */
+       /*
+        * See if we are already matching a DTMF feature hook sequence or
+        * if this DTMF matches the beginning of any DTMF feature hooks.
+        */
        dtmf[0] = frame->subclass.integer;
        dtmf[1] = '\0';
-       hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY);
-       if (hook) {
+       if (bridge_channel->dtmf_hook_state.collected[0]
+               || (hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_SEARCH_PARTIAL_KEY))) {
                enum ast_frame_type frametype = frame->frametype;
 
-               ast_frfree(frame);
+               bridge_frame_free(frame);
                frame = NULL;
 
-               ao2_ref(hook, -1);
+               ao2_cleanup(hook);
 
-               /* 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);
+                       /* Just eat the frame. */
                        break;
                case AST_FRAME_DTMF_END:
-                       bridge_channel_feature(bridge_channel, dtmf);
+                       ast_bridge_channel_feature_digit(bridge_channel, dtmf[0]);
                        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
@@ -1795,7 +2322,7 @@ static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
                switch (frame->subclass.integer) {
                case AST_CONTROL_HANGUP:
                        ast_bridge_channel_kick(bridge_channel, 0);
-                       ast_frfree(frame);
+                       bridge_frame_free(frame);
                        return;
                default:
                        break;
@@ -1808,7 +2335,7 @@ static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
                        return;
                }
                if (!bridge_channel->features->dtmf_passthrough) {
-                       ast_frfree(frame);
+                       bridge_frame_free(frame);
                        return;
                }
                break;
@@ -1818,7 +2345,7 @@ static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
 
        /* Simply write the frame out to the bridge technology. */
        bridge_channel_write_frame(bridge_channel, frame);
-       ast_frfree(frame);
+       bridge_frame_free(frame);
 }
 
 /*!
@@ -1856,6 +2383,60 @@ static int bridge_channel_next_interval(struct ast_bridge_channel *bridge_channe
 
 /*!
  * \internal
+ * \brief Determine how long till the DTMF interdigit timeout.
+ * \since 12.8.0
+ *
+ * \param bridge_channel Channel to determine how long can wait.
+ *
+ * \retval ms Number of milliseconds to wait.
+ * \retval -1 to wait forever.
+ */
+static int bridge_channel_feature_timeout(struct ast_bridge_channel *bridge_channel)
+{
+       int ms;
+
+       if (bridge_channel->dtmf_hook_state.collected[0]) {
+               ms = ast_tvdiff_ms(bridge_channel->dtmf_hook_state.interdigit_timeout,
+                       ast_tvnow());
+               if (ms < 0) {
+                       /* Expire immediately. */
+                       ms = 0;
+               }
+       } else {
+               /* Timer is not active so wait forever. */
+               ms = -1;
+       }
+
+       return ms;
+}
+
+/*!
+ * \internal
+ * \brief Determine how long till a timeout.
+ * \since 12.8.0
+ *
+ * \param bridge_channel Channel to determine how long can wait.
+ *
+ * \retval ms Number of milliseconds to wait.
+ * \retval -1 to wait forever.
+ */
+static int bridge_channel_next_timeout(struct ast_bridge_channel *bridge_channel)
+{
+       int ms_interval;
+       int ms;
+
+       ms_interval = bridge_channel_next_interval(bridge_channel);
+       ms = bridge_channel_feature_timeout(bridge_channel);
+       if (ms < 0 || (0 <= ms_interval && ms_interval < ms)) {
+               /* Interval hook timeout is next. */
+               ms = ms_interval;
+       }
+
+       return ms;
+}
+
+/*!
+ * \internal
  * \brief Wait for something to happen on the bridge channel and handle it.
  * \since 12.0.0
  *
@@ -1883,11 +2464,11 @@ static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel)
        } else {
                ast_bridge_channel_unlock(bridge_channel);
                outfd = -1;
-               ms = bridge_channel_next_interval(bridge_channel);
+               ms = bridge_channel_next_timeout(bridge_channel);
                chan = ast_waitfor_nandfds(&bridge_channel->chan, 1,
                        &bridge_channel->alert_pipe[0], 1, NULL, &outfd, &ms);
-               if (ast_channel_softhangup_internal_flag(bridge_channel->chan) & AST_SOFTHANGUP_UNBRIDGE) {
-                       ast_channel_clear_softhangup(bridge_channel->chan, AST_SOFTHANGUP_UNBRIDGE);
+               if (ast_channel_unbridged(bridge_channel->chan)) {
+                       ast_channel_set_unbridged(bridge_channel->chan, 0);
                        ast_bridge_channel_lock_bridge(bridge_channel);
                        bridge_channel->bridge->reconfigured = 1;
                        bridge_reconfigured(bridge_channel->bridge, 0);
@@ -1900,11 +2481,17 @@ static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel)
                        && bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) {
                        if (chan) {
                                bridge_handle_trip(bridge_channel);
-                       } else if (-1 < outfd) {
-                               bridge_channel_handle_write(bridge_channel);
                        } else if (ms == 0) {
-                               /* An interval expired. */
+                               /* An interdigit timeout or interval expired. */
+                               bridge_channel_handle_feature_timeout(bridge_channel);
                                bridge_channel_handle_interval(bridge_channel);
+                       } else if (-1 < outfd) {
+                               /*
+                                * Must do this after checking timeouts or may have
+                                * an infinite loop due to deferring write queue
+                                * actions while trying to match DTMF feature hooks.
+                                */
+                               bridge_channel_handle_write(bridge_channel);
                        }
                }
                bridge_channel->activity = BRIDGE_CHANNEL_THREAD_IDLE;
@@ -1953,13 +2540,11 @@ static void bridge_channel_event_join_leave(struct ast_bridge_channel *bridge_ch
        ao2_iterator_destroy(&iter);
 }
 
-/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
 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));
+       struct ast_bridge_features *channel_features;
+       struct ast_channel *swap;
 
        ast_debug(1, "Bridge %s: %p(%s) is joining\n",
                bridge_channel->bridge->uniqueid,
@@ -1971,8 +2556,12 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
         */
        ast_bridge_lock(bridge_channel->bridge);
 
-       /* Make sure we're still good to be put into a bridge */
        ast_channel_lock(bridge_channel->chan);
+
+       bridge_channel->read_format = ao2_bump(ast_channel_readformat(bridge_channel->chan));
+       bridge_channel->write_format = ao2_bump(ast_channel_writeformat(bridge_channel->chan));
+
+       /* Make sure we're still good to be put into a bridge */
        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);
@@ -1984,6 +2573,12 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
                return -1;
        }
        ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+
+       /* Attach features requested by the channel */
+       channel_features = ast_channel_feature_hooks_get(bridge_channel->chan);
+       if (channel_features) {
+               ast_bridge_features_merge(bridge_channel->features, channel_features);
+       }
        ast_channel_unlock(bridge_channel->chan);
 
        /* Add the jitterbuffer if the channel requires it */
@@ -1993,6 +2588,9 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
                bridge_channel->bridge->callid = ast_read_threadstorage_callid();
        }
 
+       /* Take the swap channel ref from the bridge_channel struct. */
+       swap = bridge_channel->swap;
+
        if (bridge_channel_internal_push(bridge_channel)) {
                int cause = bridge_channel->bridge->cause;
 
@@ -2018,11 +2616,21 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
                }
 
                ast_bridge_unlock(bridge_channel->bridge);
+
+               /* Must release any swap ref after unlocking the bridge. */
+               ao2_t_cleanup(swap, "Bridge push with swap successful");
+               swap = NULL;
+
                bridge_channel_event_join_leave(bridge_channel, AST_BRIDGE_HOOK_TYPE_JOIN);
+
                while (bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) {
                        /* Wait for something to do. */
                        bridge_channel_wait(bridge_channel);
                }
+
+               /* Force a timeout on any accumulated DTMF hook digits. */
+               ast_bridge_channel_feature_digit(bridge_channel, 0);
+
                bridge_channel_event_join_leave(bridge_channel, AST_BRIDGE_HOOK_TYPE_LEAVE);
                ast_bridge_channel_lock_bridge(bridge_channel);
        }
@@ -2033,6 +2641,9 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
 
        ast_bridge_unlock(bridge_channel->bridge);
 
+       /* Must release any swap ref after unlocking the bridge. */
+       ao2_t_cleanup(swap, "Bridge push with swap failed or exited immediately");
+
        /* 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",
@@ -2185,7 +2796,7 @@ static void bridge_channel_destroy(void *obj)
        struct ast_frame *fr;
 
        if (bridge_channel->callid) {
-               bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
+               bridge_channel->callid = 0;
        }
 
        if (bridge_channel->bridge) {
@@ -2195,11 +2806,14 @@ 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);
 
        ast_cond_destroy(&bridge_channel->cond);
+
+       ao2_cleanup(bridge_channel->write_format);
+       ao2_cleanup(bridge_channel->read_format);
 }
 
 struct ast_bridge_channel *bridge_channel_internal_alloc(struct ast_bridge *bridge)