]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Merge in the bridge_construction branch to make the system use the Bridging API.
authorRichard Mudgett <rmudgett@digium.com>
Tue, 21 May 2013 18:00:22 +0000 (18:00 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Tue, 21 May 2013 18:00:22 +0000 (18:00 +0000)
Breaks many things until they can be reworked.  A partial list:
chan_agent
chan_dahdi, chan_misdn, chan_iax2 native bridging
app_queue
COLP updates
DTMF attended transfers
Protocol attended transfers

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3

99 files changed:
CHANGES
UPGRADE.txt
addons/chan_ooh323.c
apps/app_bridgewait.c [new file with mode: 0644]
apps/app_channelredirect.c
apps/app_chanspy.c
apps/app_confbridge.c
apps/app_dial.c
apps/app_dumpchan.c
apps/app_followme.c
apps/app_mixmonitor.c
apps/app_parkandannounce.c [deleted file]
apps/app_queue.c
apps/confbridge/conf_chan_announce.c [new file with mode: 0644]
apps/confbridge/conf_chan_record.c [new file with mode: 0644]
apps/confbridge/conf_config_parser.c
apps/confbridge/confbridge_manager.c [new file with mode: 0644]
apps/confbridge/include/confbridge.h
bridges/bridge_builtin_features.c
bridges/bridge_builtin_interval_features.c [new file with mode: 0644]
bridges/bridge_holding.c [new file with mode: 0644]
bridges/bridge_multiplexed.c [deleted file]
bridges/bridge_native_rtp.c [new file with mode: 0644]
bridges/bridge_simple.c
bridges/bridge_softmix.c
channels/chan_agent.c
channels/chan_bridge.c [deleted file]
channels/chan_dahdi.c
channels/chan_gulp.c
channels/chan_h323.c
channels/chan_iax2.c
channels/chan_jingle.c
channels/chan_local.c [deleted file]
channels/chan_mgcp.c
channels/chan_misdn.c
channels/chan_motif.c
channels/chan_sip.c
channels/chan_skinny.c
channels/chan_unistim.c
channels/chan_vpb.cc
configs/features.conf.sample
configs/res_parking.conf.sample [new file with mode: 0644]
funcs/func_channel.c
funcs/func_frame_trace.c
funcs/func_jitterbuffer.c
include/asterisk/_private.h
include/asterisk/abstract_jb.h
include/asterisk/bridging.h
include/asterisk/bridging_basic.h [new file with mode: 0644]
include/asterisk/bridging_features.h
include/asterisk/bridging_roles.h [new file with mode: 0644]
include/asterisk/bridging_technology.h
include/asterisk/ccss.h
include/asterisk/channel.h
include/asterisk/config_options.h
include/asterisk/core_local.h [new file with mode: 0644]
include/asterisk/core_unreal.h [new file with mode: 0644]
include/asterisk/frame.h
include/asterisk/framehook.h
include/asterisk/manager.h
include/asterisk/parking.h [new file with mode: 0644]
include/asterisk/rtp_engine.h
include/asterisk/stasis_bridging.h [new file with mode: 0644]
main/abstract_jb.c
main/asterisk.c
main/bridging.c
main/bridging_basic.c [new file with mode: 0644]
main/bridging_roles.c [new file with mode: 0644]
main/channel.c
main/channel_internal_api.c
main/cli.c
main/config_options.c
main/core_local.c [new file with mode: 0644]
main/core_unreal.c [new file with mode: 0644]
main/features.c
main/frame.c
main/manager.c
main/manager_bridging.c [new file with mode: 0644]
main/manager_channels.c
main/parking.c [new file with mode: 0644]
main/pbx.c
main/rtp_engine.c
main/stasis_bridging.c [new file with mode: 0644]
main/strings.c
res/Makefile
res/parking/parking_applications.c [new file with mode: 0644]
res/parking/parking_bridge.c [new file with mode: 0644]
res/parking/parking_bridge_features.c [new file with mode: 0644]
res/parking/parking_controller.c [new file with mode: 0644]
res/parking/parking_manager.c [new file with mode: 0644]
res/parking/parking_ui.c [new file with mode: 0644]
res/parking/res_parking.h [new file with mode: 0644]
res/res_parking.c [new file with mode: 0644]
res/res_stasis_json_events.c
res/res_stasis_json_events.exports.in
res/stasis_json/resource_events.h
rest-api-templates/res_stasis_json_resource.c.mustache
rest-api-templates/stasis_json_resource.h.mustache
rest-api/api-docs/events.json

diff --git a/CHANGES b/CHANGES
index f5c705ed7f983ebbd7ec4a457500ba571f25fb9f..ec85f5ded464e1ecd950e051ff42861d2d68045f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -50,6 +50,19 @@ AMI (Asterisk Manager Interface)
  * The AMI event 'UserEvent' from app_userevent now contains the channel state
    fields. The channel state fields will come before the body fields.
 
+ * The AMI events 'ParkedCall', 'ParkedCallTimeOut', 'ParkedCallGiveUp', and
+   'UnParkedCall' have changed significantly in the new res_parking module.
+   First, channel snapshot data is included for both the parker and the parkee
+   in lieu of the "From" and "Channel" fields. They follow standard channel
+   snapshot format but each field is suffixed with 'Parker' or 'Parkee'
+   depending on which side it applies to. The 'Exten' field is replaced with
+   'ParkingSpace' since the registration of extensions as for parking spaces
+   is no longer mandatory.
+
+ * The AMI event 'Parkinglot' (response to 'Parkinglots' command) in a similar
+   fashion has changed the field names 'StartExten' and 'StopExten' to
+   'StartSpace' and 'StopSpace' respectively.
+
  * The deprecated use of | (pipe) as a separator in the channelvars setting in
    manager.conf has been removed.
 
@@ -59,8 +72,23 @@ AMI (Asterisk Manager Interface)
    event, the various ChanVariable fields will contain a suffix that specifies
    which channel they correspond to.
 
+ * The AMI 'Status' response event to the AMI Status action replaces the
+   BridgedChannel and BridgedUniqueid headers with the BridgeID header to
+   indicate what bridge the channel is currently in.
+
 Channel Drivers
 ------------------
+ * When a channel driver is configured to enable jiterbuffers, they are now
+   applied unconditionally when a channel joins a bridge. If a jitterbuffer
+   is already set for that channel when it enters, such as by the JITTERBUFFER
+   function, then the existing jitterbuffer will be used and the one set by
+   the channel driver will not be applied.
+
+chan_local
+------------------
+ * The /b option is removed.
+
+ * chan_local moved into the system core and is no longer a loadable module.
 
 chan_mobile
 ------------------
@@ -86,13 +114,19 @@ Features
 
  * Add support for automixmonitor to the BRIDGE_FEATURES channel variable.
 
- * PARKINGSLOT and PARKEDLOT channel variables will now be set for a parked
-   channel even when comebactoorigin=yes
+ * Parking has been pulled from core and placed into a separate module called
+   res_parking. See Parking changes below for more details.
 
  * You can now have the settings for a channel updated using the FEATURE()
    and FEATUREMAP() functions inherited to child channels by setting
    FEATURE(inherit)=yes.
 
+Functions
+------------------
+ * JITTERBUFFER now accepts an argument of 'disabled' which can be used
+   to remove jitterbuffers previously set on a channel with JITTERBUFFER.
+   The value of this setting is ignored when disabled is used for the argument.
+
 Logging
 -------------------
  * When performing queue pause/unpause on an interface without specifying an
@@ -110,6 +144,51 @@ MeetMe
   if a denoiser is attached to the channel; this option gives them the ability
   to remove the denoiser without having to unload func_speex.
 
+Parking
+-------------------
+ * Parking is now implemented as a module instead of as core functionality.
+   The preferred way to configure parking is now through res_parking.conf while
+   configuration through features.conf is not currently supported.
+
+ * Parked calls are now placed in bridges. This is a largely architectural change,
+   but it could have some implications in allowing for new parked call retrieval
+   methods and the contents of parking lots will be visible though certain bridge
+   commands.
+
+ * The order of arguments for the new parking applications are different from the
+   old ones to be more intuitive. Timeout and return context/exten/priority are now
+   implemented as options. parking_lot_name is now the first parameter. See the
+   application documentation for Park, ParkedCall, and ParkAndAnnounce for more
+   in-depth information as well as syntax.
+
+ * Extensions are no longer automatically created in the dialplan to park calls,
+   pickup parked calls, etc by default.
+
+ * adsipark is no longer supported under the new parking model
+
+ * The PARKINGSLOT channel variable has been deprecated in favor of PARKING_SPACE
+   to match the naming scheme of the new system.
+
+ * PARKING_SPACE and PARKEDLOT channel variables will now be set for a parked
+   channel even when comebactoorigin=yes
+
+ * New CLI command 'parking show' allows you to inspect the currently in use
+   parking lots. 'parking show <parkinglot>' will also show the parked calls
+   in that specific parking lot.
+
+ * The CLI command 'parkedcalls' is now deprecated in favor of
+   'parking show <parkinglot>'.
+
+ * The AMI command 'ParkedCalls' will now accept a 'ParkingLot' argument which
+   can be used to get a list of parked calls only for a specific parking lot.
+
+ * The ParkAndAnnounce application is now provided through res_parking instead
+   of through the separate app_parkandannounce module.
+
+ * ParkAndAnnounce will no longer go to the next position in dialplan on timeout
+   by default. Instead, it will follow the timeout rules of the parking lot. The
+   old behavior can be reproduced by using the 'c' option.
+
 Queue
 -------------------
  * Add queue available hint.  exten => 8501,hint,Queue:markq_avail
index 39f08049b9606e273ac2a05d1c494cf0241df00f..e2e7090081685fe77441bac55cc6a37cb0c4ae2b 100644 (file)
@@ -69,6 +69,9 @@ chan_dahdi:
    pauses dialing for one second.
  - The default for inband_on_proceeding has changed to no.
 
+chan_local:
+ - The /b option is removed.
+
 Dialplan:
  - All channel and global variable names are evaluated in a case-sensitive manner.
    In previous versions of Asterisk, variables created and evaluated in the
@@ -81,6 +84,18 @@ Dialplan:
    Uppercase variants apply them to the calling party while lowercase variants
    apply them to the called party.
 
+Features:
+ - The features.conf [applicationmap] <FeatureName>  ActivatedBy option is
+   no longer honored.  The feature is activated by which channel
+   DYNAMIC_FEATURES includes the feature is on.  Use predial to set different
+   values of DYNAMIC_FEATURES on the channels
+
+Parking:
+ - The arguments for the Park, ParkedCall, and ParkAndAnnounce applications have
+   been modified significantly. See the application documents for specific details.
+   Also parking lot configuration is now done in res_parking.conf instead of
+   features.conf
+
 From 10 to 11:
 
 Voicemail:
index a393e8e0ef8ce4ff693937764af0b5faa867bf97..61d9245fe18866604ec60851398af04202942d48 100644 (file)
@@ -117,7 +117,6 @@ static struct ast_channel_tech ooh323_tech = {
        .fixup = ooh323_fixup,
        .send_html = 0,
        .queryoption = ooh323_queryoption,
-       .bridge = ast_rtp_instance_bridge,              /* XXX chan unlocked ? */
        .early_bridge = ast_rtp_instance_early_bridge,
        .func_channel_read = function_ooh323_read,
        .func_channel_write = function_ooh323_write,
diff --git a/apps/app_bridgewait.c b/apps/app_bridgewait.c
new file mode 100644 (file)
index 0000000..ca12f0d
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * Author: Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Application to place the channel into a holding Bridge
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+       <depend>bridge_holding</depend>
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/features.h"
+#include "asterisk/say.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/bridging.h"
+#include "asterisk/musiconhold.h"
+
+/*** DOCUMENTATION
+       <application name="BridgeWait" language="en_US">
+               <synopsis>
+                       Put a call into the holding bridge.
+               </synopsis>
+               <syntax>
+                       <parameter name="options">
+                               <optionlist>
+                                       <option name="A">
+                                               <para>The channel will join the holding bridge as an
+                                               announcer</para>
+                                       </option>
+                                       <option name="m">
+                                               <argument name="class" required="false" />
+                                               <para>Play music on hold to the entering channel while it is
+                                               on hold. If the <emphasis>class</emphasis> is included, then
+                                               that class of music on hold will take priority over the
+                                               channel default.</para>
+                                       </option>
+                                       <option name="r">
+                                               <para>Play a ringing tone to the entering channel while it is
+                                               on hold.</para>
+                                       </option>
+                                       <option name="S">
+                                               <argument name="duration" required="true" />
+                                               <para>Automatically end the hold and return to the PBX after
+                                               <emphasis>duration</emphasis> seconds.</para>
+                                       </option>
+                               </optionlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This application places the incoming channel into a holding bridge.
+                       The channel will then wait in the holding bridge until some
+                       event occurs which removes it from the holding bridge.</para>
+               </description>
+       </application>
+ ***/
+/* BUGBUG Add bridge name/id parameter to specify which holding bridge to join (required) */
+/* BUGBUG Add h(moh-class) option to put channel on hold using AST_CONTROL_HOLD/AST_CONTROL_UNHOLD while in bridge */
+/* BUGBUG Add s option to send silence media frames to channel while in bridge (uses a silence generator) */
+/* BUGBUG Add n option to send no media to channel while in bridge (Channel should not be answered yet) */
+/* BUGBUG The channel may or may not be answered with the r option. */
+/* BUGBUG You should not place an announcer into a holding bridge with unanswered channels. */
+/* BUGBUG Not supplying any option flags will assume the m option with the default music class. */
+
+static char *app = "BridgeWait";
+static struct ast_bridge *holding_bridge;
+
+AST_MUTEX_DEFINE_STATIC(bridgewait_lock);
+
+enum bridgewait_flags {
+       MUXFLAG_PLAYMOH = (1 << 0),
+       MUXFLAG_RINGING = (1 << 1),
+       MUXFLAG_TIMEOUT = (1 << 2),
+       MUXFLAG_ANNOUNCER = (1 << 3),
+};
+
+enum bridgewait_args {
+       OPT_ARG_MOHCLASS,
+       OPT_ARG_TIMEOUT,
+       OPT_ARG_ARRAY_SIZE, /* Always the last element of the enum */
+};
+
+AST_APP_OPTIONS(bridgewait_opts, {
+       AST_APP_OPTION('A', MUXFLAG_ANNOUNCER),
+       AST_APP_OPTION('r', MUXFLAG_RINGING),
+       AST_APP_OPTION_ARG('m', MUXFLAG_PLAYMOH, OPT_ARG_MOHCLASS),
+       AST_APP_OPTION_ARG('S', MUXFLAG_TIMEOUT, OPT_ARG_TIMEOUT),
+});
+
+static int apply_option_timeout(struct ast_bridge_features *features, char *duration_arg)
+{
+       struct ast_bridge_features_limits hold_limits;
+
+       if (ast_strlen_zero(duration_arg)) {
+               ast_log(LOG_ERROR, "No duration value provided for the timeout ('S') option.\n");
+               return -1;
+       }
+
+       if (ast_bridge_features_limits_construct(&hold_limits)) {
+               ast_log(LOG_ERROR, "Could not construct duration limits. Bridge canceled.\n");
+               return -1;
+       }
+
+       if (sscanf(duration_arg, "%u", &(hold_limits.duration)) != 1 || hold_limits.duration == 0) {
+               ast_log(LOG_ERROR, "Duration value provided for the timeout ('S') option must be greater than 0\n");
+               ast_bridge_features_limits_destroy(&hold_limits);
+               return -1;
+       }
+
+       /* Limits struct holds time as milliseconds, so muliply 1000x */
+       hold_limits.duration *= 1000;
+       ast_bridge_features_set_limits(features, &hold_limits, 1 /* remove_on_pull */);
+       ast_bridge_features_limits_destroy(&hold_limits);
+
+       return 0;
+}
+
+static void apply_option_moh(struct ast_channel *chan, char *class_arg)
+{
+       ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
+       ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", class_arg);
+}
+
+static void apply_option_ringing(struct ast_channel *chan)
+{
+       ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing");
+}
+
+static int process_options(struct ast_channel *chan, struct ast_flags *flags, char **opts, struct ast_bridge_features *features)
+{
+       if (ast_test_flag(flags, MUXFLAG_TIMEOUT)) {
+               if (apply_option_timeout(features, opts[OPT_ARG_TIMEOUT])) {
+                       return -1;
+               }
+       }
+
+       if (ast_test_flag(flags, MUXFLAG_ANNOUNCER)) {
+               /* Announcer specific stuff */
+               ast_channel_add_bridge_role(chan, "announcer");
+       } else {
+               /* Non Announcer specific stuff */
+               ast_channel_add_bridge_role(chan, "holding_participant");
+
+               if (ast_test_flag(flags, MUXFLAG_PLAYMOH)) {
+                       apply_option_moh(chan, opts[OPT_ARG_MOHCLASS]);
+               } else if (ast_test_flag(flags, MUXFLAG_RINGING)) {
+                       apply_option_ringing(chan);
+               }
+       }
+
+       return 0;
+}
+
+static int bridgewait_exec(struct ast_channel *chan, const char *data)
+{
+       struct ast_bridge_features chan_features;
+       struct ast_flags flags = { 0 };
+       char *parse;
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(options);
+               AST_APP_ARG(other);             /* Any remaining unused arguments */
+       );
+
+       ast_mutex_lock(&bridgewait_lock);
+       if (!holding_bridge) {
+               holding_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_HOLDING,
+                       AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+                               | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+       }
+       ast_mutex_unlock(&bridgewait_lock);
+       if (!holding_bridge) {
+               ast_log(LOG_ERROR, "Could not create holding bridge for '%s'.\n", ast_channel_name(chan));
+               return -1;
+       }
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_bridge_features_init(&chan_features)) {
+               ast_bridge_features_cleanup(&chan_features);
+               return -1;
+       }
+
+       if (args.options) {
+               char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
+               ast_app_parse_options(bridgewait_opts, &flags, opts, args.options);
+               if (process_options(chan, &flags, opts, &chan_features)) {
+                       ast_bridge_features_cleanup(&chan_features);
+                       return -1;
+               }
+       }
+
+       ast_bridge_join(holding_bridge, chan, NULL, &chan_features, NULL, 0);
+
+       ast_bridge_features_cleanup(&chan_features);
+       return ast_check_hangup_locked(chan) ? -1 : 0;
+}
+
+static int unload_module(void)
+{
+       ao2_cleanup(holding_bridge);
+       holding_bridge = NULL;
+
+       return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+       return ast_register_application_xml(app, bridgewait_exec);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Place the channel into a holding bridge application");
index 8c98ed7b723fa1ea057925f66f19cc34a49db1d4..f636e02480187e88b2751898630731de8d0e6816 100644 (file)
@@ -96,10 +96,6 @@ static int asyncgoto_exec(struct ast_channel *chan, const char *data)
                return 0;
        }
 
-       if (ast_channel_pbx(chan2)) {
-               ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_HANGUP_DONT); /* don't let the after-bridge code run the h-exten */
-       }
-
        res = ast_async_parseable_goto(chan2, args.label);
 
        chan2 = ast_channel_unref(chan2);
index c5adb78adf6e47ad229218c3093ae5b2c423c01a..8e45907819c1a8d54bd768ca00dee51bfd6025f2 100644 (file)
@@ -482,15 +482,18 @@ static struct ast_generator spygen = {
 static int start_spying(struct ast_autochan *autochan, const char *spychan_name, struct ast_audiohook *audiohook)
 {
        int res = 0;
-       struct ast_channel *peer = NULL;
 
        ast_log(LOG_NOTICE, "Attaching %s to %s\n", spychan_name, ast_channel_name(autochan->chan));
 
        ast_set_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC | AST_AUDIOHOOK_SMALL_QUEUE);
        res = ast_audiohook_attach(autochan->chan, audiohook);
 
-       if (!res && ast_test_flag(ast_channel_flags(autochan->chan), AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(autochan->chan))) {
-               ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);
+       if (!res) {
+               ast_channel_lock(autochan->chan);
+               if (ast_channel_is_bridged(autochan->chan)) {
+                       ast_softhangup_nolock(autochan->chan, AST_SOFTHANGUP_UNBRIDGE);
+               }
+               ast_channel_unlock(autochan->chan);
        }
        return res;
 }
index d4cce4b46c4148cc4069165dc9135e1fa74ea461..5107bcd5cc10b9ee42443b79c0106b37dd23ee0d 100644 (file)
@@ -67,6 +67,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/paths.h"
 #include "asterisk/manager.h"
 #include "asterisk/test.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/json.h"
 
 /*** DOCUMENTATION
        <application name="ConfBridge" language="en_US">
@@ -303,7 +306,7 @@ enum {
 };
 
 /*! \brief Container to hold all conference bridges in progress */
-static struct ao2_container *conference_bridges;
+struct ao2_container *conference_bridges;
 
 static void leave_conference(struct confbridge_user *user);
 static int play_sound_number(struct confbridge_conference *conference, int say_number);
@@ -412,221 +415,77 @@ const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds
        return "";
 }
 
-static void send_conf_start_event(const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a conference starts.</synopsis>
-                       <syntax>
-                               <parameter name="Conference">
-                                       <para>The name of the Confbridge conference.</para>
-                               </parameter>
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeEnd</ref>
-                               <ref type="application">ConfBridge</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       manager_event(EVENT_FLAG_CALL, "ConfbridgeStart", "Conference: %s\r\n", conf_name);
-}
-
-static void send_conf_end_event(const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a conference ends.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeStart</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       manager_event(EVENT_FLAG_CALL, "ConfbridgeEnd", "Conference: %s\r\n", conf_name);
-}
-
-static void send_join_event(struct ast_channel *chan, const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a channel joins a Confbridge conference.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeLeave</ref>
-                               <ref type="application">ConfBridge</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeJoin",
-               "Channel: %s\r\n"
-               "Uniqueid: %s\r\n"
-               "Conference: %s\r\n"
-               "CallerIDnum: %s\r\n"
-               "CallerIDname: %s\r\n",
-               ast_channel_name(chan),
-               ast_channel_uniqueid(chan),
-               conf_name,
-               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"),
-               S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>")
-       );
+static void send_conf_stasis(struct confbridge_conference *conference, struct ast_channel *chan, const char *type, struct ast_json *extras, int channel_topic)
+{
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
+
+       json_object = ast_json_pack("{s: s, s: s}",
+               "type", type,
+               "conference", conference->name);
+
+       if (!json_object) {
+               return;
+       }
+
+       if (extras) {
+               ast_json_object_update(json_object, extras);
+       }
+
+       msg = ast_bridge_blob_create(confbridge_message_type(),
+                                        conference->bridge,
+                                        chan,
+                                        json_object);
+       if (!msg) {
+               return;
+       }
+
+       if (channel_topic) {
+               stasis_publish(ast_channel_topic(chan), msg);
+       } else {
+               stasis_publish(ast_bridge_topic(conference->bridge), msg);
+       }
+
 }
 
-static void send_leave_event(struct ast_channel *chan, const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a channel leaves a Confbridge conference.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeJoin</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeLeave",
-               "Channel: %s\r\n"
-               "Uniqueid: %s\r\n"
-               "Conference: %s\r\n"
-               "CallerIDnum: %s\r\n"
-               "CallerIDname: %s\r\n",
-               ast_channel_name(chan),
-               ast_channel_uniqueid(chan),
-               conf_name,
-               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"),
-               S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>")
-       );
+static void send_conf_start_event(struct confbridge_conference *conference)
+{
+       send_conf_stasis(conference, NULL, "confbridge_start", NULL, 0);
 }
 
-static void send_start_record_event(const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a conference recording starts.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeStopRecord</ref>
-                               <ref type="application">ConfBridge</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-
-       manager_event(EVENT_FLAG_CALL, "ConfbridgeStartRecord", "Conference: %s\r\n", conf_name);
-}
-
-static void send_stop_record_event(const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a conference recording stops.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeStartRecord</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       manager_event(EVENT_FLAG_CALL, "ConfbridgeStopRecord", "Conference: %s\r\n", conf_name);
-}
-
-static void send_mute_event(struct ast_channel *chan, const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a Confbridge participant mutes.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeUnmute</ref>
-                               <ref type="application">ConfBridge</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeMute",
-               "Channel: %s\r\n"
-               "Uniqueid: %s\r\n"
-               "Conference: %s\r\n"
-               "CallerIDnum: %s\r\n"
-               "CallerIDname: %s\r\n",
-               ast_channel_name(chan),
-               ast_channel_uniqueid(chan),
-               conf_name,
-               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"),
-               S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>")
-       );
+static void send_conf_end_event(struct confbridge_conference *conference)
+{
+       send_conf_stasis(conference, NULL, "confbridge_end", NULL, 0);
 }
 
-static void send_unmute_event(struct ast_channel *chan, const char *conf_name)
-{
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a Confbridge participant unmutes.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                       </syntax>
-                       <see-also>
-                               <ref type="managerEvent">ConfbridgeMute</ref>
-                       </see-also>
-               </managerEventInstance>
-       ***/
-       ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeUnmute",
-               "Channel: %s\r\n"
-               "Uniqueid: %s\r\n"
-               "Conference: %s\r\n"
-               "CallerIDnum: %s\r\n"
-               "CallerIDname: %s\r\n",
-               ast_channel_name(chan),
-               ast_channel_uniqueid(chan),
-               conf_name,
-               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, "<unknown>"),
-               S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, "<unknown>")
-       );
+static void send_join_event(struct ast_channel *chan, struct confbridge_conference *conference)
+{
+       send_conf_stasis(conference, chan, "confbridge_join", NULL, 0);
 }
 
+static void send_leave_event(struct ast_channel *chan, struct confbridge_conference *conference)
+{
+       send_conf_stasis(conference, chan, "confbridge_leave", NULL, 0);
+}
 
-static struct ast_frame *rec_read(struct ast_channel *ast)
+static void send_start_record_event(struct confbridge_conference *conference)
 {
-       return &ast_null_frame;
+       send_conf_stasis(conference, NULL, "confbridge_record", NULL, 0);
 }
-static int rec_write(struct ast_channel *ast, struct ast_frame *f)
+
+static void send_stop_record_event(struct confbridge_conference *conference)
 {
-       return 0;
+       send_conf_stasis(conference, NULL, "confbridge_stop_record", NULL, 0);
 }
-static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
-static struct ast_channel_tech record_tech = {
-       .type = "ConfBridgeRec",
-       .description = "Conference Bridge Recording Channel",
-       .requester = rec_request,
-       .read = rec_read,
-       .write = rec_write,
-};
-static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
-{
-       struct ast_channel *tmp;
-       struct ast_format fmt;
-       const char *conf_name = data;
-       if (!(tmp = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", NULL, 0,
-               "ConfBridgeRecorder/conf-%s-uid-%d",
-               conf_name,
-               (int) ast_random()))) {
-               return NULL;
-       }
-       ast_format_set(&fmt, AST_FORMAT_SLINEAR, 0);
-       ast_channel_tech_set(tmp, &record_tech);
-       ast_format_cap_add_all(ast_channel_nativeformats(tmp));
-       ast_format_copy(ast_channel_writeformat(tmp), &fmt);
-       ast_format_copy(ast_channel_rawwriteformat(tmp), &fmt);
-       ast_format_copy(ast_channel_readformat(tmp), &fmt);
-       ast_format_copy(ast_channel_rawreadformat(tmp), &fmt);
-       return tmp;
+
+static void send_mute_event(struct ast_channel *chan, struct confbridge_conference *conference)
+{
+       send_conf_stasis(conference, chan, "confbridge_mute", NULL, 1);
+}
+
+static void send_unmute_event(struct ast_channel *chan, struct confbridge_conference *conference)
+{
+       send_conf_stasis(conference, chan, "confbridge_unmute", NULL, 1);
 }
 
 static void set_rec_filename(struct confbridge_conference *conference, struct ast_str **filename, int is_new)
@@ -682,6 +541,7 @@ static void *record_thread(void *obj)
        struct ast_channel *chan;
        struct ast_str *filename = ast_str_alloca(PATH_MAX);
        struct ast_str *orig_rec_file = NULL;
+       struct ast_bridge_features features;
 
        ast_mutex_lock(&conference->record_lock);
        if (!mixmonapp) {
@@ -691,20 +551,29 @@ static void *record_thread(void *obj)
                ao2_ref(conference, -1);
                return NULL;
        }
+       if (ast_bridge_features_init(&features)) {
+               ast_bridge_features_cleanup(&features);
+               conference->record_thread = AST_PTHREADT_NULL;
+               ast_mutex_unlock(&conference->record_lock);
+               ao2_ref(conference, -1);
+               return NULL;
+       }
+       ast_set_flag(&features.feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE);
 
        /* XXX If we get an EXIT right here, START will essentially be a no-op */
        while (conference->record_state != CONF_RECORD_EXIT) {
                set_rec_filename(conference, &filename,
-                                is_new_rec_file(conference->b_profile.rec_file, &orig_rec_file));
+                       is_new_rec_file(conference->b_profile.rec_file, &orig_rec_file));
                chan = ast_channel_ref(conference->record_chan);
                ast_answer(chan);
                pbx_exec(chan, mixmonapp, ast_str_buffer(filename));
-               ast_bridge_join(conference->bridge, chan, NULL, NULL, NULL);
+               ast_bridge_join(conference->bridge, chan, NULL, &features, NULL, 0);
 
                ast_hangup(chan); /* This will eat this thread's reference to the channel as well */
                /* STOP has been called. Wait for either a START or an EXIT */
                ast_cond_wait(&conference->record_cond, &conference->record_lock);
        }
+       ast_bridge_features_cleanup(&features);
        ast_free(orig_rec_file);
        ast_mutex_unlock(&conference->record_lock);
        ao2_ref(conference, -1);
@@ -739,7 +608,7 @@ static int conf_stop_record(struct confbridge_conference *conference)
        ast_queue_frame(chan, &ast_null_frame);
        chan = ast_channel_unref(chan);
        ast_test_suite_event_notify("CONF_STOP_RECORD", "Message: stopped conference recording channel\r\nConference: %s", conference->b_profile.name);
-       send_stop_record_event(conference->name);
+       send_stop_record_event(conference);
 
        return 0;
 }
@@ -783,8 +652,7 @@ static int conf_stop_record_thread(struct confbridge_conference *conference)
 static int conf_start_record(struct confbridge_conference *conference)
 {
        struct ast_format_cap *cap;
-       struct ast_format tmpfmt;
-       int cause;
+       struct ast_format format;
 
        if (conference->record_state != CONF_RECORD_STOP) {
                return -1;
@@ -795,25 +663,26 @@ static int conf_start_record(struct confbridge_conference *conference)
                return -1;
        }
 
-       if (!(cap = ast_format_cap_alloc_nolock())) {
+       cap = ast_format_cap_alloc_nolock();
+       if (!cap) {
                return -1;
        }
 
-       ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
+       ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0));
 
-       if (!(conference->record_chan = ast_request("ConfBridgeRec", cap, NULL, conference->name, &cause))) {
-               cap = ast_format_cap_destroy(cap);
+       conference->record_chan = ast_request("CBRec", cap, NULL,
+               conference->name, NULL);
+       cap = ast_format_cap_destroy(cap);
+       if (!conference->record_chan) {
                return -1;
        }
 
-       cap = ast_format_cap_destroy(cap);
-
        conference->record_state = CONF_RECORD_START;
        ast_mutex_lock(&conference->record_lock);
        ast_cond_signal(&conference->record_cond);
        ast_mutex_unlock(&conference->record_lock);
        ast_test_suite_event_notify("CONF_START_RECORD", "Message: started conference recording channel\r\nConference: %s", conference->b_profile.name);
-       send_start_record_event(conference->name);
+       send_start_record_event(conference);
 
        return 0;
 }
@@ -1017,10 +886,7 @@ static void destroy_conference_bridge(void *obj)
        ast_debug(1, "Destroying conference bridge '%s'\n", conference->name);
 
        if (conference->playback_chan) {
-               struct ast_channel *underlying_channel = ast_channel_tech(conference->playback_chan)->bridged_channel(conference->playback_chan, NULL);
-               if (underlying_channel) {
-                       ast_hangup(underlying_channel);
-               }
+               conf_announce_channel_depart(conference->playback_chan);
                ast_hangup(conference->playback_chan);
                conference->playback_chan = NULL;
        }
@@ -1252,7 +1118,7 @@ void conf_ended(struct confbridge_conference *conference)
 {
        /* Called with a reference to conference */
        ao2_unlink(conference_bridges, conference);
-       send_conf_end_event(conference->name);
+       send_conf_end_event(conference);
        conf_stop_record_thread(conference);
 }
 
@@ -1314,7 +1180,9 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
                conf_bridge_profile_copy(&conference->b_profile, &user->b_profile);
 
                /* Create an actual bridge that will do the audio mixing */
-               if (!(conference->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_MULTIMIX, 0))) {
+               conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX,
+                       AST_BRIDGE_FLAG_MASQUERADE_ONLY | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY);
+               if (!conference->bridge) {
                        ao2_ref(conference, -1);
                        conference = NULL;
                        ao2_unlock(conference_bridges);
@@ -1351,7 +1219,7 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
                        ao2_unlock(conference);
                }
 
-               send_conf_start_event(conference->name);
+               send_conf_start_event(conference);
                ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name);
        }
 
@@ -1452,74 +1320,59 @@ static void leave_conference(struct confbridge_user *user)
 
 /*!
  * \internal
- * \brief allocates playback chan on a channel
+ * \brief Allocate playback channel for a conference.
  * \pre expects conference to be locked before calling this function
  */
 static int alloc_playback_chan(struct confbridge_conference *conference)
 {
-       int cause;
        struct ast_format_cap *cap;
-       struct ast_format tmpfmt;
+       struct ast_format format;
 
-       if (conference->playback_chan) {
-               return 0;
-       }
-       if (!(cap = ast_format_cap_alloc_nolock())) {
-               return -1;
-       }
-       ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
-       if (!(conference->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) {
-               cap = ast_format_cap_destroy(cap);
+       cap = ast_format_cap_alloc_nolock();
+       if (!cap) {
                return -1;
        }
+       ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0));
+       conference->playback_chan = ast_request("CBAnn", cap, NULL,
+               conference->name, NULL);
        cap = ast_format_cap_destroy(cap);
-
-       ast_channel_internal_bridge_set(conference->playback_chan, conference->bridge);
-
-       if (ast_call(conference->playback_chan, "", 0)) {
-               ast_hangup(conference->playback_chan);
-               conference->playback_chan = NULL;
+       if (!conference->playback_chan) {
                return -1;
        }
 
-       ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference->name);
+       ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n",
+               ast_channel_name(conference->playback_chan), conference->name);
        return 0;
 }
 
 static int play_sound_helper(struct confbridge_conference *conference, const char *filename, int say_number)
 {
-       struct ast_channel *underlying_channel;
-
        /* Do not waste resources trying to play files that do not exist */
        if (!ast_strlen_zero(filename) && !sound_file_exists(filename)) {
                return 0;
        }
 
        ast_mutex_lock(&conference->playback_lock);
-       if (!(conference->playback_chan)) {
-               if (alloc_playback_chan(conference)) {
-                       ast_mutex_unlock(&conference->playback_lock);
-                       return -1;
-               }
-               underlying_channel = ast_channel_tech(conference->playback_chan)->bridged_channel(conference->playback_chan, NULL);
-       } else {
-               /* Channel was already available so we just need to add it back into the bridge */
-               underlying_channel = ast_channel_tech(conference->playback_chan)->bridged_channel(conference->playback_chan, NULL);
-               if (ast_bridge_impart(conference->bridge, underlying_channel, NULL, NULL, 0)) {
-                       ast_mutex_unlock(&conference->playback_lock);
-                       return -1;
-               }
+       if (!conference->playback_chan && alloc_playback_chan(conference)) {
+               ast_mutex_unlock(&conference->playback_lock);
+               return -1;
+       }
+       if (conf_announce_channel_push(conference->playback_chan)) {
+               ast_mutex_unlock(&conference->playback_lock);
+               return -1;
        }
 
        /* The channel is all under our control, in goes the prompt */
        if (!ast_strlen_zero(filename)) {
                ast_stream_and_wait(conference->playback_chan, filename, "");
        } else if (say_number >= 0) {
-               ast_say_number(conference->playback_chan, say_number, "", ast_channel_language(conference->playback_chan), NULL);
+               ast_say_number(conference->playback_chan, say_number, "",
+                       ast_channel_language(conference->playback_chan), NULL);
        }
 
-       ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", ast_channel_name(underlying_channel), conference->bridge);
-       ast_bridge_depart(conference->bridge, underlying_channel);
+       ast_debug(1, "Departing announcer channel '%s' from conference bridge '%s'\n",
+               ast_channel_name(conference->playback_chan), conference->name);
+       conf_announce_channel_depart(conference->playback_chan);
 
        ast_mutex_unlock(&conference->playback_lock);
 
@@ -1550,43 +1403,25 @@ static void conf_handle_talker_destructor(void *pvt_data)
        ast_free(pvt_data);
 }
 
-static void conf_handle_talker_cb(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data)
+static void conf_handle_talker_cb(struct ast_bridge_channel *bridge_channel, void *pvt_data, int talking)
 {
-       char *conf_name = pvt_data;
-       int talking;
+       const char *conf_name = pvt_data;
+       struct confbridge_conference *conference = ao2_find(conference_bridges, conf_name, OBJ_KEY);
+       struct ast_json *talking_extras;
 
-       switch (bridge_channel->state) {
-       case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
-               talking = 1;
-               break;
-       case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
-               talking = 0;
-               break;
-       default:
-               return; /* uhh this shouldn't happen, but bail if it does. */
-       }
-
-       /* notify AMI someone is has either started or stopped talking */
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when a conference participant has started or stopped talking.</synopsis>
-                       <syntax>
-                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ConfbridgeStart']/managerEventInstance/syntax/parameter[@name='Conference'])" />
-                               <parameter name="TalkingStatus">
-                                       <enumlist>
-                                               <enum name="on"/>
-                                               <enum name="off"/>
-                                       </enumlist>
-                               </parameter>
-                       </syntax>
-               </managerEventInstance>
-       ***/
-       ast_manager_event(bridge_channel->chan, EVENT_FLAG_CALL, "ConfbridgeTalking",
-             "Channel: %s\r\n"
-             "Uniqueid: %s\r\n"
-             "Conference: %s\r\n"
-             "TalkingStatus: %s\r\n",
-             ast_channel_name(bridge_channel->chan), ast_channel_uniqueid(bridge_channel->chan), conf_name, talking ? "on" : "off");
+       if (!conference) {
+               return;
+       }
+
+       talking_extras = ast_json_pack("{s: s}",
+                                        "talking_status", talking ? "on" : "off");
+
+       if (!talking_extras) {
+               return;
+       }
+
+       send_conf_stasis(conference, bridge_channel->chan, "confbridge_talking", talking_extras, 0);
+       ast_json_unref(talking_extras);
 }
 
 static int conf_get_pin(struct ast_channel *chan, struct confbridge_user *user)
@@ -1681,12 +1516,16 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
                AST_APP_ARG(u_profile_name);
                AST_APP_ARG(menu_name);
        );
-       ast_bridge_features_init(&user.features);
 
        if (ast_channel_state(chan) != AST_STATE_UP) {
                ast_answer(chan);
        }
 
+       if (ast_bridge_features_init(&user.features)) {
+               res = -1;
+               goto confbridge_cleanup;
+       }
+
        if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app);
                res = -1; /* invalid PIN */
@@ -1832,13 +1671,14 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
        conf_moh_unsuspend(&user);
 
        /* Join our conference bridge for real */
-       send_join_event(user.chan, conference->name);
+       send_join_event(user.chan, conference);
        ast_bridge_join(conference->bridge,
                chan,
                NULL,
                &user.features,
-               &user.tech_args);
-       send_leave_event(user.chan, conference->name);
+               &user.tech_args,
+               0);
+       send_leave_event(user.chan, conference);
 
        /* if we're shutting down, don't attempt to do further processing */
        if (ast_shutting_down()) {
@@ -1905,9 +1745,9 @@ static int action_toggle_mute(struct confbridge_conference *conference,
                user->features.mute = (!user->features.mute ? 1 : 0);
                ast_test_suite_event_notify("CONF_MUTE", "Message: participant %s %s\r\nConference: %s\r\nChannel: %s", ast_channel_name(chan), user->features.mute ? "muted" : "unmuted", user->b_profile.name, ast_channel_name(chan));
                if (user->features.mute) {
-                       send_mute_event(chan, conference->name);
-               } else { 
-                       send_unmute_event(chan, conference->name);
+                       send_mute_event(chan, conference);
+               } else {
+                       send_unmute_event(chan, conference);
                }
        }
        return ast_stream_and_wait(chan, (user->features.mute ?
@@ -3207,6 +3047,46 @@ void conf_remove_user_waiting(struct confbridge_conference *conference, struct c
        conference->waitingusers--;
 }
 
+/*!
+ * \internal
+ * \brief Unregister a ConfBridge channel technology.
+ * \since 12.0.0
+ *
+ * \param tech What to unregister.
+ *
+ * \return Nothing
+ */
+static void unregister_channel_tech(struct ast_channel_tech *tech)
+{
+       ast_channel_unregister(tech);
+       tech->capabilities = ast_format_cap_destroy(tech->capabilities);
+}
+
+/*!
+ * \internal
+ * \brief Register a ConfBridge channel technology.
+ * \since 12.0.0
+ *
+ * \param tech What to register.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int register_channel_tech(struct ast_channel_tech *tech)
+{
+       tech->capabilities = ast_format_cap_alloc();
+       if (!tech->capabilities) {
+               return -1;
+       }
+       ast_format_cap_add_all(tech->capabilities);
+       if (ast_channel_register(tech)) {
+               ast_log(LOG_ERROR, "Unable to register channel technology %s(%s).\n",
+                       tech->type, tech->description);
+               return -1;
+       }
+       return 0;
+}
+
 /*! \brief Called when module is being unloaded */
 static int unload_module(void)
 {
@@ -3228,14 +3108,17 @@ static int unload_module(void)
        ast_manager_unregister("ConfbridgeStopRecord");
        ast_manager_unregister("ConfbridgeSetSingleVideoSrc");
 
+       /* Unsubscribe from stasis confbridge message type and clean it up. */
+       manager_confbridge_shutdown();
+
        /* Get rid of the conference bridges container. Since we only allow dynamic ones none will be active. */
        ao2_cleanup(conference_bridges);
        conference_bridges = NULL;
 
        conf_destroy_config();
 
-       ast_channel_unregister(&record_tech);
-       record_tech.capabilities = ast_format_cap_destroy(record_tech.capabilities);
+       unregister_channel_tech(conf_announce_get_tech());
+       unregister_channel_tech(conf_record_get_tech());
 
        return 0;
 }
@@ -3259,13 +3142,8 @@ static int load_module(void)
                return AST_MODULE_LOAD_DECLINE;
        }
 
-       if (!(record_tech.capabilities = ast_format_cap_alloc())) {
-               unload_module();
-               return AST_MODULE_LOAD_FAILURE;
-       }
-       ast_format_cap_add_all(record_tech.capabilities);
-       if (ast_channel_register(&record_tech)) {
-               ast_log(LOG_ERROR, "Unable to register ConfBridge recorder.\n");
+       if (register_channel_tech(conf_record_get_tech())
+               || register_channel_tech(conf_announce_get_tech())) {
                unload_module();
                return AST_MODULE_LOAD_FAILURE;
        }
@@ -3278,6 +3156,9 @@ static int load_module(void)
                return AST_MODULE_LOAD_FAILURE;
        }
 
+       /* Setup manager stasis subscriptions */
+       res |= manager_confbridge_init();
+
        res |= ast_register_application_xml(app, confbridge_exec);
 
        res |= ast_custom_function_register(&confbridge_function);
index d3d37216aa5ca47c689d0fb99345a048de3bc2b7..35c9ad8bfb9c76a6cf613be1d87c7ccbe085df7c 100644 (file)
@@ -26,7 +26,6 @@
  */
 
 /*** MODULEINFO
-       <depend>chan_local</depend>
        <support_level>core</support_level>
  ***/
 
@@ -67,6 +66,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/ccss.h"
 #include "asterisk/indications.h"
 #include "asterisk/framehook.h"
+#include "asterisk/bridging.h"
 #include "asterisk/stasis_channels.h"
 
 /*** DOCUMENTATION
@@ -2037,6 +2037,40 @@ static int dial_handle_playtones(struct ast_channel *chan, const char *data)
        return res;
 }
 
+/*!
+ * \internal
+ * \brief Setup the after bridge goto location on the peer.
+ * \since 12.0.0
+ *
+ * \param chan Calling channel for bridge.
+ * \param peer Peer channel for bridge.
+ * \param opts Dialing option flags.
+ * \param opt_args Dialing option argument strings.
+ *
+ * \return Nothing
+ */
+static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_channel *peer, struct ast_flags64 *opts, char *opt_args[])
+{
+       const char *context;
+       const char *extension;
+       int priority;
+
+       if (ast_test_flag64(opts, OPT_PEER_H)) {
+               ast_channel_lock(chan);
+               context = ast_strdupa(ast_channel_context(chan));
+               ast_channel_unlock(chan);
+               ast_after_bridge_set_h(peer, context);
+       } else if (ast_test_flag64(opts, OPT_CALLEE_GO_ON)) {
+               ast_channel_lock(chan);
+               context = ast_strdupa(ast_channel_context(chan));
+               extension = ast_strdupa(ast_channel_exten(chan));
+               priority = ast_channel_priority(chan);
+               ast_channel_unlock(chan);
+               ast_after_bridge_set_go_on(peer, context, extension, priority,
+                       opt_args[OPT_ARG_CALLEE_GO_ON]);
+       }
+}
+
 static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast_flags64 *peerflags, int *continue_exec)
 {
        int res = -1; /* default: error */
@@ -2974,6 +3008,14 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                }
 
                if (res) { /* some error */
+                       if (!ast_check_hangup(chan) && ast_check_hangup(peer)) {
+                               ast_channel_hangupcause_set(chan, ast_channel_hangupcause(peer));
+                       }
+                       setup_peer_after_bridge_goto(chan, peer, &opts, opt_args);
+                       if (ast_after_bridge_goto_setup(peer)
+                               || ast_pbx_start(peer)) {
+                               ast_autoservice_chan_hangup_peer(chan, peer);
+                       }
                        res = -1;
                } else {
                        if (ast_test_flag64(peerflags, OPT_CALLEE_TRANSFER))
@@ -2996,8 +3038,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                                ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMIXMON);
                        if (ast_test_flag64(peerflags, OPT_CALLER_MIXMONITOR))
                                ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMIXMON);
-                       if (ast_test_flag64(peerflags, OPT_GO_ON))
-                               ast_set_flag(&(config.features_caller), AST_FEATURE_NO_H_EXTEN);
 
                        config.end_bridge_callback = end_bridge_callback;
                        config.end_bridge_callback_data = chan;
@@ -3029,38 +3069,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 
                                ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0);
                        }
+/* BUGBUG bridge needs to set hangup cause on chan when peer breaks the bridge. */
+                       setup_peer_after_bridge_goto(chan, peer, &opts, opt_args);
                        res = ast_bridge_call(chan, peer, &config);
                }
-
-               ast_channel_context_set(peer, ast_channel_context(chan));
-
-               if (ast_test_flag64(&opts, OPT_PEER_H)
-                       && ast_exists_extension(peer, ast_channel_context(peer), "h", 1,
-                               S_COR(ast_channel_caller(peer)->id.number.valid, ast_channel_caller(peer)->id.number.str, NULL))) {
-                       ast_autoservice_start(chan);
-                       ast_pbx_h_exten_run(peer, ast_channel_context(peer));
-                       ast_autoservice_stop(chan);
-               }
-               if (!ast_check_hangup(peer)) {
-                       if (ast_test_flag64(&opts, OPT_CALLEE_GO_ON)) {
-                               int goto_res;
-
-                               if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) {
-                                       ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]);
-                                       goto_res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]);
-                               } else { /* F() */
-                                       goto_res = ast_goto_if_exists(peer, ast_channel_context(chan),
-                                               ast_channel_exten(chan), ast_channel_priority(chan) + 1);
-                               }
-                               if (!goto_res && !ast_pbx_start(peer)) {
-                                       /* The peer is now running its own PBX. */
-                                       goto out;
-                               }
-                       }
-               } else if (!ast_check_hangup(chan)) {
-                       ast_channel_hangupcause_set(chan, ast_channel_hangupcause(peer));
-               }
-               ast_autoservice_chan_hangup_peer(chan, peer);
        }
 out:
        if (moh) {
index 4a80a3d13ea269bebed442b03a7e8e12a824dde3..722f15541757cd9545900d0cfa11a61b8eeaba6c 100644 (file)
@@ -41,6 +41,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/channel.h"
 #include "asterisk/app.h"
 #include "asterisk/translate.h"
+#include "asterisk/bridging.h"
 
 /*** DOCUMENTATION
        <application name="DumpChan" language="en_US">
@@ -77,6 +78,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
        char pgrp[256];
        struct ast_str *write_transpath = ast_str_alloca(256);
        struct ast_str *read_transpath = ast_str_alloca(256);
+       struct ast_bridge *bridge;
 
        now = ast_tvnow();
        memset(buf, 0, size);
@@ -90,6 +92,9 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
                sec = elapsed_seconds % 60;
        }
 
+       ast_channel_lock(c);
+       bridge = ast_channel_get_bridge(c);
+       ast_channel_unlock(c);
        snprintf(buf,size,
                "Name=               %s\n"
                "Type=               %s\n"
@@ -117,8 +122,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
                "Framesout=          %d %s\n"
                "TimetoHangup=       %ld\n"
                "ElapsedTime=        %dh%dm%ds\n"
-               "DirectBridge=       %s\n"
-               "IndirectBridge=     %s\n"
+               "BridgeID=           %s\n"
                "Context=            %s\n"
                "Extension=          %s\n"
                "Priority=           %d\n"
@@ -158,8 +162,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
                hour,
                min,
                sec,
-               ast_channel_internal_bridged_channel(c) ? ast_channel_name(ast_channel_internal_bridged_channel(c)) : "<none>",
-               ast_bridged_channel(c) ? ast_channel_name(ast_bridged_channel(c)) : "<none>", 
+               bridge ? bridge->uniqueid : "(Not bridged)",
                ast_channel_context(c),
                ast_channel_exten(c),
                ast_channel_priority(c),
@@ -169,6 +172,7 @@ static int serialize_showchan(struct ast_channel *c, char *buf, size_t size)
                ast_channel_data(c) ? S_OR(ast_channel_data(c), "(Empty)") : "(None)",
                (ast_test_flag(ast_channel_flags(c), AST_FLAG_BLOCKING) ? ast_channel_blockproc(c) : "(Not Blocking)"));
 
+       ao2_cleanup(bridge);
        return 0;
 }
 
index 83f583bb3777339268f41a9f5a4ae176eac967a1..43f1967087bf246abf4a0255fa5b188ade1cb935 100644 (file)
@@ -36,7 +36,6 @@
  */
 
 /*** MODULEINFO
-       <depend>chan_local</depend>
        <support_level>core</support_level>
  ***/
 
@@ -1520,7 +1519,6 @@ static int app_exec(struct ast_channel *chan, const char *data)
                }
 
                res = ast_bridge_call(caller, outbound, &config);
-               ast_autoservice_chan_hangup_peer(caller, outbound);
        }
 
 outrun:
index 6e7976ec9fd4ba4d269339d8b626754b3ee02f51..e8d4903f0b1d9d4513a43811a9aba0d8ff6d0158 100644 (file)
@@ -411,7 +411,6 @@ static void destroy_monitor_audiohook(struct mixmonitor *mixmonitor)
 
 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
 {
-       struct ast_channel *peer = NULL;
        int res = 0;
 
        if (!chan)
@@ -419,8 +418,13 @@ static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
 
        ast_audiohook_attach(chan, audiohook);
 
-       if (!res && ast_test_flag(ast_channel_flags(chan), AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan)))
-               ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
+       if (!res) {
+               ast_channel_lock(chan);
+               if (ast_channel_is_bridged(chan)) {
+                       ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE);
+               }
+               ast_channel_unlock(chan);
+       }
 
        return res;
 }
diff --git a/apps/app_parkandannounce.c b/apps/app_parkandannounce.c
deleted file mode 100644 (file)
index 6d6ccae..0000000
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2006, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * Author: Ben Miller <bgmiller@dccinc.com>
- *    With TONS of help from Mark!
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief ParkAndAnnounce application for Asterisk
- *
- * \author Ben Miller <bgmiller@dccinc.com>
- * \arg With TONS of help from Mark!
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
-       <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/file.h"
-#include "asterisk/channel.h"
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/features.h"
-#include "asterisk/say.h"
-#include "asterisk/lock.h"
-#include "asterisk/utils.h"
-#include "asterisk/app.h"
-
-/*** DOCUMENTATION
-       <application name="ParkAndAnnounce" language="en_US">
-               <synopsis>
-                       Park and Announce.
-               </synopsis>
-               <syntax>
-                       <parameter name="announce_template" required="true" argsep=":">
-                               <argument name="announce" required="true">
-                                       <para>Colon-separated list of files to announce. The word
-                                       <literal>PARKED</literal> will be replaced by a say_digits of the extension in which
-                                       the call is parked.</para>
-                               </argument>
-                               <argument name="announce1" multiple="true" />
-                       </parameter>
-                       <parameter name="timeout" required="true">
-                               <para>Time in seconds before the call returns into the return
-                               context.</para>
-                       </parameter>
-                       <parameter name="dial" required="true">
-                               <para>The app_dial style resource to call to make the
-                               announcement. Console/dsp calls the console.</para>
-                       </parameter>
-                       <parameter name="return_context">
-                               <para>The goto-style label to jump the call back into after
-                               timeout. Default <literal>priority+1</literal>.</para>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>Park a call into the parkinglot and announce the call to another channel.</para>
-                       <para>The variable <variable>PARKEDAT</variable> will contain the parking extension
-                       into which the call was placed.  Use with the Local channel to allow the dialplan to make
-                       use of this information.</para>
-               </description>
-               <see-also>
-                       <ref type="application">Park</ref>
-                       <ref type="application">ParkedCall</ref>
-               </see-also>
-       </application>
- ***/
-
-static char *app = "ParkAndAnnounce";
-
-static int parkandannounce_exec(struct ast_channel *chan, const char *data)
-{
-       int res = -1;
-       int lot, timeout = 0, dres;
-       char *dialtech, *tmp[100], buf[13];
-       int looptemp, i;
-       char *s;
-       struct ast_party_id caller_id;
-
-       struct ast_channel *dchan;
-       struct outgoing_helper oh = { 0, };
-       int outstate;
-       struct ast_format tmpfmt;
-       struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
-
-       AST_DECLARE_APP_ARGS(args,
-               AST_APP_ARG(template);
-               AST_APP_ARG(timeout);
-               AST_APP_ARG(dial);
-               AST_APP_ARG(return_context);
-       );
-       if (ast_strlen_zero(data)) {
-               ast_log(LOG_WARNING, "ParkAndAnnounce requires arguments: (announce_template,timeout,dial,[return_context])\n");
-               res = -1;
-               goto parkcleanup;
-       }
-       if (!cap_slin) {
-               res = -1;
-               goto parkcleanup;
-       }
-       ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
-
-       s = ast_strdupa(data);
-       AST_STANDARD_APP_ARGS(args, s);
-
-       if (args.timeout)
-               timeout = atoi(args.timeout) * 1000;
-
-       if (ast_strlen_zero(args.dial)) {
-               ast_log(LOG_WARNING, "PARK: A dial resource must be specified i.e: Console/dsp or DAHDI/g1/5551212\n");
-               res = -1;
-               goto parkcleanup;
-       }
-
-       dialtech = strsep(&args.dial, "/");
-       ast_verb(3, "Dial Tech,String: (%s,%s)\n", dialtech, args.dial);
-
-       if (!ast_strlen_zero(args.return_context)) {
-               ast_clear_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
-               ast_parseable_goto(chan, args.return_context);
-       } else {
-               ast_channel_priority_set(chan, ast_channel_priority(chan) + 1);
-       }
-
-       ast_verb(3, "Return Context: (%s,%s,%d) ID: %s\n", ast_channel_context(chan), ast_channel_exten(chan),
-               ast_channel_priority(chan),
-               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""));
-       if (!ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan),
-               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
-               ast_verb(3, "Warning: Return Context Invalid, call will return to default|s\n");
-       }
-
-       /* Save the CallerID because the masquerade turns chan into a ZOMBIE. */
-       ast_party_id_init(&caller_id);
-       ast_channel_lock(chan);
-       ast_party_id_copy(&caller_id, &ast_channel_caller(chan)->id);
-       ast_channel_unlock(chan);
-
-       /* we are using masq_park here to protect * from touching the channel once we park it.  If the channel comes out of timeout
-       before we are done announcing and the channel is messed with, Kablooeee.  So we use Masq to prevent this.  */
-
-       res = ast_masq_park_call(chan, NULL, timeout, &lot);
-       if (res) {
-               /* Parking failed. */
-               ast_party_id_free(&caller_id);
-               res = -1;
-               goto parkcleanup;
-       }
-
-       ast_verb(3, "Call parked in space: %d, timeout: %d, return-context: %s\n",
-               lot, timeout, args.return_context ? args.return_context : "");
-
-       /* Now place the call to the extension */
-
-       snprintf(buf, sizeof(buf), "%d", lot);
-       oh.parent_channel = chan;
-       oh.vars = ast_variable_new("_PARKEDAT", buf, "");
-       dchan = __ast_request_and_dial(dialtech, cap_slin, chan, args.dial, 30000,
-               &outstate,
-               S_COR(caller_id.number.valid, caller_id.number.str, NULL),
-               S_COR(caller_id.name.valid, caller_id.name.str, NULL),
-               &oh);
-       ast_variables_destroy(oh.vars);
-       ast_party_id_free(&caller_id);
-       if (dchan) {
-               if (ast_channel_state(dchan) == AST_STATE_UP) {
-                       ast_verb(4, "Channel %s was answered.\n", ast_channel_name(dchan));
-               } else {
-                       ast_verb(4, "Channel %s was never answered.\n", ast_channel_name(dchan));
-                       ast_log(LOG_WARNING, "PARK: Channel %s was never answered for the announce.\n", ast_channel_name(dchan));
-                       ast_hangup(dchan);
-                       res = -1;
-                       goto parkcleanup;
-               }
-       } else {
-               ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
-               res = -1;
-               goto parkcleanup;
-       }
-
-       ast_stopstream(dchan);
-
-       /* now we have the call placed and are ready to play stuff to it */
-
-       ast_verb(4, "Announce Template:%s\n", args.template);
-
-       for (looptemp = 0; looptemp < ARRAY_LEN(tmp); looptemp++) {
-               if ((tmp[looptemp] = strsep(&args.template, ":")) != NULL)
-                       continue;
-               else
-                       break;
-       }
-
-       for (i = 0; i < looptemp; i++) {
-               ast_verb(4, "Announce:%s\n", tmp[i]);
-               if (!strcmp(tmp[i], "PARKED")) {
-                       ast_say_digits(dchan, lot, "", ast_channel_language(dchan));
-               } else {
-                       dres = ast_streamfile(dchan, tmp[i], ast_channel_language(dchan));
-                       if (!dres) {
-                               dres = ast_waitstream(dchan, "");
-                       } else {
-                               ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", tmp[i], ast_channel_name(dchan));
-                       }
-               }
-       }
-
-       ast_stopstream(dchan);  
-       ast_hangup(dchan);
-
-parkcleanup:
-       cap_slin = ast_format_cap_destroy(cap_slin);
-
-       return res;
-}
-
-static int unload_module(void)
-{
-       return ast_unregister_application(app);
-}
-
-static int load_module(void)
-{
-       /* return ast_register_application(app, park_exec); */
-       return ast_register_application_xml(app, parkandannounce_exec);
-}
-
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Parking and Announce Application");
index c63cd071ed0191c55ed99d5d95aa19d4766438c9..8a96b64b21b5afe5d900d97ee19c647ec93d958d 100644 (file)
@@ -106,6 +106,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cel.h"
 #include "asterisk/data.h"
 #include "asterisk/term.h"
+#include "asterisk/bridging.h"
 
 /* Define, to debug reference counts on queues, without debugging reference counts on queue members */
 /* #define REF_DEBUG_ONLY_QUEUES */
@@ -4912,6 +4913,7 @@ enum agent_complete_reason {
        TRANSFER
 };
 
+#if 0  // BUGBUG
 /*! \brief Send out AMI message with member call completion status information */
 static void send_agent_complete(const struct queue_ent *qe, const char *queuename,
        const struct ast_channel *peer, const struct member *member, time_t callstart,
@@ -4975,6 +4977,7 @@ static void send_agent_complete(const struct queue_ent *qe, const char *queuenam
                (long)(callstart - qe->start), (long)(time(NULL) - callstart), reason,
                qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, vars_len) : "");
 }
+#endif // BUGBUG
 
 struct queue_transfer_ds {
        struct queue_ent *qe;
@@ -5029,6 +5032,7 @@ static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struc
        }
 }
 
+#if 0  // BUGBUG
 /*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
  *
  * When a caller is atxferred, then the queue_transfer_info datastore
@@ -5041,6 +5045,7 @@ static int attended_transfer_occurred(struct ast_channel *chan)
 {
        return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
 }
+#endif // BUGBUG
 
 /*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log
  */
@@ -5098,6 +5103,35 @@ static void end_bridge_callback(void *data)
        }
 }
 
+/*!
+ * \internal
+ * \brief Setup the after bridge goto location on the peer.
+ * \since 12.0.0
+ *
+ * \param chan Calling channel for bridge.
+ * \param peer Peer channel for bridge.
+ * \param opts Dialing option flags.
+ * \param opt_args Dialing option argument strings.
+ *
+ * \return Nothing
+ */
+static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_channel *peer, struct ast_flags *opts, char *opt_args[])
+{
+       const char *context;
+       const char *extension;
+       int priority;
+
+       if (ast_test_flag(opts, OPT_CALLEE_GO_ON)) {
+               ast_channel_lock(chan);
+               context = ast_strdupa(ast_channel_context(chan));
+               extension = ast_strdupa(ast_channel_exten(chan));
+               priority = ast_channel_priority(chan);
+               ast_channel_unlock(chan);
+               ast_after_bridge_set_go_on(peer, context, extension, priority,
+                       opt_args[OPT_ARG_CALLEE_GO_ON]);
+       }
+}
+
 /*!
  * \internal
  * \brief A large function which calls members, updates statistics, and bridges the caller and a member
@@ -5128,7 +5162,7 @@ static void end_bridge_callback(void *data)
  * \param[in] gosub the gosub passed as the seventh parameter to the Queue() application
  * \param[in] ringing 1 if the 'r' option is set, otherwise 0
  */
-static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing)
+static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing)
 {
        struct member *cur;
        struct callattempt *outgoing = NULL; /* the list of calls we are building */
@@ -5200,9 +5234,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
        if (ast_test_flag(&opts, OPT_CALLER_AUTOMON)) {
                ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON);
        }
-       if (ast_test_flag(&opts, OPT_GO_ON)) {
-               ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_NO_H_EXTEN);
-       }
        if (ast_test_flag(&opts, OPT_DATA_QUALITY)) {
                nondataquality = 0;
        }
@@ -5244,7 +5275,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
        }
 
        /* if the calling channel has AST_CAUSE_ANSWERED_ELSEWHERE set, make sure this is inherited.
-               (this is mainly to support chan_local)
+               (this is mainly to support unreal/local channels)
        */
        if (ast_channel_hangupcause(qe->chan) == AST_CAUSE_ANSWERED_ELSEWHERE) {
                qe->cancel_answered_elsewhere = 1;
@@ -5437,11 +5468,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                        }
                }
        } else { /* peer is valid */
-               /* These variables are used with the F option without arguments (callee jumps to next priority after queue) */
-               char *caller_context;
-               char *caller_extension;
-               int caller_priority;
-
                /* Ah ha!  Someone answered within the desired timeframe.  Of course after this
                   we will always return with -1 so that it is hung up properly after the
                   conversation.  */
@@ -5595,11 +5621,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                set_queue_variables(qe->parent, qe->chan);
                set_queue_variables(qe->parent, peer);
 
+               setup_peer_after_bridge_goto(qe->chan, peer, &opts, opt_args);
                ast_channel_lock(qe->chan);
-               /* Copy next destination data for 'F' option (no args) */
-               caller_context = ast_strdupa(ast_channel_context(qe->chan));
-               caller_extension = ast_strdupa(ast_channel_exten(qe->chan));
-               caller_priority = ast_channel_priority(qe->chan);
                if ((monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"))) {
                                monitorfilename = ast_strdupa(monitorfilename);
                }
@@ -5883,6 +5906,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
                bridge = ast_bridge_call(qe->chan, peer, &bridge_config);
 
+/* BUGBUG need to do this queue logging a different way because we cannot reference peer anymore.  Likely needs to be made a subscriber of stasis transfer events. */
+#if 0  // BUGBUG
                /* If the queue member did an attended transfer, then the TRANSFER already was logged in the queue_log
                 * when the masquerade occurred. These other "ending" queue_log messages are unnecessary, except for
                 * the AgentComplete manager event
@@ -5917,28 +5942,12 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char *
                        /* We already logged the TRANSFER on the queue_log, but we still need to send the AgentComplete event */
                        send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER);
                }
+#endif // BUGBUG
 
                if (transfer_ds) {
                        ast_datastore_free(transfer_ds);
                }
 
-               if (!ast_check_hangup(peer) && ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
-                       int goto_res;
-
-                       if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) {
-                               ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]);
-                               goto_res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]);
-                       } else { /* F() */
-                               goto_res = ast_goto_if_exists(peer, caller_context, caller_extension,
-                                       caller_priority + 1);
-                       }
-                       if (goto_res || ast_pbx_start(peer)) {
-                               ast_autoservice_chan_hangup_peer(qe->chan, peer);
-                       }
-               } else {
-                       ast_autoservice_chan_hangup_peer(qe->chan, peer);
-               }
-
                res = bridge ? bridge : 1;
                ao2_ref(member, -1);
        }
diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c
new file mode 100644 (file)
index 0000000..46e074b
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief ConfBridge announcer channel driver
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+#include "include/confbridge.h"
+
+/* ------------------------------------------------------------------- */
+
+/*! ConfBridge announcer channel private. */
+struct announce_pvt {
+       /*! Unreal channel driver base class values. */
+       struct ast_unreal_pvt base;
+       /*! Conference bridge associated with this announcer. */
+       struct ast_bridge *bridge;
+};
+
+static int announce_call(struct ast_channel *chan, const char *addr, int timeout)
+{
+       /* Make sure anyone calling ast_call() for this channel driver is going to fail. */
+       return -1;
+}
+
+static int announce_hangup(struct ast_channel *ast)
+{
+       struct announce_pvt *p = ast_channel_tech_pvt(ast);
+       int res;
+
+       if (!p) {
+               return -1;
+       }
+
+       /* give the pvt a ref to fulfill calling requirements. */
+       ao2_ref(p, +1);
+       res = ast_unreal_hangup(&p->base, ast);
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+static void announce_pvt_destructor(void *vdoomed)
+{
+       struct announce_pvt *doomed = vdoomed;
+
+       ao2_cleanup(doomed->bridge);
+       doomed->bridge = NULL;
+}
+
+static struct ast_channel *announce_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
+{
+       struct ast_channel *chan;
+       const char *conf_name = data;
+       RAII_VAR(struct confbridge_conference *, conference, NULL, ao2_cleanup);
+       RAII_VAR(struct announce_pvt *, pvt, NULL, ao2_cleanup);
+
+       conference = ao2_find(conference_bridges, conf_name, OBJ_KEY);
+       if (!conference) {
+               return NULL;
+       }
+       ast_assert(conference->bridge != NULL);
+
+       /* Allocate a new private structure and then Asterisk channels */
+       pvt = (struct announce_pvt *) ast_unreal_alloc(sizeof(*pvt), announce_pvt_destructor,
+               cap);
+       if (!pvt) {
+               return NULL;
+       }
+       ast_set_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION);
+       ast_copy_string(pvt->base.name, conf_name, sizeof(pvt->base.name));
+       pvt->bridge = conference->bridge;
+       ao2_ref(pvt->bridge, +1);
+
+       chan = ast_unreal_new_channels(&pvt->base, conf_announce_get_tech(),
+               AST_STATE_UP, AST_STATE_UP, NULL, NULL, requestor, NULL);
+       if (chan) {
+               ast_answer(pvt->base.owner);
+               ast_answer(pvt->base.chan);
+               if (ast_channel_add_bridge_role(pvt->base.chan, "announcer")) {
+                       ast_hangup(chan);
+                       chan = NULL;
+               }
+       }
+
+       return chan;
+}
+
+static struct ast_channel_tech announce_tech = {
+       .type = "CBAnn",
+       .description = "Conference Bridge Announcing Channel",
+       .requester = announce_request,
+       .call = announce_call,
+       .hangup = announce_hangup,
+
+       .send_digit_begin = ast_unreal_digit_begin,
+       .send_digit_end = ast_unreal_digit_end,
+       .read = ast_unreal_read,
+       .write = ast_unreal_write,
+       .write_video = ast_unreal_write,
+       .exception = ast_unreal_read,
+       .indicate = ast_unreal_indicate,
+       .fixup = ast_unreal_fixup,
+       .send_html = ast_unreal_sendhtml,
+       .send_text = ast_unreal_sendtext,
+       .queryoption = ast_unreal_queryoption,
+       .setoption = ast_unreal_setoption,
+};
+
+struct ast_channel_tech *conf_announce_get_tech(void)
+{
+       return &announce_tech;
+}
+
+void conf_announce_channel_depart(struct ast_channel *chan)
+{
+       struct announce_pvt *p = ast_channel_tech_pvt(chan);
+
+       if (!p) {
+               return;
+       }
+
+       ao2_ref(p, +1);
+       ao2_lock(p);
+       if (!ast_test_flag(&p->base, AST_UNREAL_CARETAKER_THREAD)) {
+               ao2_unlock(p);
+               ao2_ref(p, -1);
+               return;
+       }
+       ast_clear_flag(&p->base, AST_UNREAL_CARETAKER_THREAD);
+       chan = p->base.chan;
+       if (chan) {
+               ast_channel_ref(chan);
+       }
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+       if (chan) {
+               ast_bridge_depart(chan);
+               ast_channel_unref(chan);
+       }
+}
+
+int conf_announce_channel_push(struct ast_channel *ast)
+{
+       struct ast_bridge_features *features;
+       RAII_VAR(struct announce_pvt *, p, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel *, chan, NULL, ast_channel_unref);
+
+       {
+               SCOPED_CHANNELLOCK(lock, ast);
+
+               p = ast_channel_tech_pvt(ast);
+               if (!p) {
+                       return -1;
+               }
+               ao2_ref(p, +1);
+               chan = p->base.chan;
+               if (!chan) {
+                       return -1;
+               }
+               ast_channel_ref(chan);
+       }
+
+       features = ast_bridge_features_new();
+       if (!features) {
+               return -1;
+       }
+       ast_set_flag(&features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE);
+
+       /* Impart the output channel into the bridge */
+       if (ast_bridge_impart(p->bridge, chan, NULL, features, 0)) {
+               return -1;
+       }
+       ao2_lock(p);
+       ast_set_flag(&p->base, AST_UNREAL_CARETAKER_THREAD);
+       ao2_unlock(p);
+       return 0;
+}
diff --git a/apps/confbridge/conf_chan_record.c b/apps/confbridge/conf_chan_record.c
new file mode 100644 (file)
index 0000000..18f971f
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief ConfBridge recorder channel driver
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "include/confbridge.h"
+
+/* ------------------------------------------------------------------- */
+
+static int rec_call(struct ast_channel *chan, const char *addr, int timeout)
+{
+       /* Make sure anyone calling ast_call() for this channel driver is going to fail. */
+       return -1;
+}
+
+static struct ast_frame *rec_read(struct ast_channel *ast)
+{
+       return &ast_null_frame;
+}
+
+static int rec_write(struct ast_channel *ast, struct ast_frame *f)
+{
+       return 0;
+}
+
+static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
+{
+       struct ast_channel *chan;
+       struct ast_format format;
+       const char *conf_name = data;
+
+       chan = ast_channel_alloc(1, AST_STATE_UP, NULL, NULL, NULL, NULL, NULL, NULL, 0,
+               "CBRec/conf-%s-uid-%d",
+               conf_name, (int) ast_random());
+       if (!chan) {
+               return NULL;
+       }
+       if (ast_channel_add_bridge_role(chan, "recorder")) {
+               ast_channel_release(chan);
+               return NULL;
+       }
+       ast_format_set(&format, AST_FORMAT_SLINEAR, 0);
+       ast_channel_tech_set(chan, conf_record_get_tech());
+       ast_format_cap_add_all(ast_channel_nativeformats(chan));
+       ast_format_copy(ast_channel_writeformat(chan), &format);
+       ast_format_copy(ast_channel_rawwriteformat(chan), &format);
+       ast_format_copy(ast_channel_readformat(chan), &format);
+       ast_format_copy(ast_channel_rawreadformat(chan), &format);
+       return chan;
+}
+
+static struct ast_channel_tech record_tech = {
+       .type = "CBRec",
+       .description = "Conference Bridge Recording Channel",
+       .requester = rec_request,
+       .call = rec_call,
+       .read = rec_read,
+       .write = rec_write,
+};
+
+struct ast_channel_tech *conf_record_get_tech(void)
+{
+       return &record_tech;
+}
index 1bca2d5c2f3c4fab4b3e56ce06c15eb80596470c..6cec255221be14ad64162747f6c15dbdfbe2b9fb 100644 (file)
@@ -1924,6 +1924,7 @@ int conf_load_config(int reload)
        /* This option should only be used with the CONFBRIDGE dialplan function */
        aco_option_register_custom(&cfg_info, "template", ACO_EXACT, user_types, NULL, user_template_handler, 0);
 
+/* BUGBUG need a user supplied bridge merge_priority to merge ConfBridges (default = 1, range 1-INT_MAX) */
        /* Bridge options */
        aco_option_register(&cfg_info, "type", ACO_EXACT, bridge_types, NULL, OPT_NOOP_T, 0, 0);
        aco_option_register(&cfg_info, "jitterbuffer", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), USER_OPT_JITTERBUFFER);
@@ -2156,7 +2157,8 @@ int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user)
                ao2_ref(menu, +1);
                pvt->menu = menu;
 
-               ast_bridge_features_hook(&user->features, pvt->menu_entry.dtmf, menu_hook_callback, pvt, menu_hook_destroy);
+               ast_bridge_dtmf_hook(&user->features, pvt->menu_entry.dtmf, menu_hook_callback,
+                       pvt, menu_hook_destroy, 0);
        }
 
        ao2_unlock(menu);
diff --git a/apps/confbridge/confbridge_manager.c b/apps/confbridge/confbridge_manager.c
new file mode 100644 (file)
index 0000000..56fedb9
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Confbridge manager events for stasis messages
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/manager.h"
+#include "asterisk/stasis_message_router.h"
+#include "include/confbridge.h"
+
+/*** DOCUMENTATION
+       <managerEvent language="en_US" name="ConfbridgeStart">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a conference starts.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeEnd</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeEnd">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a conference ends.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeStart</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeJoin">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel joins a Confbridge conference.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeLeave</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeLeave">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel leaves a Confbridge conference.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeJoin</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeRecord">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a conference starts recording.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeStopRecord</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeStopRecord">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a conference that was recording stops recording.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeRecord</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeMute">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a Confbridge participant mutes.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeUnmute</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ConfbridgeUnmute">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a confbridge participant unmutes.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+                       <see-also>
+                               <ref type="managerEvent">ConfbridgeMute</ref>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+
+       <managerEvent language="en_US" name="ConfbridgeTalking">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a confbridge participant unmutes.</synopsis>
+                       <syntax>
+                               <parameter name="Conference">
+                                       <para>The name of the Confbridge conference.</para>
+                               </parameter>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="TalkingStatus">
+                                       <enumlist>
+                                               <enum name="on"/>
+                                               <enum name="off"/>
+                                       </enumlist>
+                               </parameter>
+                       </syntax>
+                       <see-also>
+                               <ref type="application">ConfBridge</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+***/
+
+static struct stasis_message_router *bridge_state_router;
+static struct stasis_message_router *channel_state_router;
+
+static void append_event_header(struct ast_str **fields_string,
+                                       const char *header, const char *value)
+{
+       struct ast_str *working_str = *fields_string;
+
+       if (!working_str) {
+               working_str = ast_str_create(128);
+               if (!working_str) {
+                       return;
+               }
+               *fields_string = working_str;
+       }
+
+       ast_str_append(&working_str, 0,
+               "%s: %s\r\n",
+               header, value);
+}
+
+static void stasis_confbridge_cb(void *data, struct stasis_subscription *sub,
+                                       struct stasis_topic *topic,
+                                       struct stasis_message *message)
+{
+       struct ast_bridge_blob *blob = stasis_message_data(message);
+       const char *type = ast_bridge_blob_json_type(blob);
+       const char *conference_name;
+       RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+       RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+       RAII_VAR(struct ast_str *, extra_text, NULL, ast_free);
+       char *event;
+
+       if (!blob || !type) {
+               ast_assert(0);
+               return;
+       }
+
+       if (!strcmp("confbridge_start", type)) {
+               event = "ConfbridgeStart";
+       } else if (!strcmp("confbridge_end", type)) {
+               event = "ConfbridgeEnd";
+       } else if (!strcmp("confbridge_leave", type)) {
+               event = "ConfbridgeLeave";
+       } else if (!strcmp("confbridge_join", type)) {
+               event = "ConfbridgeJoin";
+       } else if (!strcmp("confbridge_record", type)) {
+               event = "ConfbridgeRecord";
+       } else if (!strcmp("confbridge_stop_record", type)) {
+               event = "ConfbridgeStopRecord";
+       } else if (!strcmp("confbridge_mute", type)) {
+               event = "ConfbridgeMute";
+       } else if (!strcmp("confbridge_unmute", type)) {
+               event = "ConfbridgeUnmute";
+       } else if (!strcmp("confbridge_talking", type)) {
+               const char *talking_status = ast_json_string_get(ast_json_object_get(blob->blob, "talking_status"));
+               event = "ConfbridgeTalking";
+
+               if (!talking_status) {
+                       return;
+               }
+
+               append_event_header(&extra_text, "TalkingStatus", talking_status);
+
+       } else {
+               return;
+       }
+
+       conference_name = ast_json_string_get(ast_json_object_get(blob->blob, "conference"));
+
+       if (!conference_name) {
+               ast_assert(0);
+               return;
+       }
+
+       bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+       if (blob->channel) {
+               channel_text = ast_manager_build_channel_state_string(blob->channel);
+       }
+
+       manager_event(EVENT_FLAG_CALL, event,
+               "Conference: %s\r\n"
+               "%s"
+               "%s"
+               "%s",
+               conference_name,
+               ast_str_buffer(bridge_text),
+               channel_text ? ast_str_buffer(channel_text) : "",
+               extra_text ? ast_str_buffer(extra_text) : "");
+}
+
+static struct stasis_message_type *confbridge_msg_type;
+
+struct stasis_message_type *confbridge_message_type(void)
+{
+       return confbridge_msg_type;
+}
+
+void manager_confbridge_shutdown(void) {
+       ao2_cleanup(confbridge_msg_type);
+       confbridge_msg_type = NULL;
+
+       if (bridge_state_router) {
+               stasis_message_router_unsubscribe(bridge_state_router);
+               bridge_state_router = NULL;
+       }
+
+       if (channel_state_router) {
+               stasis_message_router_unsubscribe(channel_state_router);
+               channel_state_router = NULL;
+       }
+}
+
+int manager_confbridge_init(void)
+{
+       if (!(confbridge_msg_type = stasis_message_type_create("confbridge"))) {
+               return -1;
+       }
+
+       bridge_state_router = stasis_message_router_create(
+               stasis_caching_get_topic(ast_bridge_topic_all_cached()));
+
+       if (!bridge_state_router) {
+               return -1;
+       }
+
+       if (stasis_message_router_add(bridge_state_router,
+                                        confbridge_message_type(),
+                                        stasis_confbridge_cb,
+                                        NULL)) {
+               manager_confbridge_shutdown();
+               return -1;
+       }
+
+       channel_state_router = stasis_message_router_create(
+               stasis_caching_get_topic(ast_channel_topic_all_cached()));
+
+       if (!channel_state_router) {
+               manager_confbridge_shutdown();
+               return -1;
+       }
+
+       if (stasis_message_router_add(channel_state_router,
+                                        confbridge_message_type(),
+                                        stasis_confbridge_cb,
+                                        NULL)) {
+               manager_confbridge_shutdown();
+               return -1;
+       }
+
+       return 0;
+}
index 2455613769f34077175c7c97f33c9f0feec4e6fb..f0620149a0c7a9be28f78c8cf7c435ecb4f0c92d 100644 (file)
@@ -222,6 +222,8 @@ struct confbridge_conference {
        AST_LIST_HEAD_NOLOCK(, confbridge_user) waiting_list;             /*!< List of users waiting to join the conference bridge */
 };
 
+extern struct ao2_container *conference_bridges;
+
 struct post_join_action {
        int (*func)(struct confbridge_user *user);
        AST_LIST_ENTRY(post_join_action) list;
@@ -460,4 +462,65 @@ void conf_remove_user_waiting(struct confbridge_conference *conference, struct c
  * \retval non-zero failure
  */
 int conf_add_post_join_action(struct confbridge_user *user, int (*func)(struct confbridge_user *user));
+
+/*!
+ * \since 12.0
+ * \brief get the confbridge stasis message type
+ *
+ * \retval stasis message type for confbridge messages if it's available
+ * \retval NULL if it isn't
+ */
+struct stasis_message_type *confbridge_message_type(void);
+
+/*!
+ * \since 12.0
+ * \brief register stasis message routers to handle manager events for confbridge messages
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int manager_confbridge_init(void);
+
+/*!
+ * \since 12.0
+ * \brief unregister stasis message routers to handle manager events for confbridge messages
+ */
+void manager_confbridge_shutdown(void);
+
+/*!
+ * \brief Get ConfBridge record channel technology struct.
+ * \since 12.0.0
+ *
+ * \return ConfBridge record channel technology.
+ */
+struct ast_channel_tech *conf_record_get_tech(void);
+
+/*!
+ * \brief Get ConfBridge announce channel technology struct.
+ * \since 12.0.0
+ *
+ * \return ConfBridge announce channel technology.
+ */
+struct ast_channel_tech *conf_announce_get_tech(void);
+
+/*!
+ * \brief Remove the announcer channel from the conference.
+ * \since 12.0.0
+ *
+ * \param chan Either channel in the announcer channel pair.
+ *
+ * \return Nothing
+ */
+void conf_announce_channel_depart(struct ast_channel *chan);
+
+/*!
+ * \brief Push the announcer channel into the conference.
+ * \since 12.0.0
+ *
+ * \param ast Either channel in the announcer channel pair.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int conf_announce_channel_push(struct ast_channel *ast);
 #endif
index 428d6deda24a48087f30b8bf610ede50d6cba2fc..493b5c89136d34d3697f062b3168b8057609ae46 100644 (file)
@@ -47,8 +47,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/file.h"
 #include "asterisk/app.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
+#include "asterisk/parking.h"
 
-/*! \brief Helper function that presents dialtone and grabs extension */
+/*!
+ * \brief Helper function that presents dialtone and grabs extension
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
 static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len, const char *context)
 {
        int res;
@@ -56,15 +63,35 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
        /* Play the simple "transfer" prompt out and wait */
        res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
        ast_stopstream(chan);
-
-       /* If the person hit a DTMF digit while the above played back stick it into the buffer */
+       if (res < 0) {
+               /* Hangup or error */
+               return -1;
+       }
        if (res) {
-               exten[0] = (char)res;
+               /* Store the DTMF digit that interrupted playback of the file. */
+               exten[0] = res;
        }
 
        /* Drop to dialtone so they can enter the extension they want to transfer to */
-       res = ast_app_dtget(chan, context, exten, exten_len, 100, 1000);
-
+/* BUGBUG the timeout needs to be configurable from features.conf. */
+       res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, 3000);
+       if (res < 0) {
+               /* Hangup or error */
+               res = -1;
+       } else if (!res) {
+               /* 0 for invalid extension dialed. */
+               if (ast_strlen_zero(exten)) {
+                       ast_debug(1, "%s dialed no digits.\n", ast_channel_name(chan));
+               } else {
+                       ast_debug(1, "%s dialed '%s@%s' does not exist.\n",
+                               ast_channel_name(chan), exten, context);
+               }
+               ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE);
+               res = -1;
+       } else {
+               /* Dialed extension is valid. */
+               res = 0;
+       }
        return res;
 }
 
@@ -78,8 +105,10 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
        /* Fill the variable with the extension and context we want to call */
        snprintf(destination, sizeof(destination), "%s@%s", exten, context);
 
-       /* Now we request that chan_local prepare to call the destination */
-       if (!(chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination, &cause))) {
+       /* Now we request a local channel to prepare to call the destination */
+       chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination,
+               &cause);
+       if (!chan) {
                return NULL;
        }
 
@@ -100,67 +129,124 @@ static struct ast_channel *dial_transfer(struct ast_channel *caller, const char
        return chan;
 }
 
+/*!
+ * \internal
+ * \brief Determine the transfer context to use.
+ * \since 12.0.0
+ *
+ * \param transferer Channel initiating the transfer.
+ * \param context User supplied context if available.  May be NULL.
+ *
+ * \return The context to use for the transfer.
+ */
+static const char *get_transfer_context(struct ast_channel *transferer, const char *context)
+{
+       if (!ast_strlen_zero(context)) {
+               return context;
+       }
+       context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
+       if (!ast_strlen_zero(context)) {
+               return context;
+       }
+       context = ast_channel_macrocontext(transferer);
+       if (!ast_strlen_zero(context)) {
+               return context;
+       }
+       context = ast_channel_context(transferer);
+       if (!ast_strlen_zero(context)) {
+               return context;
+       }
+       return "default";
+}
+
 /*! \brief Internal built in feature for blind transfers */
 static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
        char exten[AST_MAX_EXTENSION] = "";
        struct ast_channel *chan = NULL;
        struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
-       const char *context = (blind_transfer && !ast_strlen_zero(blind_transfer->context) ? blind_transfer->context : ast_channel_context(bridge_channel->chan));
+       const char *context;
+       struct ast_exten *park_exten;
+
+/* BUGBUG the peer needs to be put on hold for the transfer. */
+       ast_channel_lock(bridge_channel->chan);
+       context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+               blind_transfer ? blind_transfer->context : NULL));
+       ast_channel_unlock(bridge_channel->chan);
 
        /* Grab the extension to transfer to */
-       if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
-               ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
+       if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+               return 0;
+       }
+
+       /* Parking blind transfer override - phase this out for something more general purpose in the future. */
+       park_exten = ast_get_parking_exten(exten, bridge_channel->chan, context);
+       if (park_exten) {
+               /* We are transfering the transferee to a parking lot. */
+               if (ast_park_blind_xfer(bridge, bridge_channel, park_exten)) {
+                       ast_log(LOG_ERROR, "%s attempted to transfer to park application and failed.\n", ast_channel_name(bridge_channel->chan));
+               };
                return 0;
        }
 
+/* BUGBUG just need to ast_async_goto the peer so this bridge will go away and not accumulate local channels and bridges if the destination is to an application. */
+/* ast_async_goto actually is a blind transfer. */
+/* BUGBUG Use the bridge count to determine if can do DTMF transfer features.  If count is not 2 then don't allow it. */
+
        /* Get a channel that is the destination we wish to call */
-       if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
-               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+       chan = dial_transfer(bridge_channel->chan, exten, context);
+       if (!chan) {
                return 0;
        }
 
-       /* This is sort of the fun part. We impart the above channel onto the bridge, and have it take our place. */
-       ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
+       /* Impart the new channel onto the bridge, and have it take our place. */
+       if (ast_bridge_impart(bridge_channel->bridge, chan, bridge_channel->chan, NULL, 1)) {
+               ast_hangup(chan);
+               return 0;
+       }
 
        return 0;
 }
 
-/*! \brief Attended transfer feature to turn it into a threeway call */
-static int attended_threeway_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+/*! Attended transfer code */
+enum atxfer_code {
+       /*! Party C hungup or other reason to abandon the transfer. */
+       ATXFER_INCOMPLETE,
+       /*! Transfer party C to party A. */
+       ATXFER_COMPLETE,
+       /*! Turn the transfer into a threeway call. */
+       ATXFER_THREEWAY,
+       /*! Hangup party C and return party B to the bridge. */
+       ATXFER_ABORT,
+};
+
+/*! \brief Attended transfer feature to complete transfer */
+static int attended_transfer_complete(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
-       /*
-        * This is sort of abusing the depart state but in this instance
-        * it is only going to be handled by feature_attended_transfer()
-        * so it is okay.
-        */
-       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
+       enum atxfer_code *transfer_code = hook_pvt;
+
+       *transfer_code = ATXFER_COMPLETE;
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
        return 0;
 }
 
-/*! \brief Attended transfer abort feature */
-static int attended_abort_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+/*! \brief Attended transfer feature to turn it into a threeway call */
+static int attended_transfer_threeway(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
-       struct ast_bridge_channel *called_bridge_channel = NULL;
-
-       /* It is possible (albeit unlikely) that the bridge channels list may change, so we have to ensure we do all of our magic while locked */
-       ao2_lock(bridge);
+       enum atxfer_code *transfer_code = hook_pvt;
 
-       if (AST_LIST_FIRST(&bridge->channels) != bridge_channel) {
-               called_bridge_channel = AST_LIST_FIRST(&bridge->channels);
-       } else {
-               called_bridge_channel = AST_LIST_LAST(&bridge->channels);
-       }
-
-       /* Now we basically eject the other channel from the bridge. This will cause their thread to hang them up, and our own code to consider the transfer failed. */
-       if (called_bridge_channel) {
-               ast_bridge_change_state(called_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-       }
-
-       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+       *transfer_code = ATXFER_THREEWAY;
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+       return 0;
+}
 
-       ao2_unlock(bridge);
+/*! \brief Attended transfer feature to abort transfer */
+static int attended_transfer_abort(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       enum atxfer_code *transfer_code = hook_pvt;
 
+       *transfer_code = ATXFER_ABORT;
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
        return 0;
 }
 
@@ -168,71 +254,159 @@ static int attended_abort_transfer(struct ast_bridge *bridge, struct ast_bridge_
 static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
        char exten[AST_MAX_EXTENSION] = "";
-       struct ast_channel *chan = NULL;
-       struct ast_bridge *attended_bridge = NULL;
-       struct ast_bridge_features caller_features, called_features;
-       enum ast_bridge_channel_state attended_bridge_result;
+       struct ast_channel *peer;
+       struct ast_bridge *attended_bridge;
+       struct ast_bridge_features caller_features;
+       int xfer_failed;
        struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt;
-       const char *context = (attended_transfer && !ast_strlen_zero(attended_transfer->context) ? attended_transfer->context : ast_channel_context(bridge_channel->chan));
+       const char *context;
+       enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
+
+       bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
+
+/* BUGBUG the peer needs to be put on hold for the transfer. */
+       ast_channel_lock(bridge_channel->chan);
+       context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+               attended_transfer ? attended_transfer->context : NULL));
+       ast_channel_unlock(bridge_channel->chan);
 
        /* Grab the extension to transfer to */
-       if (!grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
-               ast_stream_and_wait(bridge_channel->chan, "pbx-invalid", AST_DIGIT_ANY);
+       if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+               ast_bridge_merge_inhibit(bridge, -1);
+               ao2_ref(bridge, -1);
                return 0;
        }
 
        /* Get a channel that is the destination we wish to call */
-       if (!(chan = dial_transfer(bridge_channel->chan, exten, context))) {
-               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+       peer = dial_transfer(bridge_channel->chan, exten, context);
+       if (!peer) {
+               ast_bridge_merge_inhibit(bridge, -1);
+               ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
                return 0;
        }
 
-       /* Create a bridge to use to talk to the person we are calling */
-       if (!(attended_bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, 0))) {
-               ast_hangup(chan);
-               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
+/* BUGBUG bridging API features does not support features.conf featuremap */
+/* BUGBUG bridging API features does not support the features.conf atxfer bounce between C & B channels */
+       /* Setup a DTMF menu to control the transfer. */
+       if (ast_bridge_features_init(&caller_features)
+               || ast_bridge_hangup_hook(&caller_features,
+                       attended_transfer_complete, &transfer_code, NULL, 0)
+               || ast_bridge_dtmf_hook(&caller_features,
+                       attended_transfer && !ast_strlen_zero(attended_transfer->abort)
+                               ? attended_transfer->abort : "*1",
+                       attended_transfer_abort, &transfer_code, NULL, 0)
+               || ast_bridge_dtmf_hook(&caller_features,
+                       attended_transfer && !ast_strlen_zero(attended_transfer->complete)
+                               ? attended_transfer->complete : "*2",
+                       attended_transfer_complete, &transfer_code, NULL, 0)
+               || ast_bridge_dtmf_hook(&caller_features,
+                       attended_transfer && !ast_strlen_zero(attended_transfer->threeway)
+                               ? attended_transfer->threeway : "*3",
+                       attended_transfer_threeway, &transfer_code, NULL, 0)) {
+               ast_bridge_features_cleanup(&caller_features);
+               ast_hangup(peer);
+               ast_bridge_merge_inhibit(bridge, -1);
+               ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
                return 0;
        }
 
-       /* Setup our called features structure so that if they hang up we immediately get thrown out of the bridge */
-       ast_bridge_features_init(&called_features);
-       ast_bridge_features_set_flag(&called_features, AST_BRIDGE_FLAG_DISSOLVE);
+       /* Create a bridge to use to talk to the person we are calling */
+       attended_bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX,
+               AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
+       if (!attended_bridge) {
+               ast_bridge_features_cleanup(&caller_features);
+               ast_hangup(peer);
+               ast_bridge_merge_inhibit(bridge, -1);
+               ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+               return 0;
+       }
+       ast_bridge_merge_inhibit(attended_bridge, +1);
 
        /* This is how this is going down, we are imparting the channel we called above into this bridge first */
-       ast_bridge_impart(attended_bridge, chan, NULL, &called_features, 1);
+/* BUGBUG we should impart the peer as an independent and move it to the original bridge. */
+       if (ast_bridge_impart(attended_bridge, peer, NULL, NULL, 0)) {
+               ast_bridge_destroy(attended_bridge);
+               ast_bridge_features_cleanup(&caller_features);
+               ast_hangup(peer);
+               ast_bridge_merge_inhibit(bridge, -1);
+               ao2_ref(bridge, -1);
+/* BUGBUG beeperr needs to be configurable from features.conf */
+               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
+               return 0;
+       }
 
-       /* Before we join setup a features structure with the hangup option, just in case they want to use DTMF */
-       ast_bridge_features_init(&caller_features);
-       ast_bridge_features_enable(&caller_features, AST_BRIDGE_BUILTIN_HANGUP,
-                                  (attended_transfer && !ast_strlen_zero(attended_transfer->complete) ? attended_transfer->complete : "*1"), NULL);
-       ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->threeway) ? attended_transfer->threeway : "*2"),
-                                attended_threeway_transfer, NULL, NULL);
-       ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->abort) ? attended_transfer->abort : "*3"),
-                                attended_abort_transfer, NULL, NULL);
+       /*
+        * For the caller we want to join the bridge in a blocking
+        * fashion so we don't spin around in this function doing
+        * nothing while waiting.
+        */
+       ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL, 0);
 
-       /* But for the caller we want to join the bridge in a blocking fashion so we don't spin around in this function doing nothing while waiting */
-       attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL);
+/*
+ * BUGBUG there is a small window where the channel does not point to the bridge_channel.
+ *
+ * This window is expected to go away when atxfer is redesigned
+ * to fully support existing functionality.  There will be one
+ * and only one ast_bridge_channel structure per channel.
+ */
+       /* Point the channel back to the original bridge and bridge_channel. */
+       ast_bridge_channel_lock(bridge_channel);
+       ast_channel_lock(bridge_channel->chan);
+       ast_channel_internal_bridge_channel_set(bridge_channel->chan, bridge_channel);
+       ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
+       ast_channel_unlock(bridge_channel->chan);
+       ast_bridge_channel_unlock(bridge_channel);
+
+       /* Wait for peer thread to exit bridge and die. */
+       if (!ast_autoservice_start(bridge_channel->chan)) {
+               ast_bridge_depart(peer);
+               ast_autoservice_stop(bridge_channel->chan);
+       } else {
+               ast_bridge_depart(peer);
+       }
 
-       /* Since the above returned the caller features structure is of no more use */
+       /* Now that all channels are out of it we can destroy the bridge and the feature structures */
+       ast_bridge_destroy(attended_bridge);
        ast_bridge_features_cleanup(&caller_features);
 
-       /* Drop the channel we are transferring to out of the above bridge since it has ended */
-       if ((attended_bridge_result != AST_BRIDGE_CHANNEL_STATE_HANGUP) && !ast_bridge_depart(attended_bridge, chan)) {
-               /* If the user wants to turn this into a threeway transfer then do so, otherwise they take our place */
-               if (attended_bridge_result == AST_BRIDGE_CHANNEL_STATE_DEPART) {
-                       /* We want to impart them upon the bridge and just have us return to it as normal */
-                       ast_bridge_impart(bridge, chan, NULL, NULL, 1);
-               } else {
-                       ast_bridge_impart(bridge, chan, bridge_channel->chan, NULL, 1);
+       xfer_failed = -1;
+       switch (transfer_code) {
+       case ATXFER_INCOMPLETE:
+               /* Peer hungup */
+               break;
+       case ATXFER_COMPLETE:
+               /* The peer takes our place in the bridge. */
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+               xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, bridge_channel->chan, NULL, 1);
+               break;
+       case ATXFER_THREEWAY:
+               /*
+                * Transferer wants to convert to a threeway call.
+                *
+                * Just impart the peer onto the bridge and have us return to it
+                * as normal.
+                */
+               xfer_failed = ast_bridge_impart(bridge_channel->bridge, peer, NULL, NULL, 1);
+               break;
+       case ATXFER_ABORT:
+               /* Transferer decided not to transfer the call after all. */
+               break;
+       }
+       ast_bridge_merge_inhibit(bridge, -1);
+       ao2_ref(bridge, -1);
+       if (xfer_failed) {
+               ast_hangup(peer);
+               if (!ast_check_hangup_locked(bridge_channel->chan)) {
+                       ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_NONE);
                }
-       } else {
-               ast_stream_and_wait(bridge_channel->chan, "beeperr", AST_DIGIT_ANY);
        }
 
-       /* Now that all channels are out of it we can destroy the bridge and the called features structure */
-       ast_bridge_features_cleanup(&called_features);
-       ast_bridge_destroy(attended_bridge);
-
        return 0;
 }
 
diff --git a/bridges/bridge_builtin_interval_features.c b/bridges/bridge_builtin_interval_features.c
new file mode 100644 (file)
index 0000000..a0e767e
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Built in bridging interval features
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$REVISION: 381278 $")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/file.h"
+#include "asterisk/app.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/test.h"
+
+#include "asterisk/say.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+
+static int bridge_features_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct ast_bridge_features_limits *limits = hook_pvt;
+
+       if (!ast_strlen_zero(limits->duration_sound)) {
+               ast_stream_and_wait(bridge_channel->chan, limits->duration_sound, AST_DIGIT_NONE);
+       }
+
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+
+       ast_test_suite_event_notify("BRIDGE_TIMELIMIT", "Channel1: %s", ast_channel_name(bridge_channel->chan));
+       return -1;
+}
+
+static void limits_interval_playback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_bridge_features_limits *limits, const char *file)
+{
+       if (!strcasecmp(file, "timeleft")) {
+               unsigned int remaining = ast_tvdiff_ms(limits->quitting_time, ast_tvnow()) / 1000;
+               unsigned int min;
+               unsigned int sec;
+
+               if (remaining <= 0) {
+                       return;
+               }
+
+               if ((remaining / 60) > 1) {
+                       min = remaining / 60;
+                       sec = remaining % 60;
+               } else {
+                       min = 0;
+                       sec = remaining;
+               }
+
+               ast_stream_and_wait(bridge_channel->chan, "vm-youhave", AST_DIGIT_NONE);
+               if (min) {
+                       ast_say_number(bridge_channel->chan, min, AST_DIGIT_NONE,
+                               ast_channel_language(bridge_channel->chan), NULL);
+                       ast_stream_and_wait(bridge_channel->chan, "queue-minutes", AST_DIGIT_NONE);
+               }
+               if (sec) {
+                       ast_say_number(bridge_channel->chan, sec, AST_DIGIT_NONE,
+                               ast_channel_language(bridge_channel->chan), NULL);
+                       ast_stream_and_wait(bridge_channel->chan, "queue-seconds", AST_DIGIT_NONE);
+               }
+       } else {
+               ast_stream_and_wait(bridge_channel->chan, file, AST_DIGIT_NONE);
+       }
+
+       /*
+        * 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.
+        */
+       if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
+               ast_moh_start(bridge_channel->chan, NULL, NULL);
+       }
+}
+
+static int bridge_features_connect_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct ast_bridge_features_limits *limits = hook_pvt;
+
+       if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               return -1;
+       }
+
+       limits_interval_playback(bridge, bridge_channel, limits, limits->connect_sound);
+       return -1;
+}
+
+static int bridge_features_warning_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct ast_bridge_features_limits *limits = hook_pvt;
+
+       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               /* If we aren't in the wait state, something more important than this warning is happening and we should skip it. */
+               limits_interval_playback(bridge, bridge_channel, limits, limits->warning_sound);
+       }
+
+       return !limits->frequency ? -1 : limits->frequency;
+}
+
+static void copy_bridge_features_limits(struct ast_bridge_features_limits *dst, struct ast_bridge_features_limits *src)
+{
+       dst->duration = src->duration;
+       dst->warning = src->warning;
+       dst->frequency = src->frequency;
+       dst->quitting_time = src->quitting_time;
+
+       ast_string_field_set(dst, duration_sound, src->duration_sound);
+       ast_string_field_set(dst, warning_sound, src->warning_sound);
+       ast_string_field_set(dst, connect_sound, src->connect_sound);
+}
+
+static int bridge_builtin_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull)
+{
+       struct ast_bridge_features_limits *feature_limits;
+
+       if (!limits->duration) {
+               return -1;
+       }
+
+       if (features->limits) {
+               ast_log(LOG_ERROR, "Tried to apply limits to a feature set that already has limits.\n");
+               return -1;
+       }
+
+       feature_limits = ast_malloc(sizeof(*feature_limits));
+       if (!feature_limits) {
+               return -1;
+       }
+
+       if (ast_bridge_features_limits_construct(feature_limits)) {
+               return -1;
+       }
+
+       copy_bridge_features_limits(feature_limits, limits);
+       features->limits = feature_limits;
+
+/* BUGBUG feature interval hooks need to be reimplemented to be more stand alone. */
+       if (ast_bridge_interval_hook(features, feature_limits->duration,
+               bridge_features_duration_callback, feature_limits, NULL, remove_on_pull)) {
+               ast_log(LOG_ERROR, "Failed to schedule the duration limiter to the bridge channel.\n");
+               return -1;
+       }
+
+       feature_limits->quitting_time = ast_tvadd(ast_tvnow(), ast_samp2tv(feature_limits->duration, 1000));
+
+       if (!ast_strlen_zero(feature_limits->connect_sound)) {
+               if (ast_bridge_interval_hook(features, 1,
+                       bridge_features_connect_callback, feature_limits, NULL, remove_on_pull)) {
+                       ast_log(LOG_WARNING, "Failed to schedule connect sound to the bridge channel.\n");
+               }
+       }
+
+       if (feature_limits->warning && feature_limits->warning < feature_limits->duration) {
+               if (ast_bridge_interval_hook(features, feature_limits->duration - feature_limits->warning,
+                       bridge_features_warning_callback, feature_limits, NULL, remove_on_pull)) {
+                       ast_log(LOG_WARNING, "Failed to schedule warning sound playback to the bridge channel.\n");
+               }
+       }
+
+       return 0;
+}
+
+static int unload_module(void)
+{
+       return 0;
+}
+
+static int load_module(void)
+{
+       ast_bridge_interval_register(AST_BRIDGE_BUILTIN_INTERVAL_LIMITS, bridge_builtin_set_limits);
+
+       /* Bump up our reference count so we can't be unloaded. */
+       ast_module_ref(ast_module_info->self);
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Built in bridging interval features");
diff --git a/bridges/bridge_holding.c b/bridges/bridge_holding.c
new file mode 100644 (file)
index 0000000..fe0a730
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Bridging technology for storing channels in a bridge for
+ *        the purpose of holding, parking, queues, and other such
+ *        states where a channel may need to be in a bridge but not
+ *        actually communicating with anything.
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+#include "asterisk/frame.h"
+#include "asterisk/musiconhold.h"
+
+enum role_flags {
+       HOLDING_ROLE_PARTICIPANT = (1 << 0),
+       HOLDING_ROLE_ANNOUNCER = (1 << 1),
+};
+
+/* BUGBUG Add IDLE_MODE_HOLD option to put channel on hold using AST_CONTROL_HOLD/AST_CONTROL_UNHOLD while in bridge */
+/* BUGBUG Add IDLE_MODE_SILENCE to send silence media frames to channel while in bridge (uses a silence generator) */
+/* BUGBUG A channel without the holding_participant role will assume IDLE_MODE_MOH with the default music class. */
+enum idle_modes {
+       IDLE_MODE_NONE = 0,
+       IDLE_MODE_MOH,
+       IDLE_MODE_RINGING,
+};
+
+/*! \brief Structure which contains per-channel role information */
+struct holding_channel {
+       struct ast_flags holding_roles;
+       enum idle_modes idle_mode;
+};
+
+static void participant_stop_hold_audio(struct ast_bridge_channel *bridge_channel)
+{
+       struct holding_channel *hc = bridge_channel->tech_pvt;
+       if (!hc) {
+               return;
+       }
+
+       switch (hc->idle_mode) {
+       case IDLE_MODE_MOH:
+               ast_moh_stop(bridge_channel->chan);
+               break;
+       case IDLE_MODE_RINGING:
+               ast_indicate(bridge_channel->chan, -1);
+               break;
+       case IDLE_MODE_NONE:
+               break;
+       }
+}
+
+static void participant_reaction_announcer_join(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_channel *chan;
+       chan = bridge_channel->chan;
+       participant_stop_hold_audio(bridge_channel);
+       if (ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR)) {
+               ast_log(LOG_WARNING, "Could not make participant %s compatible.\n", ast_channel_name(chan));
+       }
+}
+
+/* This should only be called on verified holding_participants. */
+static void participant_start_hold_audio(struct ast_bridge_channel *bridge_channel)
+{
+       struct holding_channel *hc = bridge_channel->tech_pvt;
+       const char *moh_class;
+
+       if (!hc) {
+               return;
+       }
+
+       switch(hc->idle_mode) {
+       case IDLE_MODE_MOH:
+               moh_class = ast_bridge_channel_get_role_option(bridge_channel, "holding_participant", "moh_class");
+               ast_moh_start(bridge_channel->chan, ast_strlen_zero(moh_class) ? NULL : moh_class, NULL);
+               break;
+       case IDLE_MODE_RINGING:
+               ast_indicate(bridge_channel->chan, AST_CONTROL_RINGING);
+               break;
+       case IDLE_MODE_NONE:
+               break;
+       }
+}
+
+static void handle_participant_join(struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *announcer_channel)
+{
+       struct ast_channel *us = bridge_channel->chan;
+       struct holding_channel *hc = bridge_channel->tech_pvt;
+       const char *idle_mode = ast_bridge_channel_get_role_option(bridge_channel, "holding_participant", "idle_mode");
+
+
+       if (!hc) {
+               return;
+       }
+
+       if (ast_strlen_zero(idle_mode)) {
+               hc->idle_mode = IDLE_MODE_NONE;
+       } else if (!strcmp(idle_mode, "musiconhold")) {
+               hc->idle_mode = IDLE_MODE_MOH;
+       } else if (!strcmp(idle_mode, "ringing")) {
+               hc->idle_mode = IDLE_MODE_RINGING;
+       } else {
+               ast_debug(2, "channel %s idle mode '%s' doesn't match any expected idle mode\n", ast_channel_name(us), idle_mode);
+       }
+
+       /* If the announcer channel isn't present, we need to set up ringing, music on hold, or whatever. */
+       if (!announcer_channel) {
+               participant_start_hold_audio(bridge_channel);
+               return;
+       }
+
+       /* If it is present though, we need to establish compatability. */
+       if (ast_set_write_format_by_id(us, AST_FORMAT_SLINEAR)) {
+               ast_log(LOG_WARNING, "Could not make participant %s compatible.\n", ast_channel_name(us));
+       }
+}
+
+static int holding_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_channel *other_channel;
+       struct ast_bridge_channel *announcer_channel;
+       struct holding_channel *hc;
+       struct ast_channel *us = bridge_channel->chan; /* The joining channel */
+
+       if (!(hc = ast_calloc(1, sizeof(*hc)))) {
+               return -1;
+       }
+
+       bridge_channel->tech_pvt = hc;
+
+       /* The bridge pvt holds the announcer channel if we have one. */
+       announcer_channel = bridge->tech_pvt;
+
+       if (ast_bridge_channel_has_role(bridge_channel, "announcer")) {
+               /* If another announcer already exists, scrap the holding channel struct so we know to ignore it in the future */
+               if (announcer_channel) {
+                       bridge_channel->tech_pvt = NULL;
+                       ast_free(hc);
+                       ast_log(LOG_WARNING, "A second announcer channel %s attempted to enter a holding bridge.\n",
+                               ast_channel_name(announcer_channel->chan));
+                       return -1;
+               }
+
+               bridge->tech_pvt = bridge_channel;
+               ast_set_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER);
+
+               /* The announcer should always be made compatible with signed linear */
+               if (ast_set_read_format_by_id(us, AST_FORMAT_SLINEAR)) {
+                       ast_log(LOG_ERROR, "Could not make announcer %s compatible.\n", ast_channel_name(us));
+               }
+
+               /* Make everyone compatible. While we are at it we should stop music on hold and ringing. */
+               AST_LIST_TRAVERSE(&bridge->channels, other_channel, entry) {
+                       /* Skip the reaction if we are the channel in question */
+                       if (bridge_channel == other_channel) {
+                               continue;
+                       }
+                       participant_reaction_announcer_join(other_channel);
+               }
+
+               return 0;
+       }
+
+       /* If the entering channel isn't an announcer then we need to setup it's properties and put it in its holding state if necessary */
+       ast_set_flag(&hc->holding_roles, HOLDING_ROLE_PARTICIPANT);
+       handle_participant_join(bridge_channel, announcer_channel);
+       return 0;
+}
+
+static void participant_reaction_announcer_leave(struct ast_bridge_channel *bridge_channel)
+{
+       struct holding_channel *hc = bridge_channel->tech_pvt;
+
+       if (!hc) {
+               /* We are dealing with a channel that failed to join properly. Skip it. */
+               return;
+       }
+
+       ast_bridge_channel_restore_formats(bridge_channel);
+       if (ast_test_flag(&hc->holding_roles, HOLDING_ROLE_PARTICIPANT)) {
+               participant_start_hold_audio(bridge_channel);
+       }
+}
+
+static void holding_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_channel *other_channel;
+       struct holding_channel *hc = bridge_channel->tech_pvt;
+
+       if (!hc) {
+               return;
+       }
+
+       if (!ast_test_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER)) {
+               /* It's not an announcer so nothing needs to react to its departure. Just free the tech_pvt. */
+               if (!bridge->tech_pvt) {
+                       /* Since no announcer is in the channel, we may be playing MOH/ringing. Stop that. */
+                       participant_stop_hold_audio(bridge_channel);
+               }
+               ast_free(hc);
+               bridge_channel->tech_pvt = NULL;
+               return;
+       }
+
+       /* When the announcer leaves, the other channels should reset their formats and go back to moh/ringing */
+       AST_LIST_TRAVERSE(&bridge->channels, other_channel, entry) {
+               participant_reaction_announcer_leave(other_channel);
+       }
+
+       /* Since the announcer is leaving, we should clear the tech_pvt pointing to it */
+       bridge->tech_pvt = NULL;
+
+       ast_free(hc);
+       bridge_channel->tech_pvt = NULL;
+}
+
+static int holding_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+       struct ast_bridge_channel *cur;
+       struct holding_channel *hc = bridge_channel->tech_pvt;
+
+       /* If there is no tech_pvt, then the channel failed to allocate one when it joined and is borked. Don't listen to him. */
+       if (!hc) {
+               return -1;
+       }
+
+       /* If we aren't an announcer, we never have any business writing anything. */
+       if (!ast_test_flag(&hc->holding_roles, HOLDING_ROLE_ANNOUNCER)) {
+               return -1;
+       }
+
+       /* Ok, so we are the announcer and there are one or more people available to receive our writes. Let's do it. */
+       AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+               if (bridge_channel == cur || !cur->tech_pvt) {
+                       continue;
+               }
+
+               ast_bridge_channel_queue_frame(cur, frame);
+       }
+
+       return 0;
+}
+
+static struct ast_bridge_technology holding_bridge = {
+       .name = "holding_bridge",
+       .capabilities = AST_BRIDGE_CAPABILITY_HOLDING,
+       .preference = AST_BRIDGE_PREFERENCE_BASE_HOLDING,
+       .write = holding_bridge_write,
+       .join = holding_bridge_join,
+       .leave = holding_bridge_leave,
+};
+
+static int unload_module(void)
+{
+       ast_format_cap_destroy(holding_bridge.format_capabilities);
+       return ast_bridge_technology_unregister(&holding_bridge);
+}
+
+static int load_module(void)
+{
+       if (!(holding_bridge.format_capabilities = ast_format_cap_alloc())) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+       ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
+       ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
+       ast_format_cap_add_all_by_type(holding_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
+
+       return ast_bridge_technology_register(&holding_bridge);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Holding bridge module");
+
diff --git a/bridges/bridge_multiplexed.c b/bridges/bridge_multiplexed.c
deleted file mode 100644 (file)
index 309ad47..0000000
+++ /dev/null
@@ -1,513 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2008, Digium, Inc.
- *
- * Joshua Colp <jcolp@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Two channel bridging module which groups bridges into batches of threads
- *
- * \author Joshua Colp <jcolp@digium.com>
- *
- * \ingroup bridges
- */
-
-/*** MODULEINFO
-       <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "asterisk/module.h"
-#include "asterisk/channel.h"
-#include "asterisk/bridging.h"
-#include "asterisk/bridging_technology.h"
-#include "asterisk/frame.h"
-#include "asterisk/astobj2.h"
-
-/*! \brief Number of buckets our multiplexed thread container can have */
-#define MULTIPLEXED_BUCKETS 53
-
-/*! \brief Number of bridges we handle in a single thread */
-#define MULTIPLEXED_MAX_BRIDGES                4
-
-/*! \brief Structure which represents a single thread handling multiple 2 channel bridges */
-struct multiplexed_thread {
-       /*! Thread itself */
-       pthread_t thread;
-       /*! Channels serviced by this thread */
-       struct ast_channel *chans[2 * MULTIPLEXED_MAX_BRIDGES];
-       /*! Pipe used to wake up the multiplexed thread */
-       int pipe[2];
-       /*! Number of channels actually being serviced by this thread */
-       unsigned int service_count;
-       /*! Number of bridges in this thread */
-       unsigned int bridges;
-       /*! TRUE if the thread is waiting on channels */
-       unsigned int waiting:1;
-};
-
-/*! \brief Container of all operating multiplexed threads */
-static struct ao2_container *muxed_threads;
-
-/*! \brief Callback function for finding a free multiplexed thread */
-static int find_multiplexed_thread(void *obj, void *arg, int flags)
-{
-       struct multiplexed_thread *muxed_thread = obj;
-
-       return (muxed_thread->bridges < MULTIPLEXED_MAX_BRIDGES) ? CMP_MATCH | CMP_STOP : 0;
-}
-
-/*! \brief Destroy callback for a multiplexed thread structure */
-static void destroy_multiplexed_thread(void *obj)
-{
-       struct multiplexed_thread *muxed_thread = obj;
-
-       if (muxed_thread->pipe[0] > -1) {
-               close(muxed_thread->pipe[0]);
-       }
-       if (muxed_thread->pipe[1] > -1) {
-               close(muxed_thread->pipe[1]);
-       }
-}
-
-/*! \brief Create function which finds/reserves/references a multiplexed thread structure */
-static int multiplexed_bridge_create(struct ast_bridge *bridge)
-{
-       struct multiplexed_thread *muxed_thread;
-
-       ao2_lock(muxed_threads);
-
-       /* Try to find an existing thread to handle our additional channels */
-       muxed_thread = ao2_callback(muxed_threads, 0, find_multiplexed_thread, NULL);
-       if (!muxed_thread) {
-               int flags;
-
-               /* If we failed we will have to create a new one from scratch */
-               muxed_thread = ao2_alloc(sizeof(*muxed_thread), destroy_multiplexed_thread);
-               if (!muxed_thread) {
-                       ast_debug(1, "Failed to find or create a new multiplexed thread for bridge '%p'\n", bridge);
-                       ao2_unlock(muxed_threads);
-                       return -1;
-               }
-
-               muxed_thread->pipe[0] = muxed_thread->pipe[1] = -1;
-               /* Setup a pipe so we can poke the thread itself when needed */
-               if (pipe(muxed_thread->pipe)) {
-                       ast_debug(1, "Failed to create a pipe for poking a multiplexed thread for bridge '%p'\n", bridge);
-                       ao2_ref(muxed_thread, -1);
-                       ao2_unlock(muxed_threads);
-                       return -1;
-               }
-
-               /* Setup each pipe for non-blocking operation */
-               flags = fcntl(muxed_thread->pipe[0], F_GETFL);
-               if (fcntl(muxed_thread->pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
-                       ast_log(LOG_WARNING, "Failed to setup first nudge pipe for non-blocking operation on %p (%d: %s)\n", bridge, errno, strerror(errno));
-                       ao2_ref(muxed_thread, -1);
-                       ao2_unlock(muxed_threads);
-                       return -1;
-               }
-               flags = fcntl(muxed_thread->pipe[1], F_GETFL);
-               if (fcntl(muxed_thread->pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
-                       ast_log(LOG_WARNING, "Failed to setup second nudge pipe for non-blocking operation on %p (%d: %s)\n", bridge, errno, strerror(errno));
-                       ao2_ref(muxed_thread, -1);
-                       ao2_unlock(muxed_threads);
-                       return -1;
-               }
-
-               /* Set up default parameters */
-               muxed_thread->thread = AST_PTHREADT_NULL;
-
-               /* Finally link us into the container so others may find us */
-               ao2_link(muxed_threads, muxed_thread);
-               ast_debug(1, "Created multiplexed thread '%p' for bridge '%p'\n", muxed_thread, bridge);
-       } else {
-               ast_debug(1, "Found multiplexed thread '%p' for bridge '%p'\n", muxed_thread, bridge);
-       }
-
-       /* Increase the number of bridges using this multiplexed bridge */
-       ++muxed_thread->bridges;
-
-       ao2_unlock(muxed_threads);
-
-       bridge->bridge_pvt = muxed_thread;
-
-       return 0;
-}
-
-/*!
- * \internal
- * \brief Nudges the multiplex thread.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to poke the thread.
- *
- * \note This function assumes the muxed_thread is locked.
- *
- * \return Nothing
- */
-static void multiplexed_nudge(struct multiplexed_thread *muxed_thread)
-{
-       int nudge = 0;
-
-       if (muxed_thread->thread == AST_PTHREADT_NULL) {
-               return;
-       }
-
-       if (write(muxed_thread->pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
-               ast_log(LOG_ERROR, "We couldn't poke multiplexed thread '%p'... something is VERY wrong\n", muxed_thread);
-       }
-
-       while (muxed_thread->waiting) {
-               sched_yield();
-       }
-}
-
-/*! \brief Destroy function which unreserves/unreferences/removes a multiplexed thread structure */
-static int multiplexed_bridge_destroy(struct ast_bridge *bridge)
-{
-       struct multiplexed_thread *muxed_thread;
-       pthread_t thread;
-
-       muxed_thread = bridge->bridge_pvt;
-       if (!muxed_thread) {
-               return -1;
-       }
-       bridge->bridge_pvt = NULL;
-
-       ao2_lock(muxed_threads);
-
-       if (--muxed_thread->bridges) {
-               /* Other bridges are still using the multiplexed thread. */
-               ao2_unlock(muxed_threads);
-       } else {
-               ast_debug(1, "Unlinking multiplexed thread '%p' since nobody is using it anymore\n",
-                       muxed_thread);
-               ao2_unlink(muxed_threads, muxed_thread);
-               ao2_unlock(muxed_threads);
-
-               /* Stop the multiplexed bridge thread. */
-               ao2_lock(muxed_thread);
-               multiplexed_nudge(muxed_thread);
-               thread = muxed_thread->thread;
-               muxed_thread->thread = AST_PTHREADT_STOP;
-               ao2_unlock(muxed_thread);
-
-               if (thread != AST_PTHREADT_NULL) {
-                       /* Wait for multiplexed bridge thread to die. */
-                       pthread_join(thread, NULL);
-               }
-       }
-
-       ao2_ref(muxed_thread, -1);
-       return 0;
-}
-
-/*! \brief Thread function that executes for multiplexed threads */
-static void *multiplexed_thread_function(void *data)
-{
-       struct multiplexed_thread *muxed_thread = data;
-       int fds = muxed_thread->pipe[0];
-
-       ast_debug(1, "Starting actual thread for multiplexed thread '%p'\n", muxed_thread);
-
-       ao2_lock(muxed_thread);
-
-       while (muxed_thread->thread != AST_PTHREADT_STOP) {
-               struct ast_channel *winner;
-               int to = -1;
-               int outfd = -1;
-
-               if (1 < muxed_thread->service_count) {
-                       struct ast_channel *first;
-
-                       /* Move channels around so not just the first one gets priority */
-                       first = muxed_thread->chans[0];
-                       memmove(muxed_thread->chans, muxed_thread->chans + 1,
-                               sizeof(struct ast_channel *) * (muxed_thread->service_count - 1));
-                       muxed_thread->chans[muxed_thread->service_count - 1] = first;
-               }
-
-               muxed_thread->waiting = 1;
-               ao2_unlock(muxed_thread);
-               winner = ast_waitfor_nandfds(muxed_thread->chans, muxed_thread->service_count, &fds, 1, NULL, &outfd, &to);
-               muxed_thread->waiting = 0;
-               ao2_lock(muxed_thread);
-               if (muxed_thread->thread == AST_PTHREADT_STOP) {
-                       break;
-               }
-
-               if (outfd > -1) {
-                       int nudge;
-
-                       if (read(muxed_thread->pipe[0], &nudge, sizeof(nudge)) < 0) {
-                               if (errno != EINTR && errno != EAGAIN) {
-                                       ast_log(LOG_WARNING, "read() failed for pipe on multiplexed thread '%p': %s\n", muxed_thread, strerror(errno));
-                               }
-                       }
-               }
-               if (winner && ast_channel_internal_bridge(winner)) {
-                       struct ast_bridge *bridge;
-                       int stop = 0;
-
-                       ao2_unlock(muxed_thread);
-                       while ((bridge = ast_channel_internal_bridge(winner)) && ao2_trylock(bridge)) {
-                               sched_yield();
-                               if (muxed_thread->thread == AST_PTHREADT_STOP) {
-                                       stop = 1;
-                                       break;
-                               }
-                       }
-                       if (!stop && bridge) {
-                               ast_bridge_handle_trip(bridge, NULL, winner, -1);
-                               ao2_unlock(bridge);
-                       }
-                       ao2_lock(muxed_thread);
-               }
-       }
-
-       ao2_unlock(muxed_thread);
-
-       ast_debug(1, "Stopping actual thread for multiplexed thread '%p'\n", muxed_thread);
-       ao2_ref(muxed_thread, -1);
-
-       return NULL;
-}
-
-/*!
- * \internal
- * \brief Check to see if the multiplexed bridge thread needs to be started.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to check if need to start thread.
- *
- * \note This function assumes the muxed_thread is locked.
- *
- * \return Nothing
- */
-static void multiplexed_thread_start(struct multiplexed_thread *muxed_thread)
-{
-       if (muxed_thread->service_count && muxed_thread->thread == AST_PTHREADT_NULL) {
-               ao2_ref(muxed_thread, +1);
-               if (ast_pthread_create(&muxed_thread->thread, NULL, multiplexed_thread_function, muxed_thread)) {
-                       muxed_thread->thread = AST_PTHREADT_NULL;/* For paranoia's sake. */
-                       ao2_ref(muxed_thread, -1);
-                       ast_log(LOG_WARNING, "Failed to create the common thread for multiplexed thread '%p', trying next time\n",
-                               muxed_thread);
-               }
-       }
-}
-
-/*!
- * \internal
- * \brief Add a channel to the multiplexed bridge.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to add a channel.
- * \param chan Channel to add to the channel service array.
- *
- * \return Nothing
- */
-static void multiplexed_chan_add(struct multiplexed_thread *muxed_thread, struct ast_channel *chan)
-{
-       int idx;
-
-       ao2_lock(muxed_thread);
-
-       multiplexed_nudge(muxed_thread);
-
-       /* Check if already in the channel service array for safety. */
-       for (idx = 0; idx < muxed_thread->service_count; ++idx) {
-               if (muxed_thread->chans[idx] == chan) {
-                       break;
-               }
-       }
-       if (idx == muxed_thread->service_count) {
-               /* Channel to add was not already in the array. */
-               if (muxed_thread->service_count < ARRAY_LEN(muxed_thread->chans)) {
-                       muxed_thread->chans[muxed_thread->service_count++] = chan;
-               } else {
-                       ast_log(LOG_ERROR, "Could not add channel %s to multiplexed thread %p.  Array not large enough.\n",
-                               ast_channel_name(chan), muxed_thread);
-                       ast_assert(0);
-               }
-       }
-
-       multiplexed_thread_start(muxed_thread);
-
-       ao2_unlock(muxed_thread);
-}
-
-/*!
- * \internal
- * \brief Remove a channel from the multiplexed bridge.
- * \since 12.0.0
- *
- * \param muxed_thread Controller to remove a channel.
- * \param chan Channel to remove from the channel service array.
- *
- * \return Nothing
- */
-static void multiplexed_chan_remove(struct multiplexed_thread *muxed_thread, struct ast_channel *chan)
-{
-       int idx;
-
-       ao2_lock(muxed_thread);
-
-       multiplexed_nudge(muxed_thread);
-
-       /* Remove channel from service array. */
-       for (idx = 0; idx < muxed_thread->service_count; ++idx) {
-               if (muxed_thread->chans[idx] != chan) {
-                       continue;
-               }
-               muxed_thread->chans[idx] = muxed_thread->chans[--muxed_thread->service_count];
-               break;
-       }
-
-       multiplexed_thread_start(muxed_thread);
-
-       ao2_unlock(muxed_thread);
-}
-
-/*! \brief Join function which actually adds the channel into the array to be monitored */
-static int multiplexed_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;
-       struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;
-       struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-       ast_debug(1, "Adding channel '%s' to multiplexed thread '%p' for monitoring\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-       multiplexed_chan_add(muxed_thread, bridge_channel->chan);
-
-       /* If the second channel has not yet joined do not make things compatible */
-       if (c0 == c1) {
-               return 0;
-       }
-
-       if ((ast_format_cmp(ast_channel_writeformat(c0), ast_channel_readformat(c1)) == AST_FORMAT_CMP_EQUAL) &&
-               (ast_format_cmp(ast_channel_readformat(c0), ast_channel_writeformat(c1)) == AST_FORMAT_CMP_EQUAL) &&
-               (ast_format_cap_identical(ast_channel_nativeformats(c0), ast_channel_nativeformats(c1)))) {
-               return 0;
-       }
-
-       return ast_channel_make_compatible(c0, c1);
-}
-
-/*! \brief Leave function which actually removes the channel from the array */
-static int multiplexed_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-       ast_debug(1, "Removing channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-       multiplexed_chan_remove(muxed_thread, bridge_channel->chan);
-
-       return 0;
-}
-
-/*! \brief Suspend function which means control of the channel is going elsewhere */
-static void multiplexed_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-       ast_debug(1, "Suspending channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-       multiplexed_chan_remove(muxed_thread, bridge_channel->chan);
-}
-
-/*! \brief Unsuspend function which means control of the channel is coming back to us */
-static void multiplexed_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       struct multiplexed_thread *muxed_thread = bridge->bridge_pvt;
-
-       ast_debug(1, "Unsuspending channel '%s' from multiplexed thread '%p'\n", ast_channel_name(bridge_channel->chan), muxed_thread);
-
-       multiplexed_chan_add(muxed_thread, bridge_channel->chan);
-}
-
-/*! \brief Write function for writing frames into the bridge */
-static enum ast_bridge_write_result multiplexed_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
-{
-       struct ast_bridge_channel *other;
-
-       /* If this is the only channel in this bridge then immediately exit */
-       if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
-               return AST_BRIDGE_WRITE_FAILED;
-       }
-
-       /* Find the channel we actually want to write to */
-       if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
-               return AST_BRIDGE_WRITE_FAILED;
-       }
-
-       /* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
-       if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-               ast_write(other->chan, frame);
-       }
-
-       return AST_BRIDGE_WRITE_SUCCESS;
-}
-
-static struct ast_bridge_technology multiplexed_bridge = {
-       .name = "multiplexed_bridge",
-       .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
-       .preference = AST_BRIDGE_PREFERENCE_HIGH,
-       .create = multiplexed_bridge_create,
-       .destroy = multiplexed_bridge_destroy,
-       .join = multiplexed_bridge_join,
-       .leave = multiplexed_bridge_leave,
-       .suspend = multiplexed_bridge_suspend,
-       .unsuspend = multiplexed_bridge_unsuspend,
-       .write = multiplexed_bridge_write,
-};
-
-static int unload_module(void)
-{
-       int res = ast_bridge_technology_unregister(&multiplexed_bridge);
-
-       ao2_ref(muxed_threads, -1);
-       multiplexed_bridge.format_capabilities = ast_format_cap_destroy(multiplexed_bridge.format_capabilities);
-
-       return res;
-}
-
-static int load_module(void)
-{
-       if (!(muxed_threads = ao2_container_alloc(MULTIPLEXED_BUCKETS, NULL, NULL))) {
-               return AST_MODULE_LOAD_DECLINE;
-       }
-       if (!(multiplexed_bridge.format_capabilities = ast_format_cap_alloc())) {
-               return AST_MODULE_LOAD_DECLINE;
-       }
-       ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
-       ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
-       ast_format_cap_add_all_by_type(multiplexed_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
-       return ast_bridge_technology_register(&multiplexed_bridge);
-}
-
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Multiplexed two channel bridging module");
diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c
new file mode 100644 (file)
index 0000000..1117e5a
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Native RTP bridging module
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+#include "asterisk/frame.h"
+#include "asterisk/rtp_engine.h"
+#include "asterisk/audiohook.h"
+
+/*! \brief Forward declarations for frame hook usage */
+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+
+/*! \brief Internal structure which contains information about bridged RTP channels */
+struct native_rtp_bridge_data {
+       /*! \brief Framehook used to intercept certain control frames */
+       int id;
+};
+
+/*! \brief Frame hook that is called to intercept hold/unhold */
+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+       RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+
+       if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+               return f;
+       }
+
+       ast_channel_lock(chan);
+       bridge = ast_channel_get_bridge(chan);
+       ast_channel_unlock(chan);
+
+       /* It's safe for NULL to be passed to both of these, bridge_channel isn't used at all */
+       if (bridge) {
+               if (f->subclass.integer == AST_CONTROL_HOLD) {
+                       native_rtp_bridge_leave(ast_channel_internal_bridge(chan), NULL);
+               } else if ((f->subclass.integer == AST_CONTROL_UNHOLD) || (f->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
+                       native_rtp_bridge_join(ast_channel_internal_bridge(chan), NULL);
+               }
+       }
+
+       return f;
+}
+
+/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */
+static int native_rtp_bridge_capable(struct ast_channel *chan)
+{
+       if (ast_channel_monitor(chan) || (ast_channel_audiohooks(chan) &&
+               !ast_audiohook_write_list_empty(ast_channel_audiohooks(chan))) ||
+               !ast_framehook_list_is_empty(ast_channel_framehooks(chan))) {
+               return 0;
+       } else {
+               return 1;
+       }
+}
+
+/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */
+static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,
+       struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,
+       struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)
+{
+       enum ast_rtp_glue_result audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID, video_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+       enum ast_rtp_glue_result audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID, video_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+
+       if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||
+               (c1 && !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type)))) {
+               return AST_RTP_GLUE_RESULT_FORBID;
+       }
+
+       audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);
+       video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
+
+       if (c1) {
+               audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);
+               video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
+       }
+
+       /* Apply any limitations on direct media bridging that may be present */
+       if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
+               if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {
+                       /* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
+                       audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+               } else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {
+                       audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+               }
+       }
+       if (c1 && video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
+               if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {
+                       /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
+                       video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+               } else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {
+                       video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+               }
+       }
+
+       /* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
+       if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
+               audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+       }
+       if (c1 && video_glue1_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
+               audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+       }
+
+       /* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
+       if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID || (c1 && audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID)) {
+               return AST_RTP_GLUE_RESULT_FORBID;
+       }
+
+       return audio_glue0_res;
+}
+
+static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
+{
+       struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels);
+       struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+       enum ast_rtp_glue_result native_type;
+       struct ast_rtp_glue *glue0, *glue1;
+       struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL, *vinstance1 = NULL;
+       RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+       RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+       int read_ptime0, read_ptime1, write_ptime0, write_ptime1;
+
+       /* We require two channels before even considering native bridging */
+       if (bridge->num_channels != 2) {
+               ast_debug(1, "Bridge '%s' can not use native RTP bridge as two channels are required\n",
+                       bridge->uniqueid);
+               return 0;
+       }
+
+       if (!native_rtp_bridge_capable(c0->chan)) {
+               ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
+                       bridge->uniqueid, ast_channel_name(c0->chan));
+               return 0;
+       }
+
+       if (!native_rtp_bridge_capable(c1->chan)) {
+               ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
+                       bridge->uniqueid, ast_channel_name(c1->chan));
+               return 0;
+       }
+
+       if ((native_type = native_rtp_bridge_get(c0->chan, c1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1))
+               == AST_RTP_GLUE_RESULT_FORBID) {
+               ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",
+                       bridge->uniqueid);
+               return 0;
+       }
+
+       if (ao2_container_count(c0->features->dtmf_hooks) && ast_rtp_instance_dtmf_mode_get(instance0)) {
+               ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
+                       bridge->uniqueid, ast_channel_name(c0->chan));
+               return 0;
+       }
+
+       if (ao2_container_count(c1->features->dtmf_hooks) && ast_rtp_instance_dtmf_mode_get(instance1)) {
+               ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
+                       bridge->uniqueid, ast_channel_name(c1->chan));
+               return 0;
+       }
+
+       if ((native_type == AST_RTP_GLUE_RESULT_LOCAL) && ((ast_rtp_instance_get_engine(instance0)->local_bridge !=
+               ast_rtp_instance_get_engine(instance1)->local_bridge) ||
+               (ast_rtp_instance_get_engine(instance0)->dtmf_compatible &&
+                       !ast_rtp_instance_get_engine(instance0)->dtmf_compatible(c0->chan, instance0, c1->chan, instance1)))) {
+               ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",
+                       bridge->uniqueid);
+               return 0;
+       }
+
+       /* Make sure that codecs match */
+       if (glue0->get_codec) {
+               glue0->get_codec(c0->chan, cap0);
+       }
+       if (glue1->get_codec) {
+               glue1->get_codec(c1->chan, cap1);
+       }
+       if (!ast_format_cap_is_empty(cap0) && !ast_format_cap_is_empty(cap1) && !ast_format_cap_has_joint(cap0, cap1)) {
+               char tmp0[256] = { 0, }, tmp1[256] = { 0, };
+
+               ast_debug(1, "Channel codec0 = %s is not codec1 = %s, cannot native bridge in RTP.\n",
+                       ast_getformatname_multiple(tmp0, sizeof(tmp0), cap0),
+                       ast_getformatname_multiple(tmp1, sizeof(tmp1), cap1));
+               return 0;
+       }
+
+       read_ptime0 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance0)->pref, ast_channel_rawreadformat(c0->chan))).cur_ms;
+       read_ptime1 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance1)->pref, ast_channel_rawreadformat(c1->chan))).cur_ms;
+       write_ptime0 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance0)->pref, ast_channel_rawwriteformat(c0->chan))).cur_ms;
+       write_ptime1 = (ast_codec_pref_getsize(&ast_rtp_instance_get_codecs(instance1)->pref, ast_channel_rawwriteformat(c1->chan))).cur_ms;
+
+       if (read_ptime0 != write_ptime1 || read_ptime1 != write_ptime0) {
+               ast_debug(1, "Packetization differs between RTP streams (%d != %d or %d != %d). Cannot native bridge in RTP\n",
+                               read_ptime0, write_ptime1, read_ptime1, write_ptime0);
+               return 0;
+       }
+
+       return 1;
+}
+
+/*! \brief Helper function which adds frame hook to bridge channel */
+static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)
+{
+       struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);
+       static struct ast_framehook_interface hook = {
+               .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+               .event_cb = native_rtp_framehook,
+       };
+
+       if (!data) {
+               return -1;
+       }
+
+       ast_channel_lock(bridge_channel->chan);
+
+       if (!(data->id = ast_framehook_attach(bridge_channel->chan, &hook)) < 0) {
+               ast_channel_unlock(bridge_channel->chan);
+               ao2_cleanup(data);
+               return -1;
+       }
+
+       ast_channel_unlock(bridge_channel->chan);
+
+       bridge_channel->bridge_pvt = data;
+
+       return 0;
+}
+
+/*! \brief Helper function which removes frame hook from bridge channel */
+static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)
+{
+       RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->bridge_pvt, ao2_cleanup);
+
+       if (!data) {
+               return;
+       }
+
+       ast_channel_lock(bridge_channel->chan);
+       ast_framehook_detach(bridge_channel->chan, data->id);
+       ast_channel_unlock(bridge_channel->chan);
+       bridge_channel->bridge_pvt = NULL;
+}
+
+static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels);
+       struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+       enum ast_rtp_glue_result native_type;
+       struct ast_rtp_glue *glue0, *glue1;
+       struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL;
+       struct ast_rtp_instance *vinstance1 = NULL, *tinstance0 = NULL, *tinstance1 = NULL;
+       RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+       RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc_nolock(), ast_format_cap_destroy);
+
+       native_rtp_bridge_framehook_detach(c0);
+       if (native_rtp_bridge_framehook_attach(c0)) {
+               return -1;
+       }
+
+       native_rtp_bridge_framehook_detach(c1);
+       if (native_rtp_bridge_framehook_attach(c1)) {
+               native_rtp_bridge_framehook_detach(c0);
+               return -1;
+       }
+
+       native_type = native_rtp_bridge_get(c0->chan, c1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+       if (glue0->get_codec) {
+               glue0->get_codec(c0->chan, cap0);
+       }
+       if (glue1->get_codec) {
+               glue1->get_codec(c1->chan, cap1);
+       }
+
+       if (native_type == AST_RTP_GLUE_RESULT_LOCAL) {
+               if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
+                       ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);
+               }
+               if (ast_rtp_instance_get_engine(instance1)->local_bridge) {
+                       ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);
+               }
+               ast_rtp_instance_set_bridged(instance0, instance1);
+               ast_rtp_instance_set_bridged(instance1, instance0);
+       } else {
+               glue0->update_peer(c0->chan, instance1, vinstance1, tinstance1, cap1, 0);
+               glue1->update_peer(c1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+       }
+
+       return 0;
+}
+
+static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+       native_rtp_bridge_join(bridge, bridge_channel);
+}
+
+static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_channel *c0 = AST_LIST_FIRST(&bridge->channels) ? AST_LIST_FIRST(&bridge->channels) : bridge_channel;
+       struct ast_bridge_channel *c1 = AST_LIST_LAST(&bridge->channels);
+       enum ast_rtp_glue_result native_type;
+       struct ast_rtp_glue *glue0, *glue1 = NULL;
+       struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL, *vinstance0 = NULL, *vinstance1 = NULL;
+
+       native_rtp_bridge_framehook_detach(c0);
+       if (c1) {
+               native_rtp_bridge_framehook_detach(c1);
+       }
+
+       native_type = native_rtp_bridge_get(c0->chan, c1 ? c1->chan : NULL, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+       if (native_type == AST_RTP_GLUE_RESULT_LOCAL) {
+               if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
+                       ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);
+               }
+               if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {
+                       ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);
+               }
+               ast_rtp_instance_set_bridged(instance0, instance1);
+               if (instance1) {
+                       ast_rtp_instance_set_bridged(instance1, instance0);
+               }
+       } else {
+               glue0->update_peer(c0->chan, NULL, NULL, NULL, NULL, 0);
+               if (glue1) {
+                       glue1->update_peer(c1->chan, NULL, NULL, NULL, NULL, 0);
+               }
+       }
+}
+
+static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+       struct ast_bridge_channel *other = ast_bridge_channel_peer(bridge_channel);
+
+       if (!other) {
+               return -1;
+       }
+
+       /* The bridging core takes care of freeing the passed in frame. */
+       ast_bridge_channel_queue_frame(other, frame);
+
+       return 0;
+}
+
+static struct ast_bridge_technology native_rtp_bridge = {
+       .name = "native_rtp",
+       .capabilities = AST_BRIDGE_CAPABILITY_NATIVE,
+       .preference = AST_BRIDGE_PREFERENCE_BASE_NATIVE,
+       .join = native_rtp_bridge_join,
+       .unsuspend = native_rtp_bridge_unsuspend,
+       .leave = native_rtp_bridge_leave,
+       .suspend = native_rtp_bridge_leave,
+       .write = native_rtp_bridge_write,
+       .compatible = native_rtp_bridge_compatible,
+};
+
+static int unload_module(void)
+{
+       ast_format_cap_destroy(native_rtp_bridge.format_capabilities);
+       return ast_bridge_technology_unregister(&native_rtp_bridge);
+}
+
+static int load_module(void)
+{
+       if (!(native_rtp_bridge.format_capabilities = ast_format_cap_alloc())) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+       ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_AUDIO);
+       ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_VIDEO);
+       ast_format_cap_add_all_by_type(native_rtp_bridge.format_capabilities, AST_FORMAT_TYPE_TEXT);
+
+       return ast_bridge_technology_register(&native_rtp_bridge);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Native RTP bridging module");
index 947983bae495bc2ff8dc6d24de1049dba6fcbea4..3e53b31c0e1849b7f361927228954c54e6e8d921 100644 (file)
@@ -66,32 +66,26 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
        return ast_channel_make_compatible(c0, c1);
 }
 
-static enum ast_bridge_write_result simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
        struct ast_bridge_channel *other;
 
-       /* If this is the only channel in this bridge then immediately exit */
-       if (AST_LIST_FIRST(&bridge->channels) == AST_LIST_LAST(&bridge->channels)) {
-               return AST_BRIDGE_WRITE_FAILED;
-       }
-
        /* Find the channel we actually want to write to */
-       if (!(other = (AST_LIST_FIRST(&bridge->channels) == bridge_channel ? AST_LIST_LAST(&bridge->channels) : AST_LIST_FIRST(&bridge->channels)))) {
-               return AST_BRIDGE_WRITE_FAILED;
+       other = ast_bridge_channel_peer(bridge_channel);
+       if (!other) {
+               return -1;
        }
 
-       /* Write the frame out if they are in the waiting state... don't worry about freeing it, the bridging core will take care of it */
-       if (other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-               ast_write(other->chan, frame);
-       }
+       /* The bridging core takes care of freeing the passed in frame. */
+       ast_bridge_channel_queue_frame(other, frame);
 
-       return AST_BRIDGE_WRITE_SUCCESS;
+       return 0;
 }
 
 static struct ast_bridge_technology simple_bridge = {
        .name = "simple_bridge",
-       .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_THREAD,
-       .preference = AST_BRIDGE_PREFERENCE_MEDIUM,
+       .capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX,
+       .preference = AST_BRIDGE_PREFERENCE_BASE_1TO1MIX,
        .join = simple_bridge_join,
        .write = simple_bridge_write,
 };
index 613601a1f154e7828f7f6aed3a86afb42cd7670a..4583435a08d471d7dbab34ac9d862da445779aa6 100644 (file)
@@ -100,13 +100,15 @@ struct softmix_channel {
        struct ast_frame read_frame;
        /*! DSP for detecting silence */
        struct ast_dsp *dsp;
-       /*! Bit used to indicate if a channel is talking or not. This affects how
-        * the channel's audio is mixed back to it. */
-       int talking:1;
-       /*! Bit used to indicate that the channel provided audio for this mixing interval */
-       int have_audio:1;
-       /*! Bit used to indicate that a frame is available to be written out to the channel */
-       int have_frame:1;
+       /*!
+        * \brief TRUE if a channel is talking.
+        *
+        * \note This affects how the channel's audio is mixed back to
+        * it.
+        */
+       unsigned int talking:1;
+       /*! TRUE if the channel provided audio for this mixing interval */
+       unsigned int have_audio:1;
        /*! Buffer containing final mixed audio from all sources */
        short final_buf[MAX_DATALEN];
        /*! Buffer containing only the audio from the channel */
@@ -117,28 +119,36 @@ struct softmix_channel {
 
 struct softmix_bridge_data {
        struct ast_timer *timer;
+       /*! Lock for signaling the mixing thread. */
+       ast_mutex_t lock;
+       /*! Condition, used if we need to wake up the mixing thread. */
+       ast_cond_t cond;
+       /*! Thread handling the mixing */
+       pthread_t thread;
        unsigned int internal_rate;
        unsigned int internal_mixing_interval;
+       /*! TRUE if the mixing thread should stop */
+       unsigned int stop:1;
 };
 
 struct softmix_stats {
-               /*! Each index represents a sample rate used above the internal rate. */
-               unsigned int sample_rates[16];
-               /*! Each index represents the number of channels using the same index in the sample_rates array.  */
-               unsigned int num_channels[16];
-               /*! the number of channels above the internal sample rate */
-               unsigned int num_above_internal_rate;
-               /*! the number of channels at the internal sample rate */
-               unsigned int num_at_internal_rate;
-               /*! the absolute highest sample rate supported by any channel in the bridge */
-               unsigned int highest_supported_rate;
-               /*! Is the sample rate locked by the bridge, if so what is that rate.*/
-               unsigned int locked_rate;
+       /*! Each index represents a sample rate used above the internal rate. */
+       unsigned int sample_rates[16];
+       /*! Each index represents the number of channels using the same index in the sample_rates array.  */
+       unsigned int num_channels[16];
+       /*! the number of channels above the internal sample rate */
+       unsigned int num_above_internal_rate;
+       /*! the number of channels at the internal sample rate */
+       unsigned int num_at_internal_rate;
+       /*! the absolute highest sample rate supported by any channel in the bridge */
+       unsigned int highest_supported_rate;
+       /*! Is the sample rate locked by the bridge, if so what is that rate.*/
+       unsigned int locked_rate;
 };
 
 struct softmix_mixing_array {
-       int max_num_entries;
-       int used_entries;
+       unsigned int max_num_entries;
+       unsigned int used_entries;
        int16_t **buffers;
 };
 
@@ -213,7 +223,7 @@ static void softmix_translate_helper_change_rate(struct softmix_translate_helper
 /*!
  * \internal
  * \brief Get the next available audio on the softmix channel's read stream
- * and determine if it should be mixed out or not on the write stream. 
+ * and determine if it should be mixed out or not on the write stream.
  *
  * \retval pointer to buffer containing the exact number of samples requested on success.
  * \retval NULL if no samples are present
@@ -295,54 +305,9 @@ static void softmix_translate_helper_cleanup(struct softmix_translate_helper *tr
        }
 }
 
-static void softmix_bridge_data_destroy(void *obj)
-{
-       struct softmix_bridge_data *softmix_data = obj;
-
-       if (softmix_data->timer) {
-               ast_timer_close(softmix_data->timer);
-               softmix_data->timer = NULL;
-       }
-}
-
-/*! \brief Function called when a bridge is created */
-static int softmix_bridge_create(struct ast_bridge *bridge)
-{
-       struct softmix_bridge_data *softmix_data;
-
-       if (!(softmix_data = ao2_alloc(sizeof(*softmix_data), softmix_bridge_data_destroy))) {
-               return -1;
-       }
-       if (!(softmix_data->timer = ast_timer_open())) {
-               ao2_ref(softmix_data, -1);
-               return -1;
-       }
-
-       /* start at 8khz, let it grow from there */
-       softmix_data->internal_rate = 8000;
-       softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
-
-       bridge->bridge_pvt = softmix_data;
-       return 0;
-}
-
-/*! \brief Function called when a bridge is destroyed */
-static int softmix_bridge_destroy(struct ast_bridge *bridge)
-{
-       struct softmix_bridge_data *softmix_data;
-
-       softmix_data = bridge->bridge_pvt;
-       if (!softmix_data) {
-               return -1;
-       }
-       ao2_ref(softmix_data, -1);
-       bridge->bridge_pvt = NULL;
-       return 0;
-}
-
 static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset)
 {
-       struct softmix_channel *sc = bridge_channel->bridge_pvt;
+       struct softmix_channel *sc = bridge_channel->tech_pvt;
        unsigned int channel_read_rate = ast_format_rate(ast_channel_rawreadformat(bridge_channel->chan));
 
        ast_mutex_lock(&sc->lock);
@@ -382,39 +347,89 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch
        ast_mutex_unlock(&sc->lock);
 }
 
+/*!
+ * \internal
+ * \brief Poke the mixing thread in case it is waiting for an active channel.
+ * \since 12.0.0
+ *
+ * \param softmix_data Bridge mixing data.
+ *
+ * \return Nothing
+ */
+static void softmix_poke_thread(struct softmix_bridge_data *softmix_data)
+{
+       ast_mutex_lock(&softmix_data->lock);
+       ast_cond_signal(&softmix_data->cond);
+       ast_mutex_unlock(&softmix_data->lock);
+}
+
+/*! \brief Function called when a channel is unsuspended from the bridge */
+static void softmix_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+       if (bridge->tech_pvt) {
+               softmix_poke_thread(bridge->tech_pvt);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Indicate a source change to the channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel source is changing.
+ *
+ * \return Nothing
+ */
+static void softmix_src_change(struct ast_bridge_channel *bridge_channel)
+{
+       ast_bridge_channel_queue_control_data(bridge_channel, AST_CONTROL_SRCCHANGE, NULL, 0);
+}
+
 /*! \brief Function called when a channel is joined into the bridge */
 static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
        struct softmix_channel *sc;
-       struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
+       struct softmix_bridge_data *softmix_data;
+
+       softmix_data = bridge->tech_pvt;
+       if (!softmix_data) {
+               return -1;
+       }
 
        /* Create a new softmix_channel structure and allocate various things on it */
        if (!(sc = ast_calloc(1, sizeof(*sc)))) {
                return -1;
        }
 
+       softmix_src_change(bridge_channel);
+
        /* Can't forget the lock */
        ast_mutex_init(&sc->lock);
 
        /* Can't forget to record our pvt structure within the bridged channel structure */
-       bridge_channel->bridge_pvt = sc;
+       bridge_channel->tech_pvt = sc;
 
        set_softmix_bridge_data(softmix_data->internal_rate,
-               softmix_data->internal_mixing_interval ? softmix_data->internal_mixing_interval : DEFAULT_SOFTMIX_INTERVAL,
+               softmix_data->internal_mixing_interval
+                       ? softmix_data->internal_mixing_interval
+                       : DEFAULT_SOFTMIX_INTERVAL,
                bridge_channel, 0);
 
+       softmix_poke_thread(softmix_data);
        return 0;
 }
 
 /*! \brief Function called when a channel leaves the bridge */
-static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
-       struct softmix_channel *sc = bridge_channel->bridge_pvt;
+       struct softmix_channel *sc = bridge_channel->tech_pvt;
 
-       if (!(bridge_channel->bridge_pvt)) {
-               return 0;
+       if (!sc) {
+               return;
        }
-       bridge_channel->bridge_pvt = NULL;
+       bridge_channel->tech_pvt = NULL;
+
+       softmix_src_change(bridge_channel);
 
        /* Drop mutex lock */
        ast_mutex_destroy(&sc->lock);
@@ -427,111 +442,122 @@ static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_cha
 
        /* Eep! drop ourselves */
        ast_free(sc);
-
-       return 0;
 }
 
 /*!
  * \internal
- * \brief If the bridging core passes DTMF to us, then they want it to be distributed out to all memebers. Do that here.
+ * \brief Pass the given frame to everyone else.
+ * \since 12.0.0
+ *
+ * \param bridge What bridge to distribute frame.
+ * \param bridge_channel Channel to optionally not pass frame to. (NULL to pass to everyone)
+ * \param frame Frame to pass.
+ *
+ * \return Nothing
  */
-static void softmix_pass_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+static void softmix_pass_everyone_else(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-       struct ast_bridge_channel *tmp;
-       AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
-               if (tmp == bridge_channel) {
+       struct ast_bridge_channel *cur;
+
+       AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+               if (cur == bridge_channel) {
                        continue;
                }
-               ast_write(tmp->chan, frame);
+               ast_bridge_channel_queue_frame(cur, frame);
        }
 }
 
 static void softmix_pass_video_top_priority(struct ast_bridge *bridge, struct ast_frame *frame)
 {
-       struct ast_bridge_channel *tmp;
-       AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
-               if (tmp->suspended) {
+       struct ast_bridge_channel *cur;
+
+       AST_LIST_TRAVERSE(&bridge->channels, cur, entry) {
+               if (cur->suspended) {
                        continue;
                }
-               if (ast_bridge_is_video_src(bridge, tmp->chan) == 1) {
-                       ast_write(tmp->chan, frame);
+               if (ast_bridge_is_video_src(bridge, cur->chan) == 1) {
+                       ast_bridge_channel_queue_frame(cur, frame);
                        break;
                }
        }
 }
 
-static void softmix_pass_video_all(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame, int echo)
+/*!
+ * \internal
+ * \brief Determine what to do with a video frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_video(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-       struct ast_bridge_channel *tmp;
-       AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
-               if (tmp->suspended) {
-                       continue;
+       struct softmix_channel *sc;
+       int video_src_priority;
+
+       /* Determine if the video frame should be distributed or not */
+       switch (bridge->video_mode.mode) {
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+               break;
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
+               if (video_src_priority == 1) {
+                       /* Pass to me and everyone else. */
+                       softmix_pass_everyone_else(bridge, NULL, frame);
                }
-               if ((tmp->chan == bridge_channel->chan) && !echo) {
-                       continue;
+               break;
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               sc = bridge_channel->tech_pvt;
+               ast_mutex_lock(&sc->lock);
+               ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan,
+                       sc->video_talker.energy_average,
+                       ast_format_get_video_mark(&frame->subclass.format));
+               ast_mutex_unlock(&sc->lock);
+               video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
+               if (video_src_priority == 1) {
+                       int num_src = ast_bridge_number_video_src(bridge);
+                       int echo = num_src > 1 ? 0 : 1;
+
+                       softmix_pass_everyone_else(bridge, echo ? NULL : bridge_channel, frame);
+               } else if (video_src_priority == 2) {
+                       softmix_pass_video_top_priority(bridge, frame);
                }
-               ast_write(tmp->chan, frame);
+               break;
        }
 }
 
-/*! \brief Function called when a channel writes a frame into the bridge */
-static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+/*!
+ * \internal
+ * \brief Determine what to do with a voice frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-       struct softmix_channel *sc = bridge_channel->bridge_pvt;
-       struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
+       struct softmix_channel *sc = bridge_channel->tech_pvt;
+       struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
        int totalsilence = 0;
        int cur_energy = 0;
        int silence_threshold = bridge_channel->tech_args.silence_threshold ?
                bridge_channel->tech_args.silence_threshold :
                DEFAULT_SOFTMIX_SILENCE_THRESHOLD;
        char update_talking = -1;  /* if this is set to 0 or 1, tell the bridge that the channel has started or stopped talking. */
-       int res = AST_BRIDGE_WRITE_SUCCESS;
-
-       /* Only accept audio frames, all others are unsupported */
-       if (frame->frametype == AST_FRAME_DTMF_END || frame->frametype == AST_FRAME_DTMF_BEGIN) {
-               softmix_pass_dtmf(bridge, bridge_channel, frame);
-               goto bridge_write_cleanup;
-       } else if (frame->frametype != AST_FRAME_VOICE && frame->frametype != AST_FRAME_VIDEO) {
-               res = AST_BRIDGE_WRITE_UNSUPPORTED;
-               goto bridge_write_cleanup;
-       } else if (frame->datalen == 0) {
-               goto bridge_write_cleanup;
-       }
-
-       /* Determine if this video frame should be distributed or not */
-       if (frame->frametype == AST_FRAME_VIDEO) {
-               int num_src = ast_bridge_number_video_src(bridge);
-               int video_src_priority = ast_bridge_is_video_src(bridge, bridge_channel->chan);
-
-               switch (bridge->video_mode.mode) {
-               case AST_BRIDGE_VIDEO_MODE_NONE:
-                       break;
-               case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-                       if (video_src_priority == 1) {
-                               softmix_pass_video_all(bridge, bridge_channel, frame, 1);
-                       }
-                       break;
-               case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-                       ast_mutex_lock(&sc->lock);
-                       ast_bridge_update_talker_src_video_mode(bridge, bridge_channel->chan, sc->video_talker.energy_average, ast_format_get_video_mark(&frame->subclass.format));
-                       ast_mutex_unlock(&sc->lock);
-                       if (video_src_priority == 1) {
-                               int echo = num_src > 1 ? 0 : 1;
-                               softmix_pass_video_all(bridge, bridge_channel, frame, echo);
-                       } else if (video_src_priority == 2) {
-                               softmix_pass_video_top_priority(bridge, frame);
-                       }
-                       break;
-               }
-               goto bridge_write_cleanup;
-       }
 
-       /* If we made it here, we are going to write the frame into the conference */
+       /* Write the frame into the conference */
        ast_mutex_lock(&sc->lock);
        ast_dsp_silence_with_energy(sc->dsp, frame, &totalsilence, &cur_energy);
 
        if (bridge->video_mode.mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC) {
                int cur_slot = sc->video_talker.energy_history_cur_slot;
+
                sc->video_talker.energy_accum -= sc->video_talker.energy_history[cur_slot];
                sc->video_talker.energy_accum += cur_energy;
                sc->video_talker.energy_history[cur_slot] = cur_energy;
@@ -568,50 +594,77 @@ static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *brid
                ast_slinfactory_feed(&sc->factory, frame);
        }
 
-       /* If a frame is ready to be written out, do so */
-       if (sc->have_frame) {
-               ast_write(bridge_channel->chan, &sc->write_frame);
-               sc->have_frame = 0;
-       }
-
        /* Alllll done */
        ast_mutex_unlock(&sc->lock);
 
        if (update_talking != -1) {
-               ast_bridge_notify_talking(bridge, bridge_channel, update_talking);
+               ast_bridge_notify_talking(bridge_channel, update_talking);
        }
-
-       return res;
-
-bridge_write_cleanup:
-       /* Even though the frame is not being written into the conference because it is not audio,
-        * we should use this opportunity to check to see if a frame is ready to be written out from
-        * the conference to the channel. */
-       ast_mutex_lock(&sc->lock);
-       if (sc->have_frame) {
-               ast_write(bridge_channel->chan, &sc->write_frame);
-               sc->have_frame = 0;
-       }
-       ast_mutex_unlock(&sc->lock);
-
-       return res;
 }
 
-/*! \brief Function called when the channel's thread is poked */
-static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Determine what to do with a control frame.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \return Nothing
+ */
+static void softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
-       struct softmix_channel *sc = bridge_channel->bridge_pvt;
+/* BUGBUG need to look at channel roles to determine what to do with control frame. */
+       /*! \todo BUGBUG softmix_bridge_write_control() not written */
+}
 
-       ast_mutex_lock(&sc->lock);
+/*!
+ * \internal
+ * \brief Determine what to do with a frame written into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Which bridge is getting the frame
+ * \param bridge_channel Which channel is writing the frame.
+ * \param frame What is being written.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \note On entry, bridge is already locked.
+ */
+static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+       int res = 0;
 
-       if (sc->have_frame) {
-               ast_write(bridge_channel->chan, &sc->write_frame);
-               sc->have_frame = 0;
+       if (!bridge->tech_pvt || !bridge_channel->tech_pvt) {
+               return -1;
        }
 
-       ast_mutex_unlock(&sc->lock);
+       switch (frame->frametype) {
+       case AST_FRAME_DTMF_BEGIN:
+       case AST_FRAME_DTMF_END:
+               softmix_pass_everyone_else(bridge, bridge_channel, frame);
+               break;
+       case AST_FRAME_VOICE:
+               softmix_bridge_write_voice(bridge, bridge_channel, frame);
+               break;
+       case AST_FRAME_VIDEO:
+               softmix_bridge_write_video(bridge, bridge_channel, frame);
+               break;
+       case AST_FRAME_CONTROL:
+               softmix_bridge_write_control(bridge, bridge_channel, frame);
+               break;
+       case AST_FRAME_BRIDGE_ACTION:
+               softmix_pass_everyone_else(bridge, bridge_channel, frame);
+               break;
+       default:
+               ast_debug(3, "Frame type %d unsupported\n", frame->frametype);
+               res = -1;
+               break;
+       }
 
-       return 0;
+       return res;
 }
 
 static void gather_softmix_stats(struct softmix_stats *stats,
@@ -648,7 +701,7 @@ static void gather_softmix_stats(struct softmix_stats *stats,
  * \brief Analyse mixing statistics and change bridges internal rate
  * if necessary.
  *
- * \retval 0, no changes to internal rate 
+ * \retval 0, no changes to internal rate
  * \ratval 1, internal rate was changed, update all the channels on the next mixing iteration.
  */
 static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct softmix_bridge_data *softmix_data)
@@ -665,7 +718,8 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
                 * from the current rate we are using. */
                if (softmix_data->internal_rate != stats->locked_rate) {
                        softmix_data->internal_rate = stats->locked_rate;
-                       ast_debug(1, " Bridge is locked in at sample rate %d\n", softmix_data->internal_rate);
+                       ast_debug(1, "Bridge is locked in at sample rate %d\n",
+                               softmix_data->internal_rate);
                        return 1;
                }
        } else if (stats->num_above_internal_rate >= 2) {
@@ -704,13 +758,15 @@ static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct so
                        }
                }
 
-               ast_debug(1, " Bridge changed from %d To %d\n", softmix_data->internal_rate, best_rate);
+               ast_debug(1, "Bridge changed from %d To %d\n",
+                       softmix_data->internal_rate, best_rate);
                softmix_data->internal_rate = best_rate;
                return 1;
        } else if (!stats->num_at_internal_rate && !stats->num_above_internal_rate) {
                /* In this case, the highest supported rate is actually lower than the internal rate */
                softmix_data->internal_rate = stats->highest_supported_rate;
-               ast_debug(1, " Bridge changed from %d to %d\n", softmix_data->internal_rate, stats->highest_supported_rate);
+               ast_debug(1, "Bridge changed from %d to %d\n",
+                       softmix_data->internal_rate, stats->highest_supported_rate);
                return 1;
        }
        return 0;
@@ -745,38 +801,38 @@ static int softmix_mixing_array_grow(struct softmix_mixing_array *mixing_array,
        return 0;
 }
 
-/*! \brief Function which acts as the mixing thread */
-static int softmix_bridge_thread(struct ast_bridge *bridge)
+/*!
+ * \brief Mixing loop.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int softmix_mixing_loop(struct ast_bridge *bridge)
 {
        struct softmix_stats stats = { { 0 }, };
        struct softmix_mixing_array mixing_array;
-       struct softmix_bridge_data *softmix_data;
+       struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
        struct ast_timer *timer;
        struct softmix_translate_helper trans_helper;
        int16_t buf[MAX_DATALEN];
        unsigned int stat_iteration_counter = 0; /* counts down, gather stats at zero and reset. */
        int timingfd;
        int update_all_rates = 0; /* set this when the internal sample rate has changed */
-       int i, x;
+       unsigned int idx;
+       unsigned int x;
        int res = -1;
 
-       softmix_data = bridge->bridge_pvt;
-       if (!softmix_data) {
-               goto softmix_cleanup;
-       }
-
-       ao2_ref(softmix_data, 1);
        timer = softmix_data->timer;
        timingfd = ast_timer_fd(timer);
        softmix_translate_helper_init(&trans_helper, softmix_data->internal_rate);
        ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval));
 
        /* Give the mixing array room to grow, memory is cheap but allocations are expensive. */
-       if (softmix_mixing_array_init(&mixing_array, bridge->num + 10)) {
+       if (softmix_mixing_array_init(&mixing_array, bridge->num_channels + 10)) {
                goto softmix_cleanup;
        }
 
-       while (!bridge->stop && !bridge->refresh && bridge->array_num) {
+       while (!softmix_data->stop && bridge->num_active) {
                struct ast_bridge_channel *bridge_channel;
                int timeout = -1;
                enum ast_format_id cur_slin_id = ast_format_slin_by_rate(softmix_data->internal_rate);
@@ -793,8 +849,8 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
                }
 
                /* Grow the mixing array buffer as participants are added. */
-               if (mixing_array.max_num_entries < bridge->num
-                       && softmix_mixing_array_grow(&mixing_array, bridge->num + 5)) {
+               if (mixing_array.max_num_entries < bridge->num_channels
+                       && softmix_mixing_array_grow(&mixing_array, bridge->num_channels + 5)) {
                        goto softmix_cleanup;
                }
 
@@ -815,7 +871,7 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 
                /* Go through pulling audio from each factory that has it available */
                AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-                       struct softmix_channel *sc = bridge_channel->bridge_pvt;
+                       struct softmix_channel *sc = bridge_channel->tech_pvt;
 
                        /* Update the sample rate to match the bridge's native sample rate if necessary. */
                        if (update_all_rates) {
@@ -842,15 +898,15 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 
                /* mix it like crazy */
                memset(buf, 0, softmix_datalen);
-               for (i = 0; i < mixing_array.used_entries; i++) {
-                       for (x = 0; x < softmix_samples; x++) {
-                               ast_slinear_saturated_add(buf + x, mixing_array.buffers[i] + x);
+               for (idx = 0; idx < mixing_array.used_entries; ++idx) {
+                       for (x = 0; x < softmix_samples; ++x) {
+                               ast_slinear_saturated_add(buf + x, mixing_array.buffers[idx] + x);
                        }
                }
 
                /* Next step go through removing the channel's own audio and creating a good frame... */
                AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-                       struct softmix_channel *sc = bridge_channel->bridge_pvt;
+                       struct softmix_channel *sc = bridge_channel->tech_pvt;
 
                        if (bridge_channel->suspended) {
                                continue;
@@ -869,13 +925,10 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
                        /* process the softmix channel's new write audio */
                        softmix_process_write_audio(&trans_helper, ast_channel_rawwriteformat(bridge_channel->chan), sc);
 
-                       /* The frame is now ready for use... */
-                       sc->have_frame = 1;
-
                        ast_mutex_unlock(&sc->lock);
 
-                       /* Poke bridged channel thread just in case */
-                       pthread_kill(bridge_channel->thread, SIGURG);
+                       /* A frame is now ready for the channel. */
+                       ast_bridge_channel_queue_frame(bridge_channel, &sc->write_frame);
                }
 
                update_all_rates = 0;
@@ -885,17 +938,17 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
                }
                stat_iteration_counter--;
 
-               ao2_unlock(bridge);
+               ast_bridge_unlock(bridge);
                /* cleanup any translation frame data from the previous mixing iteration. */
                softmix_translate_helper_cleanup(&trans_helper);
                /* Wait for the timing source to tell us to wake up and get things done */
                ast_waitfor_n_fd(&timingfd, 1, &timeout, NULL);
                if (ast_timer_ack(timer, 1) < 0) {
                        ast_log(LOG_ERROR, "Failed to acknowledge timer in softmix bridge.\n");
-                       ao2_lock(bridge);
+                       ast_bridge_lock(bridge);
                        goto softmix_cleanup;
                }
-               ao2_lock(bridge);
+               ast_bridge_lock(bridge);
 
                /* make sure to detect mixing interval changes if they occur. */
                if (bridge->internal_mixing_interval && (bridge->internal_mixing_interval != softmix_data->internal_mixing_interval)) {
@@ -910,23 +963,141 @@ static int softmix_bridge_thread(struct ast_bridge *bridge)
 softmix_cleanup:
        softmix_translate_helper_destroy(&trans_helper);
        softmix_mixing_array_destroy(&mixing_array);
-       if (softmix_data) {
-               ao2_ref(softmix_data, -1);
-       }
        return res;
 }
 
+/*!
+ * \internal
+ * \brief Mixing thread.
+ * \since 12.0.0
+ *
+ * \note The thread does not have its own reference to the
+ * bridge.  The lifetime of the thread is tied to the lifetime
+ * of the mixing technology association with the bridge.
+ */
+static void *softmix_mixing_thread(void *data)
+{
+       struct ast_bridge *bridge = data;
+       struct softmix_bridge_data *softmix_data;
+
+       ast_bridge_lock(bridge);
+       if (bridge->callid) {
+               ast_callid_threadassoc_add(bridge->callid);
+       }
+
+       ast_debug(1, "Bridge %s: starting mixing thread\n", bridge->uniqueid);
+
+       softmix_data = bridge->tech_pvt;
+       while (!softmix_data->stop) {
+               if (!bridge->num_active) {
+                       /* Wait for something to happen to the bridge. */
+                       ast_bridge_unlock(bridge);
+                       ast_mutex_lock(&softmix_data->lock);
+                       if (!softmix_data->stop) {
+                               ast_cond_wait(&softmix_data->cond, &softmix_data->lock);
+                       }
+                       ast_mutex_unlock(&softmix_data->lock);
+                       ast_bridge_lock(bridge);
+                       continue;
+               }
+
+               if (softmix_mixing_loop(bridge)) {
+                       /*
+                        * A mixing error occurred.  Sleep and try again later so we
+                        * won't flood the logs.
+                        */
+                       ast_bridge_unlock(bridge);
+                       sleep(1);
+                       ast_bridge_lock(bridge);
+               }
+       }
+
+       ast_bridge_unlock(bridge);
+
+       ast_debug(1, "Bridge %s: stopping mixing thread\n", bridge->uniqueid);
+
+       return NULL;
+}
+
+static void softmix_bridge_data_destroy(struct softmix_bridge_data *softmix_data)
+{
+       if (softmix_data->timer) {
+               ast_timer_close(softmix_data->timer);
+               softmix_data->timer = NULL;
+       }
+       ast_mutex_destroy(&softmix_data->lock);
+       ast_free(softmix_data);
+}
+
+/*! \brief Function called when a bridge is created */
+static int softmix_bridge_create(struct ast_bridge *bridge)
+{
+       struct softmix_bridge_data *softmix_data;
+
+       softmix_data = ast_calloc(1, sizeof(*softmix_data));
+       if (!softmix_data) {
+               return -1;
+       }
+       ast_mutex_init(&softmix_data->lock);
+       softmix_data->timer = ast_timer_open();
+       if (!softmix_data->timer) {
+               softmix_bridge_data_destroy(softmix_data);
+               return -1;
+       }
+       /* start at 8khz, let it grow from there */
+       softmix_data->internal_rate = 8000;
+       softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
+
+       bridge->tech_pvt = softmix_data;
+
+       /* Start the mixing thread. */
+       if (ast_pthread_create(&softmix_data->thread, NULL, softmix_mixing_thread, bridge)) {
+               softmix_data->thread = AST_PTHREADT_NULL;
+               softmix_bridge_data_destroy(softmix_data);
+               bridge->tech_pvt = NULL;
+               return -1;
+       }
+
+       return 0;
+}
+
+/*! \brief Function called when a bridge is destroyed */
+static void softmix_bridge_destroy(struct ast_bridge *bridge)
+{
+       struct softmix_bridge_data *softmix_data;
+       pthread_t thread;
+
+       softmix_data = bridge->tech_pvt;
+       if (!softmix_data) {
+               return;
+       }
+
+       /* Stop the mixing thread. */
+       ast_mutex_lock(&softmix_data->lock);
+       softmix_data->stop = 1;
+       ast_cond_signal(&softmix_data->cond);
+       thread = softmix_data->thread;
+       softmix_data->thread = AST_PTHREADT_NULL;
+       ast_mutex_unlock(&softmix_data->lock);
+       if (thread != AST_PTHREADT_NULL) {
+               ast_debug(1, "Waiting for mixing thread to die.\n");
+               pthread_join(thread, NULL);
+       }
+
+       softmix_bridge_data_destroy(softmix_data);
+       bridge->tech_pvt = NULL;
+}
+
 static struct ast_bridge_technology softmix_bridge = {
        .name = "softmix",
-       .capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED | AST_BRIDGE_CAPABILITY_OPTIMIZE | AST_BRIDGE_CAPABILITY_VIDEO,
-       .preference = AST_BRIDGE_PREFERENCE_LOW,
+       .capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX,
+       .preference = AST_BRIDGE_PREFERENCE_BASE_MULTIMIX,
        .create = softmix_bridge_create,
        .destroy = softmix_bridge_destroy,
        .join = softmix_bridge_join,
        .leave = softmix_bridge_leave,
+       .unsuspend = softmix_bridge_unsuspend,
        .write = softmix_bridge_write,
-       .thread = softmix_bridge_thread,
-       .poke = softmix_bridge_poke,
 };
 
 static int unload_module(void)
index 3fb6891ccb4898e4658fc515855593a39d18baad..d72254ee75d914504ef411d4345caf985ba12898 100644 (file)
@@ -31,7 +31,6 @@
  * \ingroup channel_drivers
  */
 /*** MODULEINFO
-        <depend>chan_local</depend>
         <depend>res_monitor</depend>
        <support_level>core</support_level>
  ***/
@@ -346,6 +345,7 @@ static char *complete_agent_logoff_cmd(const char *line, const char *word, int p
 static struct ast_channel* agent_get_base_channel(struct ast_channel *chan);
 static int agent_logoff(const char *agent, int soft);
 
+/* BUGBUG This channel driver is totally hosed until it is rewritten. */
 /*! \brief Channel interface description for PBX integration */
 static struct ast_channel_tech agent_tech = {
        .type = "Agent",
@@ -2589,5 +2589,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Agent Proxy Channel",
                .unload = unload_module,
                .reload = reload,
                .load_pri = AST_MODPRI_CHANNEL_DRIVER,
-               .nonoptreq = "res_monitor,chan_local",
+               .nonoptreq = "res_monitor",
               );
diff --git a/channels/chan_bridge.c b/channels/chan_bridge.c
deleted file mode 100644 (file)
index 8eac76a..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2008, Digium, Inc.
- *
- * Joshua Colp <jcolp@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \author Joshua Colp <jcolp@digium.com>
- *
- * \brief Bridge Interaction Channel
- *
- * \ingroup channel_drivers
- */
-
-/*** MODULEINFO
-       <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <fcntl.h>
-#include <sys/signal.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/sched.h"
-#include "asterisk/io.h"
-#include "asterisk/acl.h"
-#include "asterisk/callerid.h"
-#include "asterisk/file.h"
-#include "asterisk/cli.h"
-#include "asterisk/app.h"
-#include "asterisk/bridging.h"
-#include "asterisk/astobj2.h"
-
-static struct ast_channel *bridge_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
-static int bridge_call(struct ast_channel *ast, const char *dest, int timeout);
-static int bridge_hangup(struct ast_channel *ast);
-static struct ast_frame *bridge_read(struct ast_channel *ast);
-static int bridge_write(struct ast_channel *ast, struct ast_frame *f);
-static struct ast_channel *bridge_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
-
-static struct ast_channel_tech bridge_tech = {
-       .type = "Bridge",
-       .description = "Bridge Interaction Channel",
-       .requester = bridge_request,
-       .call = bridge_call,
-       .hangup = bridge_hangup,
-       .read = bridge_read,
-       .write = bridge_write,
-       .write_video = bridge_write,
-       .exception = bridge_read,
-       .bridged_channel = bridge_bridgedchannel,
-};
-
-struct bridge_pvt {
-       struct ast_channel *input;  /*!< Input channel - talking to source */
-       struct ast_channel *output; /*!< Output channel - talking to bridge */
-};
-
-/*! \brief Called when the user of this channel wants to get the actual channel in the bridge */
-static struct ast_channel *bridge_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
-{
-       struct bridge_pvt *p = ast_channel_tech_pvt(chan);
-       return (chan == p->input) ? p->output : bridge;
-}
-
-/*! \brief Called when a frame should be read from the channel */
-static struct ast_frame  *bridge_read(struct ast_channel *ast)
-{
-       return &ast_null_frame;
-}
-
-/*! \brief Called when a frame should be written out to a channel */
-static int bridge_write(struct ast_channel *ast, struct ast_frame *f)
-{
-       struct bridge_pvt *p = ast_channel_tech_pvt(ast);
-       struct ast_channel *other = NULL;
-
-       ao2_lock(p);
-       /* only write frames to output. */
-       if (p->input == ast) {
-               other = p->output;
-               if (other) {
-                       ast_channel_ref(other);
-               }
-       }
-       ao2_unlock(p);
-
-       if (other) {
-               ast_channel_unlock(ast);
-               ast_queue_frame(other, f);
-               ast_channel_lock(ast);
-               other = ast_channel_unref(other);
-       }
-
-       return 0;
-}
-
-/*! \brief Called when the channel should actually be dialed */
-static int bridge_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-       struct bridge_pvt *p = ast_channel_tech_pvt(ast);
-
-       /* If no bridge has been provided on the input channel, bail out */
-       if (!ast_channel_internal_bridge(ast)) {
-               return -1;
-       }
-
-       /* Impart the output channel upon the given bridge of the input channel */
-       return ast_bridge_impart(ast_channel_internal_bridge(p->input), p->output, NULL, NULL, 0)
-               ? -1 : 0;
-}
-
-/*! \brief Called when a channel should be hung up */
-static int bridge_hangup(struct ast_channel *ast)
-{
-       struct bridge_pvt *p = ast_channel_tech_pvt(ast);
-
-       if (!p) {
-               return 0;
-       }
-
-       ao2_lock(p);
-       if (p->input == ast) {
-               p->input = NULL;
-       } else if (p->output == ast) {
-               p->output = NULL;
-       }
-       ao2_unlock(p);
-
-       ast_channel_tech_pvt_set(ast, NULL);
-       ao2_ref(p, -1);
-
-       return 0;
-}
-
-/*! \brief Called when we want to place a call somewhere, but not actually call it... yet */
-static struct ast_channel *bridge_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
-{
-       struct bridge_pvt *p = NULL;
-       struct ast_format slin;
-
-       /* Try to allocate memory for our very minimal pvt structure */
-       if (!(p = ao2_alloc(sizeof(*p), NULL))) {
-               return NULL;
-       }
-
-       /* Try to grab two Asterisk channels to use as input and output channels */
-       if (!(p->input = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", requestor ? ast_channel_linkedid(requestor) : NULL, 0, "Bridge/%p-input", p))) {
-               ao2_ref(p, -1);
-               return NULL;
-       }
-       if (!(p->output = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", requestor ? ast_channel_linkedid(requestor) : NULL, 0, "Bridge/%p-output", p))) {
-               p->input = ast_channel_release(p->input);
-               ao2_ref(p, -1);
-               return NULL;
-       }
-
-       /* Setup parameters on both new channels */
-       ast_channel_tech_set(p->input, &bridge_tech);
-       ast_channel_tech_set(p->output, &bridge_tech);
-
-       ao2_ref(p, 2);
-       ast_channel_tech_pvt_set(p->input, p);
-       ast_channel_tech_pvt_set(p->output, p);
-
-       ast_format_set(&slin, AST_FORMAT_SLINEAR, 0);
-
-       ast_format_cap_add(ast_channel_nativeformats(p->input), &slin);
-       ast_format_cap_add(ast_channel_nativeformats(p->output), &slin);
-       ast_format_copy(ast_channel_readformat(p->input), &slin);
-       ast_format_copy(ast_channel_readformat(p->output), &slin);
-       ast_format_copy(ast_channel_rawreadformat(p->input), &slin);
-       ast_format_copy(ast_channel_rawreadformat(p->output), &slin);
-       ast_format_copy(ast_channel_writeformat(p->input), &slin);
-       ast_format_copy(ast_channel_writeformat(p->output), &slin);
-       ast_format_copy(ast_channel_rawwriteformat(p->input), &slin);
-       ast_format_copy(ast_channel_rawwriteformat(p->output), &slin);
-
-       ast_answer(p->output);
-       ast_answer(p->input);
-
-       /* remove the reference from the alloc. The channels now own the pvt. */
-       ao2_ref(p, -1);
-       return p->input;
-}
-
-/*! \brief Load module into PBX, register channel */
-static int load_module(void)
-{
-       if (!(bridge_tech.capabilities = ast_format_cap_alloc())) {
-               return AST_MODULE_LOAD_FAILURE;
-       }
-
-       ast_format_cap_add_all(bridge_tech.capabilities);
-       /* Make sure we can register our channel type */
-       if (ast_channel_register(&bridge_tech)) {
-               ast_log(LOG_ERROR, "Unable to register channel class 'Bridge'\n");
-               return AST_MODULE_LOAD_FAILURE;
-       }
-       return AST_MODULE_LOAD_SUCCESS;
-}
-
-/*! \brief Unload the bridge interaction channel from Asterisk */
-static int unload_module(void)
-{
-       ast_channel_unregister(&bridge_tech);
-       bridge_tech.capabilities = ast_format_cap_destroy(bridge_tech.capabilities);
-       return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Bridge Interaction Channel",
-       .load = load_module,
-       .unload = unload_module,
-       .load_pri = AST_MODPRI_CHANNEL_DRIVER,
-);
index 44fe3f4fb90d10006fac980ec8aeade54f2a3529..1f9ee5a56fda98ec21ed556e040de3482c054470 100644 (file)
@@ -1553,6 +1553,7 @@ static int dahdi_func_write(struct ast_channel *chan, const char *function, char
 static int dahdi_devicestate(const char *data);
 static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback);
 
+/* BUGBUG The DAHDI channel driver needs its own native bridge technology. */
 static struct ast_channel_tech dahdi_tech = {
        .type = "DAHDI",
        .description = tdesc,
index 74859534b1100389dd1b93938d4f843d584295bf..4af137f282f9fc758aca7a174cd2168b7226fd7f 100644 (file)
@@ -134,7 +134,6 @@ static struct ast_channel_tech gulp_tech = {
        .send_text = gulp_sendtext,
        .send_digit_begin = gulp_digit_begin,
        .send_digit_end = gulp_digit_end,
-       .bridge = ast_rtp_instance_bridge,
        .call = gulp_call,
        .hangup = gulp_hangup,
        .answer = gulp_answer,
index f6364a1186ca524e225a42a1b44221a23b3bd105..2476f5065f8dddcecd67b50b4bb7812d74112bb0 100644 (file)
@@ -275,7 +275,6 @@ static struct ast_channel_tech oh323_tech = {
        .write = oh323_write,
        .indicate = oh323_indicate,
        .fixup = oh323_fixup,
-       .bridge = ast_rtp_instance_bridge,
 };
 
 static const char* redirectingreason2str(int redirectingreason)
index 112a99375366b9e2bdf954437a610d3c0a5a46ce..49ce66cb7921d1045c899d614c146bb94c2aa865 100644 (file)
@@ -102,6 +102,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/data.h"
 #include "asterisk/netsock2.h"
 #include "asterisk/security_events.h"
+#include "asterisk/bridging.h"
 
 #include "iax2/include/iax2.h"
 #include "iax2/include/firmware.h"
@@ -1258,6 +1259,7 @@ static void sched_delay_remove(struct sockaddr_in *sin, callno_entry entry);
 static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message);
 static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message);
 
+/* BUGBUG The IAX2 channel driver needs its own native bridge technology. */
 static struct ast_channel_tech iax2_tech = {
        .type = "IAX2",
        .description = tdesc,
@@ -9221,130 +9223,6 @@ static void spawn_dp_lookup(int callno, const char *context, const char *calledn
        }
 }
 
-struct iax_dual {
-       struct ast_channel *chan1;
-       struct ast_channel *chan2;
-       char *park_exten;
-       char *park_context;
-};
-
-static void *iax_park_thread(void *stuff)
-{
-       struct iax_dual *d;
-       int res;
-       int ext = 0;
-
-       d = stuff;
-
-       ast_debug(4, "IAX Park: Transferer channel %s, Transferee %s\n",
-               ast_channel_name(d->chan2), ast_channel_name(d->chan1));
-
-       res = ast_park_call_exten(d->chan1, d->chan2, d->park_exten, d->park_context, 0, &ext);
-       if (res) {
-               /* Parking failed. */
-               ast_hangup(d->chan1);
-       } else {
-               ast_log(LOG_NOTICE, "Parked on extension '%d'\n", ext);
-       }
-       ast_hangup(d->chan2);
-
-       ast_free(d->park_exten);
-       ast_free(d->park_context);
-       ast_free(d);
-       return NULL;
-}
-
-/*! DO NOT hold any locks while calling iax_park */
-static int iax_park(struct ast_channel *chan1, struct ast_channel *chan2, const char *park_exten, const char *park_context)
-{
-       struct iax_dual *d;
-       struct ast_channel *chan1m, *chan2m;/* Chan2m: The transferer, chan1m: The transferee */
-       pthread_t th;
-
-       chan1m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan2), ast_channel_exten(chan1), ast_channel_context(chan1), ast_channel_linkedid(chan1), ast_channel_amaflags(chan1), "Parking/%s", ast_channel_name(chan1));
-       chan2m = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan2), ast_channel_exten(chan2), ast_channel_context(chan2), ast_channel_linkedid(chan2), ast_channel_amaflags(chan2), "IAXPeer/%s", ast_channel_name(chan2));
-       d = ast_calloc(1, sizeof(*d));
-       if (!chan1m || !chan2m || !d) {
-               if (chan1m) {
-                       ast_hangup(chan1m);
-               }
-               if (chan2m) {
-                       ast_hangup(chan2m);
-               }
-               ast_free(d);
-               return -1;
-       }
-       d->park_exten = ast_strdup(park_exten);
-       d->park_context = ast_strdup(park_context);
-       if (!d->park_exten || !d->park_context) {
-               ast_hangup(chan1m);
-               ast_hangup(chan2m);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-
-       /* Make formats okay */
-       ast_format_copy(ast_channel_readformat(chan1m), ast_channel_readformat(chan1));
-       ast_format_copy(ast_channel_writeformat(chan1m), ast_channel_writeformat(chan1));
-
-       /* Prepare for taking over the channel */
-       if (ast_channel_masquerade(chan1m, chan1)) {
-               ast_hangup(chan1m);
-               ast_hangup(chan2m);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-
-       /* Setup the extensions and such */
-       ast_channel_context_set(chan1m, ast_channel_context(chan1));
-       ast_channel_exten_set(chan1m, ast_channel_exten(chan1));
-       ast_channel_priority_set(chan1m, ast_channel_priority(chan1));
-
-       ast_do_masquerade(chan1m);
-
-       /* We make a clone of the peer channel too, so we can play
-          back the announcement */
-
-       /* Make formats okay */
-       ast_format_copy(ast_channel_readformat(chan2m), ast_channel_readformat(chan2));
-       ast_format_copy(ast_channel_writeformat(chan2m), ast_channel_writeformat(chan2));
-       ast_channel_parkinglot_set(chan2m, ast_channel_parkinglot(chan2));
-
-       /* Prepare for taking over the channel */
-       if (ast_channel_masquerade(chan2m, chan2)) {
-               ast_hangup(chan1m);
-               ast_hangup(chan2m);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-
-       /* Setup the extensions and such */
-       ast_channel_context_set(chan2m, ast_channel_context(chan2));
-       ast_channel_exten_set(chan2m, ast_channel_exten(chan2));
-       ast_channel_priority_set(chan2m, ast_channel_priority(chan2));
-
-       ast_do_masquerade(chan2m);
-
-       d->chan1 = chan1m;      /* Transferee */
-       d->chan2 = chan2m;      /* Transferer */
-       if (ast_pthread_create_detached_background(&th, NULL, iax_park_thread, d) < 0) {
-               /* Could not start thread */
-               ast_hangup(chan1m);
-               ast_hangup(chan2m);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-       return 0;
-}
-
 static int check_provisioning(struct sockaddr_in *sin, int sockfd, char *si, unsigned int ver)
 {
        unsigned int ourver;
@@ -10774,56 +10652,28 @@ static int socket_process_helper(struct iax2_thread *thread)
                                break;
                        case IAX_COMMAND_TRANSFER:
                        {
-                               struct ast_channel *bridged_chan;
-                               struct ast_channel *owner;
-
                                iax2_lock_owner(fr->callno);
                                if (!iaxs[fr->callno]) {
                                        /* Initiating call went away before we could transfer. */
                                        break;
                                }
-                               owner = iaxs[fr->callno]->owner;
-                               bridged_chan = owner ? ast_bridged_channel(owner) : NULL;
-                               if (bridged_chan && ies.called_number) {
-                                       const char *context;
-
-                                       context = ast_strdupa(iaxs[fr->callno]->context);
+                               if (iaxs[fr->callno]->owner) {
+                                       struct ast_channel *owner = iaxs[fr->callno]->owner;
+                                       char *context = ast_strdupa(iaxs[fr->callno]->context);
 
                                        ast_channel_ref(owner);
-                                       ast_channel_ref(bridged_chan);
                                        ast_channel_unlock(owner);
                                        ast_mutex_unlock(&iaxsl[fr->callno]);
 
-                                       /* Set BLINDTRANSFER channel variables */
-                                       pbx_builtin_setvar_helper(owner, "BLINDTRANSFER", ast_channel_name(bridged_chan));
-                                       pbx_builtin_setvar_helper(bridged_chan, "BLINDTRANSFER", ast_channel_name(owner));
-
-                                       /* DO NOT hold any locks while calling ast_parking_ext_valid() */
-                                       if (ast_parking_ext_valid(ies.called_number, owner, context)) {
-                                               ast_debug(1, "Parking call '%s'\n", ast_channel_name(bridged_chan));
-                                               if (iax_park(bridged_chan, owner, ies.called_number, context)) {
-                                                       ast_log(LOG_WARNING, "Failed to park call '%s'\n",
-                                                               ast_channel_name(bridged_chan));
-                                               }
-                                       } else {
-                                               if (ast_async_goto(bridged_chan, context, ies.called_number, 1)) {
-                                                       ast_log(LOG_WARNING,
-                                                               "Async goto of '%s' to '%s@%s' failed\n",
-                                                               ast_channel_name(bridged_chan), ies.called_number, context);
-                                               } else {
-                                                       ast_debug(1, "Async goto of '%s' to '%s@%s' started\n",
-                                                               ast_channel_name(bridged_chan), ies.called_number, context);
-                                               }
+                                       if (ast_bridge_transfer_blind(owner, ies.called_number,
+                                                               context, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) {
+                                               ast_log(LOG_WARNING, "Blind transfer of '%s' to '%s@%s' failed\n",
+                                                       ast_channel_name(owner), ies.called_number,
+                                                       context);
                                        }
-                                       ast_channel_unref(owner);
-                                       ast_channel_unref(bridged_chan);
 
+                                       ast_channel_unref(owner);
                                        ast_mutex_lock(&iaxsl[fr->callno]);
-                               } else {
-                                       ast_debug(1, "Async goto not applicable on call %d\n", fr->callno);
-                                       if (owner) {
-                                               ast_channel_unlock(owner);
-                                       }
                                }
 
                                break;
index 717afae6750632bcb39d2b62ccb050ef928a95b1..42dccf666a8083bfc4b3238b75b8f81ac4847e89 100644 (file)
@@ -205,7 +205,6 @@ static struct ast_channel_tech jingle_tech = {
        .send_text = jingle_sendtext,
        .send_digit_begin = jingle_digit_begin,
        .send_digit_end = jingle_digit_end,
-       .bridge = ast_rtp_instance_bridge,
        .call = jingle_call,
        .hangup = jingle_hangup,
        .answer = jingle_answer,
diff --git a/channels/chan_local.c b/channels/chan_local.c
deleted file mode 100644 (file)
index be4586f..0000000
+++ /dev/null
@@ -1,1452 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \brief Local Proxy Channel
- *
- * \ingroup channel_drivers
- */
-
-/*** MODULEINFO
-       <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <fcntl.h>
-#include <sys/signal.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/causes.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/sched.h"
-#include "asterisk/io.h"
-#include "asterisk/acl.h"
-#include "asterisk/callerid.h"
-#include "asterisk/file.h"
-#include "asterisk/cli.h"
-#include "asterisk/app.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/manager.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/devicestate.h"
-#include "asterisk/astobj2.h"
-
-/*** DOCUMENTATION
-       <manager name="LocalOptimizeAway" language="en_US">
-               <synopsis>
-                       Optimize away a local channel when possible.
-               </synopsis>
-               <syntax>
-                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-                       <parameter name="Channel" required="true">
-                               <para>The channel name to optimize away.</para>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>A local channel created with "/n" will not automatically optimize away.
-                       Calling this command on the local channel will clear that flag and allow
-                       it to optimize away if it's bridged or when it becomes bridged.</para>
-               </description>
-       </manager>
- ***/
-
-static const char tdesc[] = "Local Proxy Channel Driver";
-
-#define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
-
-static struct ao2_container *locals;
-
-static unsigned int name_sequence = 0;
-
-static struct ast_jb_conf g_jb_conf = {
-       .flags = 0,
-       .max_size = -1,
-       .resync_threshold = -1,
-       .impl = "",
-       .target_extra = -1,
-};
-
-static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
-static int local_digit_begin(struct ast_channel *ast, char digit);
-static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
-static int local_call(struct ast_channel *ast, const char *dest, int timeout);
-static int local_hangup(struct ast_channel *ast);
-static int local_answer(struct ast_channel *ast);
-static struct ast_frame *local_read(struct ast_channel *ast);
-static int local_write(struct ast_channel *ast, struct ast_frame *f);
-static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
-static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
-static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
-static int local_sendtext(struct ast_channel *ast, const char *text);
-static int local_devicestate(const char *data);
-static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
-static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
-static int local_setoption(struct ast_channel *chan, int option, void *data, int datalen);
-
-/* PBX interface structure for channel registration */
-static struct ast_channel_tech local_tech = {
-       .type = "Local",
-       .description = tdesc,
-       .requester = local_request,
-       .send_digit_begin = local_digit_begin,
-       .send_digit_end = local_digit_end,
-       .call = local_call,
-       .hangup = local_hangup,
-       .answer = local_answer,
-       .read = local_read,
-       .write = local_write,
-       .write_video = local_write,
-       .exception = local_read,
-       .indicate = local_indicate,
-       .fixup = local_fixup,
-       .send_html = local_sendhtml,
-       .send_text = local_sendtext,
-       .devicestate = local_devicestate,
-       .bridged_channel = local_bridgedchannel,
-       .queryoption = local_queryoption,
-       .setoption = local_setoption,
-};
-
-/*!
- * \brief the local pvt structure for all channels
- *
- * The local channel pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
- *
- * ast_chan owner -> local_pvt -> ast_chan chan -> yet-another-pvt-depending-on-channel-type
- */
-struct local_pvt {
-       struct ast_channel *owner;      /*!< Master Channel - Bridging happens here */
-       struct ast_channel *chan;       /*!< Outbound channel - PBX is run here */
-       struct ast_format_cap *reqcap;  /*!< Requested format capabilities */
-       struct ast_jb_conf jb_conf;     /*!< jitterbuffer configuration for this local channel */
-       unsigned int flags;             /*!< Private flags */
-       char context[AST_MAX_CONTEXT];  /*!< Context to call */
-       char exten[AST_MAX_EXTENSION];  /*!< Extension to call */
-};
-
-#define LOCAL_ALREADY_MASQED  (1 << 0) /*!< Already masqueraded */
-#define LOCAL_LAUNCHED_PBX    (1 << 1) /*!< PBX was launched */
-#define LOCAL_NO_OPTIMIZATION (1 << 2) /*!< Do not optimize using masquerading */
-#define LOCAL_BRIDGE          (1 << 3) /*!< Report back the "true" channel as being bridged to */
-#define LOCAL_MOH_PASSTHRU    (1 << 4) /*!< Pass through music on hold start/stop frames */
-
-/*!
- * \brief Send a pvt in with no locks held and get all locks
- *
- * \note NO locks should be held prior to calling this function
- * \note The pvt must have a ref held before calling this function
- * \note if outchan or outowner is set != NULL after calling this function
- *       those channels are locked and reffed.
- * \note Batman.
- */
-static void awesome_locking(struct local_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
-{
-       struct ast_channel *chan = NULL;
-       struct ast_channel *owner = NULL;
-
-       ao2_lock(p);
-       for (;;) {
-               if (p->chan) {
-                       chan = p->chan;
-                       ast_channel_ref(chan);
-               }
-               if (p->owner) {
-                       owner = p->owner;
-                       ast_channel_ref(owner);
-               }
-               ao2_unlock(p);
-
-               /* if we don't have both channels, then this is very easy */
-               if (!owner || !chan) {
-                       if (owner) {
-                               ast_channel_lock(owner);
-                       } else if(chan) {
-                               ast_channel_lock(chan);
-                       }
-               } else {
-                       /* lock both channels first, then get the pvt lock */
-                       ast_channel_lock_both(chan, owner);
-               }
-               ao2_lock(p);
-
-               /* Now that we have all the locks, validate that nothing changed */
-               if (p->owner != owner || p->chan != chan) {
-                       if (owner) {
-                               ast_channel_unlock(owner);
-                               owner = ast_channel_unref(owner);
-                       }
-                       if (chan) {
-                               ast_channel_unlock(chan);
-                               chan = ast_channel_unref(chan);
-                       }
-                       continue;
-               }
-
-               break;
-       }
-       *outowner = p->owner;
-       *outchan = p->chan;
-}
-
-/* Called with ast locked */
-static int local_setoption(struct ast_channel *ast, int option, void *data, int datalen)
-{
-       int res = 0;
-       struct local_pvt *p;
-       struct ast_channel *otherchan = NULL;
-       ast_chan_write_info_t *write_info;
-
-       if (option != AST_OPTION_CHANNEL_WRITE) {
-               return -1;
-       }
-
-       write_info = data;
-
-       if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
-               ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
-               return -1;
-       }
-
-       if (!strcmp(write_info->function, "CHANNEL")
-               && !strncasecmp(write_info->data, "hangup_handler_", 15)) {
-               /* Block CHANNEL(hangup_handler_xxx) writes to the other local channel. */
-               return 0;
-       }
-
-       /* get the tech pvt */
-       if (!(p = ast_channel_tech_pvt(ast))) {
-               return -1;
-       }
-       ao2_ref(p, 1);
-       ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
-
-       /* get the channel we are supposed to write to */
-       ao2_lock(p);
-       otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
-       if (!otherchan || otherchan == write_info->chan) {
-               res = -1;
-               otherchan = NULL;
-               ao2_unlock(p);
-               goto setoption_cleanup;
-       }
-       ast_channel_ref(otherchan);
-
-       /* clear the pvt lock before grabbing the channel */
-       ao2_unlock(p);
-
-       ast_channel_lock(otherchan);
-       res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
-       ast_channel_unlock(otherchan);
-
-setoption_cleanup:
-       ao2_ref(p, -1);
-       if (otherchan) {
-               ast_channel_unref(otherchan);
-       }
-       ast_channel_lock(ast); /* Lock back before we leave */
-       return res;
-}
-
-/*! \brief Adds devicestate to local channels */
-static int local_devicestate(const char *data)
-{
-       char *exten = ast_strdupa(data);
-       char *context;
-       char *opts;
-       int res;
-       struct local_pvt *lp;
-       struct ao2_iterator it;
-
-       /* Strip options if they exist */
-       opts = strchr(exten, '/');
-       if (opts) {
-               *opts = '\0';
-       }
-
-       context = strchr(exten, '@');
-       if (!context) {
-               ast_log(LOG_WARNING,
-                       "Someone used Local/%s somewhere without a @context. This is bad.\n", data);
-               return AST_DEVICE_INVALID;
-       }
-       *context++ = '\0';
-
-       ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
-       res = ast_exists_extension(NULL, context, exten, 1, NULL);
-       if (!res) {
-               return AST_DEVICE_INVALID;
-       }
-
-       res = AST_DEVICE_NOT_INUSE;
-
-       it = ao2_iterator_init(locals, 0);
-       for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
-               int is_inuse;
-
-               ao2_lock(lp);
-               is_inuse = !strcmp(exten, lp->exten)
-                       && !strcmp(context, lp->context)
-                       && lp->owner
-                       && ast_test_flag(lp, LOCAL_LAUNCHED_PBX);
-               ao2_unlock(lp);
-               if (is_inuse) {
-                       res = AST_DEVICE_INUSE;
-                       ao2_ref(lp, -1);
-                       break;
-               }
-       }
-       ao2_iterator_destroy(&it);
-
-       return res;
-}
-
-/*! \brief Return the bridged channel of a Local channel */
-static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(bridge);
-       struct ast_channel *bridged = bridge;
-
-       if (!p) {
-               ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning <none>\n",
-                       ast_channel_name(chan), ast_channel_name(bridge));
-               return NULL;
-       }
-
-       ao2_lock(p);
-
-       if (ast_test_flag(p, LOCAL_BRIDGE)) {
-               /* Find the opposite channel */
-               bridged = (bridge == p->owner ? p->chan : p->owner);
-
-               /* Now see if the opposite channel is bridged to anything */
-               if (!bridged) {
-                       bridged = bridge;
-               } else if (ast_channel_internal_bridged_channel(bridged)) {
-                       bridged = ast_channel_internal_bridged_channel(bridged);
-               }
-       }
-
-       ao2_unlock(p);
-
-       return bridged;
-}
-
-/* Called with ast locked */
-static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
-{
-       struct local_pvt *p;
-       struct ast_channel *bridged = NULL;
-       struct ast_channel *tmp = NULL;
-       int res = 0;
-
-       if (option != AST_OPTION_T38_STATE) {
-               /* AST_OPTION_T38_STATE is the only supported option at this time */
-               return -1;
-       }
-
-       /* for some reason the channel is not locked in channel.c when this function is called */
-       if (!(p = ast_channel_tech_pvt(ast))) {
-               return -1;
-       }
-
-       ao2_lock(p);
-       if (!(tmp = IS_OUTBOUND(ast, p) ? p->owner : p->chan)) {
-               ao2_unlock(p);
-               return -1;
-       }
-       ast_channel_ref(tmp);
-       ao2_unlock(p);
-       ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
-
-       ast_channel_lock(tmp);
-       if (!(bridged = ast_bridged_channel(tmp))) {
-               res = -1;
-               ast_channel_unlock(tmp);
-               goto query_cleanup;
-       }
-       ast_channel_ref(bridged);
-       ast_channel_unlock(tmp);
-
-query_cleanup:
-       if (bridged) {
-               res = ast_channel_queryoption(bridged, option, data, datalen, 0);
-               bridged = ast_channel_unref(bridged);
-       }
-       if (tmp) {
-               tmp = ast_channel_unref(tmp);
-       }
-       ast_channel_lock(ast); /* Lock back before we leave */
-
-       return res;
-}
-
-/*!
- * \brief queue a frame onto either the p->owner or p->chan
- *
- * \note the local_pvt MUST have it's ref count bumped before entering this function and
- * decremented after this function is called.  This is a side effect of the deadlock
- * avoidance that is necessary to lock 2 channels and a tech_pvt.  Without a ref counted
- * local_pvt, it is impossible to guarantee it will not be destroyed by another thread
- * during deadlock avoidance.
- */
-static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f,
-       struct ast_channel *us, int us_locked)
-{
-       struct ast_channel *other = NULL;
-
-       /* Recalculate outbound channel */
-       other = isoutbound ? p->owner : p->chan;
-       if (!other) {
-               return 0;
-       }
-
-       /* do not queue frame if generator is on both local channels */
-       if (us && ast_channel_generator(us) && ast_channel_generator(other)) {
-               return 0;
-       }
-
-       /* grab a ref on the channel before unlocking the pvt,
-        * other can not go away from us now regardless of locking */
-       ast_channel_ref(other);
-       if (us && us_locked) {
-               ast_channel_unlock(us);
-       }
-       ao2_unlock(p);
-
-       if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
-               ast_setstate(other, AST_STATE_RINGING);
-       }
-       ast_queue_frame(other, f);
-
-       other = ast_channel_unref(other);
-       if (us && us_locked) {
-               ast_channel_lock(us);
-       }
-       ao2_lock(p);
-
-       return 0;
-}
-
-static int local_answer(struct ast_channel *ast)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int isoutbound;
-       int res = -1;
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_ref(p, 1);
-       ao2_lock(p);
-       isoutbound = IS_OUTBOUND(ast, p);
-       if (isoutbound) {
-               /* Pass along answer since somebody answered us */
-               struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
-
-               res = local_queue_frame(p, isoutbound, &answer, ast, 1);
-       } else {
-               ast_log(LOG_WARNING, "Huh?  Local is being asked to answer?\n");
-       }
-       ao2_unlock(p);
-       ao2_ref(p, -1);
-       return res;
-}
-
-/*!
- * \internal
- * \note This function assumes that we're only called from the "outbound" local channel side
- *
- * \note it is assummed p is locked and reffed before entering this function
- */
-static void check_bridge(struct ast_channel *ast, struct local_pvt *p)
-{
-       struct ast_channel *owner;
-       struct ast_channel *chan;
-       struct ast_channel *bridged_chan;
-       struct ast_frame *f;
-
-       /* Do a few conditional checks early on just to see if this optimization is possible */
-       if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
-               || !p->chan || !p->owner) {
-               return;
-       }
-
-       /* Safely get the channel bridged to p->chan */
-       chan = ast_channel_ref(p->chan);
-
-       ao2_unlock(p); /* don't call bridged channel with the pvt locked */
-       bridged_chan = ast_bridged_channel(chan);
-       ao2_lock(p);
-
-       chan = ast_channel_unref(chan);
-
-       /* since we had to unlock p to get the bridged chan, validate our
-        * data once again and verify the bridged channel is what we expect
-        * it to be in order to perform this optimization */
-       if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
-               || !p->chan || !p->owner
-               || (ast_channel_internal_bridged_channel(p->chan) != bridged_chan)) {
-               return;
-       }
-
-       /* only do the masquerade if we are being called on the outbound channel,
-          if it has been bridged to another channel and if there are no pending
-          frames on the owner channel (because they would be transferred to the
-          outbound channel during the masquerade)
-       */
-       if (!ast_channel_internal_bridged_channel(p->chan) /* Not ast_bridged_channel!  Only go one step! */
-               || !AST_LIST_EMPTY(ast_channel_readq(p->owner))
-               || ast != p->chan /* Sanity check (should always be false) */) {
-               return;
-       }
-
-       /* Masquerade bridged channel into owner */
-       /* Lock everything we need, one by one, and give up if
-          we can't get everything.  Remember, we'll get another
-          chance in just a little bit */
-       if (ast_channel_trylock(ast_channel_internal_bridged_channel(p->chan))) {
-               return;
-       }
-       if (ast_check_hangup(ast_channel_internal_bridged_channel(p->chan))
-               || ast_channel_trylock(p->owner)) {
-               ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-               return;
-       }
-
-       /*
-        * At this point we have 4 locks:
-        * p, p->chan (same as ast), p->chan->_bridge, p->owner
-        *
-        * Flush a voice or video frame on the outbound channel to make
-        * the queue empty faster so we can get optimized out.
-        */
-       f = AST_LIST_FIRST(ast_channel_readq(p->chan));
-       if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
-               AST_LIST_REMOVE_HEAD(ast_channel_readq(p->chan), frame_list);
-               ast_frfree(f);
-               f = AST_LIST_FIRST(ast_channel_readq(p->chan));
-       }
-
-       if (f
-               || ast_check_hangup(p->owner)
-               || ast_channel_masquerade(p->owner, ast_channel_internal_bridged_channel(p->chan))) {
-               ast_channel_unlock(p->owner);
-               ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-               return;
-       }
-
-       /* Masquerade got setup. */
-       ast_debug(4, "Masquerading %s <- %s\n",
-               ast_channel_name(p->owner),
-               ast_channel_name(ast_channel_internal_bridged_channel(p->chan)));
-       if (ast_channel_monitor(p->owner)
-               && !ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan))) {
-               struct ast_channel_monitor *tmp;
-
-               /* If a local channel is being monitored, we don't want a masquerade
-                * to cause the monitor to go away. Since the masquerade swaps the monitors,
-                * pre-swapping the monitors before the masquerade will ensure that the monitor
-                * ends up where it is expected.
-                */
-               tmp = ast_channel_monitor(p->owner);
-               ast_channel_monitor_set(p->owner, ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan)));
-               ast_channel_monitor_set(ast_channel_internal_bridged_channel(p->chan), tmp);
-       }
-       if (ast_channel_audiohooks(p->chan)) {
-               struct ast_audiohook_list *audiohooks_swapper;
-
-               audiohooks_swapper = ast_channel_audiohooks(p->chan);
-               ast_channel_audiohooks_set(p->chan, ast_channel_audiohooks(p->owner));
-               ast_channel_audiohooks_set(p->owner, audiohooks_swapper);
-       }
-
-       /* If any Caller ID was set, preserve it after masquerade like above. We must check
-        * to see if Caller ID was set because otherwise we'll mistakingly copy info not
-        * set from the dialplan and will overwrite the real channel Caller ID. The reason
-        * for this whole preswapping action is because the Caller ID is set on the channel
-        * thread (which is the to be masqueraded away local channel) before both local
-        * channels are optimized away.
-        */
-       if (ast_channel_caller(p->owner)->id.name.valid || ast_channel_caller(p->owner)->id.number.valid
-               || ast_channel_caller(p->owner)->id.subaddress.valid || ast_channel_caller(p->owner)->ani.name.valid
-               || ast_channel_caller(p->owner)->ani.number.valid || ast_channel_caller(p->owner)->ani.subaddress.valid) {
-               SWAP(*ast_channel_caller(p->owner), *ast_channel_caller(ast_channel_internal_bridged_channel(p->chan)));
-       }
-       if (ast_channel_redirecting(p->owner)->from.name.valid || ast_channel_redirecting(p->owner)->from.number.valid
-               || ast_channel_redirecting(p->owner)->from.subaddress.valid || ast_channel_redirecting(p->owner)->to.name.valid
-               || ast_channel_redirecting(p->owner)->to.number.valid || ast_channel_redirecting(p->owner)->to.subaddress.valid) {
-               SWAP(*ast_channel_redirecting(p->owner), *ast_channel_redirecting(ast_channel_internal_bridged_channel(p->chan)));
-       }
-       if (ast_channel_dialed(p->owner)->number.str || ast_channel_dialed(p->owner)->subaddress.valid) {
-               SWAP(*ast_channel_dialed(p->owner), *ast_channel_dialed(ast_channel_internal_bridged_channel(p->chan)));
-       }
-       ast_app_group_update(p->chan, p->owner);
-       ast_set_flag(p, LOCAL_ALREADY_MASQED);
-
-       ast_channel_unlock(p->owner);
-       ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
-
-       /* Do the masquerade now. */
-       owner = ast_channel_ref(p->owner);
-       ao2_unlock(p);
-       ast_channel_unlock(ast);
-       ast_do_masquerade(owner);
-       ast_channel_unref(owner);
-       ast_channel_lock(ast);
-       ao2_lock(p);
-}
-
-static struct ast_frame  *local_read(struct ast_channel *ast)
-{
-       return &ast_null_frame;
-}
-
-static int local_write(struct ast_channel *ast, struct ast_frame *f)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       int isoutbound;
-
-       if (!p) {
-               return -1;
-       }
-
-       /* Just queue for delivery to the other side */
-       ao2_ref(p, 1); /* ref for local_queue_frame */
-       ao2_lock(p);
-       isoutbound = IS_OUTBOUND(ast, p);
-
-       if (isoutbound
-               && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
-               check_bridge(ast, p);
-       }
-
-       if (!ast_test_flag(p, LOCAL_ALREADY_MASQED)) {
-               res = local_queue_frame(p, isoutbound, f, ast, 1);
-       } else {
-               ast_debug(1, "Not posting to '%s' queue since already masqueraded out\n",
-                       ast_channel_name(ast));
-               res = 0;
-       }
-       ao2_unlock(p);
-       ao2_ref(p, -1);
-
-       return res;
-}
-
-static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(newchan);
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_lock(p);
-
-       if ((p->owner != oldchan) && (p->chan != oldchan)) {
-               ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
-               ao2_unlock(p);
-               return -1;
-       }
-       if (p->owner == oldchan) {
-               p->owner = newchan;
-       } else {
-               p->chan = newchan;
-       }
-
-       /* Do not let a masquerade cause a Local channel to be bridged to itself! */
-       if (!ast_check_hangup(newchan)
-               && ((p->owner && ast_channel_internal_bridged_channel(p->owner) == p->chan)
-                       || (p->chan && ast_channel_internal_bridged_channel(p->chan) == p->owner))) {
-               ast_log(LOG_WARNING, "You can not bridge a Local channel to itself!\n");
-               ao2_unlock(p);
-               ast_queue_hangup(newchan);
-               return -1;
-       }
-
-       ao2_unlock(p);
-       return 0;
-}
-
-static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int res = 0;
-       struct ast_frame f = { AST_FRAME_CONTROL, };
-       int isoutbound;
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_ref(p, 1); /* ref for local_queue_frame */
-
-       /* If this is an MOH hold or unhold, do it on the Local channel versus real channel */
-       if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_HOLD) {
-               ast_moh_start(ast, data, NULL);
-       } else if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_UNHOLD) {
-               ast_moh_stop(ast);
-       } else if (condition == AST_CONTROL_CONNECTED_LINE || condition == AST_CONTROL_REDIRECTING) {
-               struct ast_channel *this_channel;
-               struct ast_channel *the_other_channel;
-
-               /* A connected line update frame may only contain a partial amount of data, such
-                * as just a source, or just a ton, and not the full amount of information. However,
-                * the collected information is all stored in the outgoing channel's connectedline
-                * structure, so when receiving a connected line update on an outgoing local channel,
-                * we need to transmit the collected connected line information instead of whatever
-                * happens to be in this control frame. The same applies for redirecting information, which
-                * is why it is handled here as well.*/
-               ao2_lock(p);
-               isoutbound = IS_OUTBOUND(ast, p);
-               if (isoutbound) {
-                       this_channel = p->chan;
-                       the_other_channel = p->owner;
-               } else {
-                       this_channel = p->owner;
-                       the_other_channel = p->chan;
-               }
-               if (the_other_channel) {
-                       unsigned char frame_data[1024];
-
-                       if (condition == AST_CONTROL_CONNECTED_LINE) {
-                               if (isoutbound) {
-                                       ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel), ast_channel_connected(this_channel));
-                               }
-                               f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data), ast_channel_connected(this_channel), NULL);
-                       } else {
-                               f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data), ast_channel_redirecting(this_channel), NULL);
-                       }
-                       f.subclass.integer = condition;
-                       f.data.ptr = frame_data;
-                       res = local_queue_frame(p, isoutbound, &f, ast, 1);
-               }
-               ao2_unlock(p);
-       } else {
-               /* Queue up a frame representing the indication as a control frame */
-               ao2_lock(p);
-               /*
-                * Block -1 stop tones events if we are to be optimized out.  We
-                * don't need a flurry of these events on a local channel chain
-                * when initially connected to slow the optimization process.
-                */
-               if (0 <= condition || ast_test_flag(p, LOCAL_NO_OPTIMIZATION)) {
-                       isoutbound = IS_OUTBOUND(ast, p);
-                       f.subclass.integer = condition;
-                       f.data.ptr = (void *) data;
-                       f.datalen = datalen;
-                       res = local_queue_frame(p, isoutbound, &f, ast, 1);
-
-                       if (!res && condition == AST_CONTROL_T38_PARAMETERS
-                               && datalen == sizeof(struct ast_control_t38_parameters)) {
-                               const struct ast_control_t38_parameters *parameters = data;
-
-                               if (parameters->request_response == AST_T38_REQUEST_PARMS) {
-                                       res = AST_T38_REQUEST_PARMS;
-                               }
-                       }
-               } else {
-                       ast_debug(4, "Blocked indication %d\n", condition);
-               }
-               ao2_unlock(p);
-       }
-
-       ao2_ref(p, -1);
-       return res;
-}
-
-static int local_digit_begin(struct ast_channel *ast, char digit)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
-       int isoutbound;
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_ref(p, 1); /* ref for local_queue_frame */
-       ao2_lock(p);
-       isoutbound = IS_OUTBOUND(ast, p);
-       f.subclass.integer = digit;
-       res = local_queue_frame(p, isoutbound, &f, ast, 0);
-       ao2_unlock(p);
-       ao2_ref(p, -1);
-
-       return res;
-}
-
-static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       struct ast_frame f = { AST_FRAME_DTMF_END, };
-       int isoutbound;
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_ref(p, 1); /* ref for local_queue_frame */
-       ao2_lock(p);
-       isoutbound = IS_OUTBOUND(ast, p);
-       f.subclass.integer = digit;
-       f.len = duration;
-       res = local_queue_frame(p, isoutbound, &f, ast, 0);
-       ao2_unlock(p);
-       ao2_ref(p, -1);
-
-       return res;
-}
-
-static int local_sendtext(struct ast_channel *ast, const char *text)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       struct ast_frame f = { AST_FRAME_TEXT, };
-       int isoutbound;
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_ref(p, 1); /* ref for local_queue_frame */
-       ao2_lock(p);
-       isoutbound = IS_OUTBOUND(ast, p);
-       f.data.ptr = (char *) text;
-       f.datalen = strlen(text) + 1;
-       res = local_queue_frame(p, isoutbound, &f, ast, 0);
-       ao2_unlock(p);
-       ao2_ref(p, -1);
-       return res;
-}
-
-static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int res = -1;
-       struct ast_frame f = { AST_FRAME_HTML, };
-       int isoutbound;
-
-       if (!p) {
-               return -1;
-       }
-
-       ao2_ref(p, 1); /* ref for local_queue_frame */
-       ao2_lock(p);
-       isoutbound = IS_OUTBOUND(ast, p);
-       f.subclass.integer = subclass;
-       f.data.ptr = (char *)data;
-       f.datalen = datalen;
-       res = local_queue_frame(p, isoutbound, &f, ast, 0);
-       ao2_unlock(p);
-       ao2_ref(p, -1);
-
-       return res;
-}
-
-/*! \brief Initiate new call, part of PBX interface
- *         dest is the dial string */
-static int local_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int pvt_locked = 0;
-
-       struct ast_channel *owner = NULL;
-       struct ast_channel *chan = NULL;
-       int res;
-       struct ast_var_t *varptr;
-       struct ast_var_t *clone_var;
-       char *reduced_dest = ast_strdupa(dest);
-       char *slash;
-       const char *exten;
-       const char *context;
-
-       if (!p) {
-               return -1;
-       }
-
-       /* since we are letting go of channel locks that were locked coming into
-        * this function, then we need to give the tech pvt a ref */
-       ao2_ref(p, 1);
-       ast_channel_unlock(ast);
-
-       awesome_locking(p, &chan, &owner);
-       pvt_locked = 1;
-
-       if (owner != ast) {
-               res = -1;
-               goto return_cleanup;
-       }
-
-       if (!owner || !chan) {
-               res = -1;
-               goto return_cleanup;
-       }
-
-       /*
-        * Note that cid_num and cid_name aren't passed in the ast_channel_alloc
-        * call, so it's done here instead.
-        *
-        * All these failure points just return -1. The individual strings will
-        * be cleared when we destroy the channel.
-        */
-       ast_party_redirecting_copy(ast_channel_redirecting(chan), ast_channel_redirecting(owner));
-
-       ast_party_dialed_copy(ast_channel_dialed(chan), ast_channel_dialed(owner));
-
-       ast_connected_line_copy_to_caller(ast_channel_caller(chan), ast_channel_connected(owner));
-       ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(owner));
-
-       ast_channel_language_set(chan, ast_channel_language(owner));
-       ast_channel_accountcode_set(chan, ast_channel_accountcode(owner));
-       ast_channel_musicclass_set(chan, ast_channel_musicclass(owner));
-       ast_cdr_update(chan);
-
-       ast_channel_cc_params_init(chan, ast_channel_get_cc_config_params(owner));
-
-       /* Make sure we inherit the AST_CAUSE_ANSWERED_ELSEWHERE if it's set on the queue/dial call request in the dialplan */
-       if (ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
-               ast_channel_hangupcause_set(chan, AST_CAUSE_ANSWERED_ELSEWHERE);
-       }
-
-       /* copy the channel variables from the incoming channel to the outgoing channel */
-       /* Note that due to certain assumptions, they MUST be in the same order */
-       AST_LIST_TRAVERSE(ast_channel_varshead(owner), varptr, entries) {
-               clone_var = ast_var_assign(varptr->name, varptr->value);
-               if (clone_var) {
-                       AST_LIST_INSERT_TAIL(ast_channel_varshead(chan), clone_var, entries);
-               }
-       }
-       ast_channel_datastore_inherit(owner, chan);
-       /* If the local channel has /n or /b on the end of it,
-        * we need to lop that off for our argument to setting
-        * up the CC_INTERFACES variable
-        */
-       if ((slash = strrchr(reduced_dest, '/'))) {
-               *slash = '\0';
-       }
-       ast_set_cc_interfaces_chanvar(chan, reduced_dest);
-
-       exten = ast_strdupa(ast_channel_exten(chan));
-       context = ast_strdupa(ast_channel_context(chan));
-
-       ao2_unlock(p);
-       pvt_locked = 0;
-
-       ast_channel_unlock(chan);
-
-       if (!ast_exists_extension(chan, context, exten, 1,
-               S_COR(ast_channel_caller(owner)->id.number.valid, ast_channel_caller(owner)->id.number.str, NULL))) {
-               ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", exten, context);
-               res = -1;
-               chan = ast_channel_unref(chan); /* we already unlocked it, so clear it here so the cleanup label won't touch it. */
-               goto return_cleanup;
-       }
-
-       /*** DOCUMENTATION
-               <managerEventInstance>
-                       <synopsis>Raised when two halves of a Local Channel form a bridge.</synopsis>
-                       <syntax>
-                               <parameter name="Channel1">
-                                       <para>The name of the Local Channel half that bridges to another channel.</para>
-                               </parameter>
-                               <parameter name="Channel2">
-                                       <para>The name of the Local Channel half that executes the dialplan.</para>
-                               </parameter>
-                               <parameter name="Context">
-                                       <para>The context in the dialplan that Channel2 starts in.</para>
-                               </parameter>
-                               <parameter name="Exten">
-                                       <para>The extension in the dialplan that Channel2 starts in.</para>
-                               </parameter>
-                               <parameter name="LocalOptimization">
-                                       <enumlist>
-                                               <enum name="Yes"/>
-                                               <enum name="No"/>
-                                       </enumlist>
-                               </parameter>
-                       </syntax>
-               </managerEventInstance>
-       ***/
-       manager_event(EVENT_FLAG_CALL, "LocalBridge",
-               "Channel1: %s\r\n"
-               "Channel2: %s\r\n"
-               "Uniqueid1: %s\r\n"
-               "Uniqueid2: %s\r\n"
-               "Context: %s\r\n"
-               "Exten: %s\r\n"
-               "LocalOptimization: %s\r\n",
-               ast_channel_name(p->owner), ast_channel_name(p->chan),
-               ast_channel_uniqueid(p->owner), ast_channel_uniqueid(p->chan),
-               p->context, p->exten,
-               ast_test_flag(p, LOCAL_NO_OPTIMIZATION) ? "Yes" : "No");
-
-
-       /* Start switch on sub channel */
-       res = ast_pbx_start(chan);
-       if (!res) {
-               ao2_lock(p);
-               ast_set_flag(p, LOCAL_LAUNCHED_PBX);
-               ao2_unlock(p);
-       }
-       chan = ast_channel_unref(chan); /* chan is already unlocked, clear it here so the cleanup lable won't touch it. */
-
-return_cleanup:
-       if (p) {
-               if (pvt_locked) {
-                       ao2_unlock(p);
-               }
-               ao2_ref(p, -1);
-       }
-       if (chan) {
-               ast_channel_unlock(chan);
-               chan = ast_channel_unref(chan);
-       }
-
-       /* owner is supposed to be == to ast,  if it
-        * is, don't unlock it because ast must exit locked */
-       if (owner) {
-               if (owner != ast) {
-                       ast_channel_unlock(owner);
-                       ast_channel_lock(ast);
-               }
-               owner = ast_channel_unref(owner);
-       } else {
-               /* we have to exit with ast locked */
-               ast_channel_lock(ast);
-       }
-
-       return res;
-}
-
-/*! \brief Hangup a call through the local proxy channel */
-static int local_hangup(struct ast_channel *ast)
-{
-       struct local_pvt *p = ast_channel_tech_pvt(ast);
-       int isoutbound;
-       int hangup_chan = 0;
-       int res = 0;
-       struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_HANGUP }, .data.uint32 = ast_channel_hangupcause(ast) };
-       struct ast_channel *owner = NULL;
-       struct ast_channel *chan = NULL;
-
-       if (!p) {
-               return -1;
-       }
-
-       /* give the pvt a ref since we are unlocking the channel. */
-       ao2_ref(p, 1);
-
-       /* the pvt isn't going anywhere, we gave it a ref */
-       ast_channel_unlock(ast);
-
-       /* lock everything */
-       awesome_locking(p, &chan, &owner);
-
-       if (ast != chan && ast != owner) {
-               res = -1;
-               goto local_hangup_cleanup;
-       }
-
-       isoutbound = IS_OUTBOUND(ast, p); /* just comparing pointer of ast */
-
-       if (p->chan && ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
-               ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
-               ast_debug(2, "This local call has AST_CAUSE_ANSWERED_ELSEWHERE set.\n");
-       }
-
-       if (isoutbound) {
-               const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
-
-               if (status && p->owner) {
-                       ast_channel_hangupcause_set(p->owner, ast_channel_hangupcause(p->chan));
-                       pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
-               }
-
-               ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
-               p->chan = NULL;
-       } else {
-               if (p->chan) {
-                       ast_queue_hangup(p->chan);
-               }
-               p->owner = NULL;
-       }
-
-       ast_channel_tech_pvt_set(ast, NULL); /* this is one of our locked channels, doesn't matter which */
-
-       if (!p->owner && !p->chan) {
-               ao2_unlock(p);
-
-               ao2_unlink(locals, p);
-               ao2_ref(p, -1);
-               p = NULL;
-               res = 0;
-               goto local_hangup_cleanup;
-       }
-       if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX)) {
-               /* Need to actually hangup since there is no PBX */
-               hangup_chan = 1;
-       } else {
-               local_queue_frame(p, isoutbound, &f, NULL, 0);
-       }
-
-local_hangup_cleanup:
-       if (p) {
-               ao2_unlock(p);
-               ao2_ref(p, -1);
-       }
-       if (owner) {
-               ast_channel_unlock(owner);
-               owner = ast_channel_unref(owner);
-       }
-       if (chan) {
-               ast_channel_unlock(chan);
-               if (hangup_chan) {
-                       ast_hangup(chan);
-               }
-               chan = ast_channel_unref(chan);
-       }
-
-       /* leave with the same stupid channel locked that came in */
-       ast_channel_lock(ast);
-       return res;
-}
-
-/*!
- * \internal
- * \brief struct local_pvt destructor.
- *
- * \param vdoomed Void local_pvt to destroy.
- *
- * \return Nothing
- */
-static void local_pvt_destructor(void *vdoomed)
-{
-       struct local_pvt *doomed = vdoomed;
-
-       doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
-
-       ast_module_unref(ast_module_info->self);
-}
-
-/*! \brief Create a call structure */
-static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
-{
-       struct local_pvt *tmp = NULL;
-       char *parse;
-       char *c = NULL;
-       char *opts = NULL;
-
-       if (!(tmp = ao2_alloc(sizeof(*tmp), local_pvt_destructor))) {
-               return NULL;
-       }
-       if (!(tmp->reqcap = ast_format_cap_dup(cap))) {
-               ao2_ref(tmp, -1);
-               return NULL;
-       }
-
-       ast_module_ref(ast_module_info->self);
-
-       /* Initialize private structure information */
-       parse = ast_strdupa(data);
-
-       memcpy(&tmp->jb_conf, &g_jb_conf, sizeof(tmp->jb_conf));
-
-       /* Look for options */
-       if ((opts = strchr(parse, '/'))) {
-               *opts++ = '\0';
-               if (strchr(opts, 'n'))
-                       ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
-               if (strchr(opts, 'j')) {
-                       if (ast_test_flag(tmp, LOCAL_NO_OPTIMIZATION))
-                               ast_set_flag(&tmp->jb_conf, AST_JB_ENABLED);
-                       else {
-                               ast_log(LOG_ERROR, "You must use the 'n' option with the 'j' option to enable the jitter buffer\n");
-                       }
-               }
-               if (strchr(opts, 'b')) {
-                       ast_set_flag(tmp, LOCAL_BRIDGE);
-               }
-               if (strchr(opts, 'm')) {
-                       ast_set_flag(tmp, LOCAL_MOH_PASSTHRU);
-               }
-       }
-
-       /* Look for a context */
-       if ((c = strchr(parse, '@'))) {
-               *c++ = '\0';
-       }
-
-       ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
-       ast_copy_string(tmp->exten, parse, sizeof(tmp->exten));
-
-       ao2_link(locals, tmp);
-
-       return tmp; /* this is returned with a ref */
-}
-
-/*! \brief Start new local channel */
-static struct ast_channel *local_new(struct local_pvt *p, int state, const char *linkedid, struct ast_callid *callid)
-{
-       struct ast_channel *owner;
-       struct ast_channel *chan;
-       struct ast_format fmt;
-       int generated_seqno = ast_atomic_fetchadd_int((int *)&name_sequence, +1);
-
-       /*
-        * Allocate two new Asterisk channels
-        *
-        * Make sure that the ;2 channel gets the same linkedid as ;1.
-        * You can't pass linkedid to both allocations since if linkedid
-        * isn't set, then each channel will generate its own linkedid.
-        */
-       if (!(owner = ast_channel_alloc(1, state, NULL, NULL, NULL,
-                       p->exten, p->context, linkedid, 0,
-                       "Local/%s@%s-%08x;1", p->exten, p->context, generated_seqno))
-               || !(chan = ast_channel_alloc(1, AST_STATE_RING, NULL, NULL, NULL,
-                       p->exten, p->context, ast_channel_linkedid(owner), 0,
-                       "Local/%s@%s-%08x;2", p->exten, p->context, generated_seqno))) {
-               if (owner) {
-                       owner = ast_channel_release(owner);
-               }
-               ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
-               return NULL;
-       }
-
-       if (callid) {
-               ast_channel_callid_set(owner, callid);
-               ast_channel_callid_set(chan, callid);
-       }
-
-       ast_channel_tech_set(owner, &local_tech);
-       ast_channel_tech_set(chan, &local_tech);
-       ast_channel_tech_pvt_set(owner, p);
-       ast_channel_tech_pvt_set(chan, p);
-
-       ast_format_cap_copy(ast_channel_nativeformats(owner), p->reqcap);
-       ast_format_cap_copy(ast_channel_nativeformats(chan), p->reqcap);
-
-       /* Determine our read/write format and set it on each channel */
-       ast_best_codec(p->reqcap, &fmt);
-       ast_format_copy(ast_channel_writeformat(owner), &fmt);
-       ast_format_copy(ast_channel_writeformat(chan), &fmt);
-       ast_format_copy(ast_channel_rawwriteformat(owner), &fmt);
-       ast_format_copy(ast_channel_rawwriteformat(chan), &fmt);
-       ast_format_copy(ast_channel_readformat(owner), &fmt);
-       ast_format_copy(ast_channel_readformat(chan), &fmt);
-       ast_format_copy(ast_channel_rawreadformat(owner), &fmt);
-       ast_format_copy(ast_channel_rawreadformat(chan), &fmt);
-
-       ast_set_flag(ast_channel_flags(owner), AST_FLAG_DISABLE_DEVSTATE_CACHE);
-       ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE);
-
-       p->owner = owner;
-       p->chan = chan;
-
-       ast_jb_configure(owner, &p->jb_conf);
-
-       return owner;
-}
-
-/*! \brief Part of PBX interface */
-static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
-{
-       struct local_pvt *p;
-       struct ast_channel *chan;
-       struct ast_callid *callid = ast_read_threadstorage_callid();
-
-       /* Allocate a new private structure and then Asterisk channel */
-       p = local_alloc(data, cap);
-       if (!p) {
-               chan = NULL;
-               goto local_request_end;
-       }
-       chan = local_new(p, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL, callid);
-       if (!chan) {
-               ao2_unlink(locals, p);
-       } else if (ast_channel_cc_params_init(chan, requestor ? ast_channel_get_cc_config_params((struct ast_channel *)requestor) : NULL)) {
-               ao2_unlink(locals, p);
-               p->owner = ast_channel_release(p->owner);
-               p->chan = ast_channel_release(p->chan);
-               chan = NULL;
-       }
-       ao2_ref(p, -1); /* kill the ref from the alloc */
-
-local_request_end:
-
-       if (callid) {
-               ast_callid_unref(callid);
-       }
-
-       return chan;
-}
-
-/*! \brief CLI command "local show channels" */
-static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       struct local_pvt *p = NULL;
-       struct ao2_iterator it;
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "local show channels";
-               e->usage =
-                       "Usage: local show channels\n"
-                       "       Provides summary information on active local proxy channels.\n";
-               return NULL;
-       case CLI_GENERATE:
-               return NULL;
-       }
-
-       if (a->argc != 3) {
-               return CLI_SHOWUSAGE;
-       }
-
-       if (ao2_container_count(locals) == 0) {
-               ast_cli(a->fd, "No local channels in use\n");
-               return RESULT_SUCCESS;
-       }
-
-       it = ao2_iterator_init(locals, 0);
-       while ((p = ao2_iterator_next(&it))) {
-               ao2_lock(p);
-               ast_cli(a->fd, "%s -- %s@%s\n", p->owner ? ast_channel_name(p->owner) : "<unowned>", p->exten, p->context);
-               ao2_unlock(p);
-               ao2_ref(p, -1);
-       }
-       ao2_iterator_destroy(&it);
-
-       return CLI_SUCCESS;
-}
-
-static struct ast_cli_entry cli_local[] = {
-       AST_CLI_DEFINE(locals_show, "List status of local channels"),
-};
-
-static int manager_optimize_away(struct mansession *s, const struct message *m)
-{
-       const char *channel;
-       struct local_pvt *p;
-       struct local_pvt *found;
-       struct ast_channel *chan;
-
-       channel = astman_get_header(m, "Channel");
-       if (ast_strlen_zero(channel)) {
-               astman_send_error(s, m, "'Channel' not specified.");
-               return 0;
-       }
-
-       chan = ast_channel_get_by_name(channel);
-       if (!chan) {
-               astman_send_error(s, m, "Channel does not exist.");
-               return 0;
-       }
-
-       p = ast_channel_tech_pvt(chan);
-       ast_channel_unref(chan);
-
-       found = p ? ao2_find(locals, p, 0) : NULL;
-       if (found) {
-               ao2_lock(found);
-               ast_clear_flag(found, LOCAL_NO_OPTIMIZATION);
-               ao2_unlock(found);
-               ao2_ref(found, -1);
-               astman_send_ack(s, m, "Queued channel to be optimized away");
-       } else {
-               astman_send_error(s, m, "Unable to find channel");
-       }
-
-       return 0;
-}
-
-
-static int locals_cmp_cb(void *obj, void *arg, int flags)
-{
-       return (obj == arg) ? CMP_MATCH : 0;
-}
-
-/*! \brief Load module into PBX, register channel */
-static int load_module(void)
-{
-       if (!(local_tech.capabilities = ast_format_cap_alloc())) {
-               return AST_MODULE_LOAD_FAILURE;
-       }
-       ast_format_cap_add_all(local_tech.capabilities);
-
-       locals = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, locals_cmp_cb);
-       if (!locals) {
-               ast_format_cap_destroy(local_tech.capabilities);
-               return AST_MODULE_LOAD_FAILURE;
-       }
-
-       /* Make sure we can register our channel type */
-       if (ast_channel_register(&local_tech)) {
-               ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
-               ao2_ref(locals, -1);
-               ast_format_cap_destroy(local_tech.capabilities);
-               return AST_MODULE_LOAD_FAILURE;
-       }
-       ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
-       ast_manager_register_xml("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
-
-       return AST_MODULE_LOAD_SUCCESS;
-}
-
-/*! \brief Unload the local proxy channel from Asterisk */
-static int unload_module(void)
-{
-       struct local_pvt *p = NULL;
-       struct ao2_iterator it;
-
-       /* First, take us out of the channel loop */
-       ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
-       ast_manager_unregister("LocalOptimizeAway");
-       ast_channel_unregister(&local_tech);
-
-       it = ao2_iterator_init(locals, 0);
-       while ((p = ao2_iterator_next(&it))) {
-               if (p->owner) {
-                       ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
-               }
-               ao2_ref(p, -1);
-       }
-       ao2_iterator_destroy(&it);
-       ao2_ref(locals, -1);
-
-       ast_format_cap_destroy(local_tech.capabilities);
-       return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Local Proxy Channel (Note: used internally by other modules)",
-               .load = load_module,
-               .unload = unload_module,
-               .load_pri = AST_MODPRI_CHANNEL_DRIVER,
-       );
index e254823bbabf2b618f4171f1eeed4d939ad51f4c..1f0830762b18ae487f28ff8759b210bc4098d872 100644 (file)
@@ -83,6 +83,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/chanvars.h"
 #include "asterisk/pktccops.h"
 #include "asterisk/stasis.h"
+#include "asterisk/bridging.h"
 
 /*
  * Define to work around buggy dlink MGCP phone firmware which
@@ -480,7 +481,6 @@ static struct ast_channel_tech mgcp_tech = {
        .fixup = mgcp_fixup,
        .send_digit_begin = mgcp_senddigit_begin,
        .send_digit_end = mgcp_senddigit_end,
-       .bridge = ast_rtp_instance_bridge,
        .func_channel_read = acf_channel_read,
 };
 
@@ -3213,56 +3213,55 @@ static void *mgcp_ss(void *data)
        return NULL;
 }
 
-static int attempt_transfer(struct mgcp_endpoint *p)
+/*! \brief Complete an attended transfer
+ *
+ * \param p The endpoint performing the attended transfer
+ * \param sub The sub-channel completing the attended transfer
+ *
+ * \note p->sub is the currently active sub-channel (the channel the phone is using)
+ * \note p->sub->next is the sub-channel not in use, potentially on hold
+ *
+ * \retval 0 when channel should be hung up
+ * \retval 1 when channel should not be hung up
+ */
+static int attempt_transfer(struct mgcp_endpoint *p, struct mgcp_subchannel *sub)
 {
-       /* *************************
-        * I hope this works.
-        * Copied out of chan_zap
-        * Cross your fingers
-        * *************************/
-
-       /* In order to transfer, we need at least one of the channels to
-          actually be in a call bridge.  We can't conference two applications
-          together (but then, why would we want to?) */
-       if (ast_bridged_channel(p->sub->owner)) {
-               /* The three-way person we're about to transfer to could still be in MOH, so
-                  stop it now */
-               ast_queue_control(p->sub->next->owner, AST_CONTROL_UNHOLD);
-               if (ast_channel_state(p->sub->owner) == AST_STATE_RINGING) {
-                       ast_queue_control(p->sub->next->owner, AST_CONTROL_RINGING);
-               }
-               if (ast_channel_masquerade(p->sub->next->owner, ast_bridged_channel(p->sub->owner))) {
-                       ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
-                               ast_channel_name(ast_bridged_channel(p->sub->owner)), ast_channel_name(p->sub->next->owner));
-                       return -1;
-               }
-               /* Orphan the channel */
-               unalloc_sub(p->sub->next);
-       } else if (ast_bridged_channel(p->sub->next->owner)) {
-               if (ast_channel_state(p->sub->owner) == AST_STATE_RINGING) {
-                       ast_queue_control(p->sub->next->owner, AST_CONTROL_RINGING);
-               }
-               ast_queue_control(p->sub->next->owner, AST_CONTROL_UNHOLD);
-               if (ast_channel_masquerade(p->sub->owner, ast_bridged_channel(p->sub->next->owner))) {
-                       ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
-                               ast_channel_name(ast_bridged_channel(p->sub->next->owner)), ast_channel_name(p->sub->owner));
-                       return -1;
+       enum ast_transfer_result res;
+
+       /* Ensure that the other channel goes off hold and that it is indicating properly */
+       ast_queue_control(sub->next->owner, AST_CONTROL_UNHOLD);
+       if (ast_channel_state(sub->owner) == AST_STATE_RINGING) {
+               ast_queue_control(sub->next->owner, AST_CONTROL_RINGING);
+       }
+
+       ast_mutex_unlock(&p->sub->next->lock);
+       ast_mutex_unlock(&p->sub->lock);
+       res = ast_bridge_transfer_attended(sub->owner, sub->next->owner, NULL);
+
+       /* Subs are only freed when the endpoint itself is destroyed, so they will continue to exist
+        * after ast_bridge_transfer_attended returns making this safe without reference counting
+        */
+       ast_mutex_lock(&p->sub->lock);
+       ast_mutex_lock(&p->sub->next->lock);
+
+       if (res != AST_BRIDGE_TRANSFER_SUCCESS) {
+               /* If transferring fails hang up the other channel if present and us */
+               if (sub->next->owner) {
+                       ast_channel_softhangup_internal_flag_add(sub->next->owner, AST_SOFTHANGUP_DEV);
+                       mgcp_queue_hangup(sub->next);
                }
-               /*swap_subs(p, SUB_THREEWAY, SUB_REAL);*/
-               ast_verb(3, "Swapping %d for %d on %s@%s\n", p->sub->id, p->sub->next->id, p->name, p->parent->name);
-               p->sub = p->sub->next;
-               unalloc_sub(p->sub->next);
-               /* Tell the caller not to hangup */
+               sub->next->alreadygone = 1;
+               return 0;
+       }
+
+       unalloc_sub(sub->next);
+
+       /* If the active sub is NOT the one completing the transfer change it to be, and hang up the other sub */
+       if (p->sub != sub) {
+               p->sub = sub;
                return 1;
-       } else {
-               ast_debug(1, "Neither %s nor %s are in a bridge, nothing to transfer\n",
-                       ast_channel_name(p->sub->owner), ast_channel_name(p->sub->next->owner));
-               ast_channel_softhangup_internal_flag_add(p->sub->next->owner, AST_SOFTHANGUP_DEV);
-               if (p->sub->next->owner) {
-                       p->sub->next->alreadygone = 1;
-                       mgcp_queue_hangup(p->sub->next);
-               }
        }
+
        return 0;
 }
 
@@ -3511,13 +3510,8 @@ static int handle_request(struct mgcp_subchannel *sub, struct mgcp_request *req,
                                /* We're allowed to transfer, we have two avtive calls and */
                                /* we made at least one of the calls.  Let's try and transfer */
                                ast_mutex_lock(&p->sub->next->lock);
-                               res = attempt_transfer(p);
-                               if (res < 0) {
-                                       if (p->sub->next->owner) {
-                                               sub->next->alreadygone = 1;
-                                               mgcp_queue_hangup(sub->next);
-                                       }
-                               } else if (res) {
+                               res = attempt_transfer(p, sub);
+                               if (res) {
                                        ast_log(LOG_WARNING, "Transfer attempt failed\n");
                                        ast_mutex_unlock(&p->sub->next->lock);
                                        return -1;
index 2bc6f1e35e2b696eeae9763450aefb8d8f997469..aaa7170b3a9a28c8bb7dd26f338e97c9da3c22b7 100644 (file)
@@ -8091,6 +8091,7 @@ static int misdn_send_text(struct ast_channel *chan, const char *text)
        return 0;
 }
 
+/* BUGBUG The mISDN channel driver needs its own native bridge technology. (More like just never give it one.) */
 static struct ast_channel_tech misdn_tech = {
        .type = misdn_type,
        .description = "Channel driver for mISDN Support (Bri/Pri)",
index 0836972e167581261096b3d104aaacb359f35cb3..56b06b14571172af9aa05b6d88fb2eeddeea01fb 100644 (file)
@@ -360,7 +360,6 @@ static struct ast_channel_tech jingle_tech = {
        .send_text = jingle_sendtext,
        .send_digit_begin = jingle_digit_begin,
        .send_digit_end = jingle_digit_end,
-       .bridge = ast_rtp_instance_bridge,
        .call = jingle_call,
        .hangup = jingle_hangup,
        .answer = jingle_answer,
index 88965fc73a94f01de15f60c534d55ce3023973f4..f7a528b681df4cbdae5b8dea9fcce15f23a67bba 100644 (file)
 /*** MODULEINFO
        <use type="module">res_crypto</use>
        <use type="module">res_http_websocket</use>
-       <depend>chan_local</depend>
        <support_level>core</support_level>
  ***/
 
@@ -295,6 +294,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "sip/include/security_events.h"
 #include "asterisk/sip_api.h"
 #include "asterisk/app.h"
+#include "asterisk/bridging.h"
 #include "asterisk/stasis_endpoints.h"
 
 /*** DOCUMENTATION
@@ -1202,8 +1202,6 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
                                              struct sip_request *req, const char *uri);
 static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag);
 static void check_pendings(struct sip_pvt *p);
-static void *sip_park_thread(void *stuff);
-static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, uint32_t seqno, const char *park_exten, const char *park_context);
 
 static void *sip_pickup_thread(void *stuff);
 static int sip_pickup(struct ast_channel *chan);
@@ -1544,7 +1542,6 @@ struct ast_channel_tech sip_tech = {
        .fixup = sip_fixup,                     /* called with chan locked */
        .send_digit_begin = sip_senddigit_begin,        /* called with chan unlocked */
        .send_digit_end = sip_senddigit_end,
-       .bridge = ast_rtp_instance_bridge,                      /* XXX chan unlocked ? */
        .early_bridge = ast_rtp_instance_early_bridge,
        .send_text = sip_sendtext,              /* called with chan locked */
        .func_channel_read = sip_acf_channel_read,
@@ -24426,160 +24423,6 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
        }
 }
 
-
-/*! \brief Park SIP call support function
-       Starts in a new thread, then parks the call
-       XXX Should we add a wait period after streaming audio and before hangup?? Sometimes the
-               audio can't be heard before hangup
-*/
-static void *sip_park_thread(void *stuff)
-{
-       struct ast_channel *transferee, *transferer;    /* Chan1: The transferee, Chan2: The transferer */
-       struct sip_dual *d;
-       int ext;
-       int res;
-
-       d = stuff;
-       transferee = d->chan1;
-       transferer = d->chan2;
-
-       ast_debug(4, "SIP Park: Transferer channel %s, Transferee %s\n", ast_channel_name(transferer), ast_channel_name(transferee));
-
-       res = ast_park_call_exten(transferee, transferer, d->park_exten, d->park_context, 0, &ext);
-
-       sip_pvt_lock(ast_channel_tech_pvt(transferer));
-#ifdef WHEN_WE_KNOW_THAT_THE_CLIENT_SUPPORTS_MESSAGE
-       if (res) {
-               destroy_msg_headers(ast_channel_tech_pvt(transferer));
-               ast_string_field_set(ast_channel_tech_pvt(transferer), msg_body, "Unable to park call.");
-               transmit_message(ast_channel_tech_pvt(transferer), 0, 0);
-       } else {
-               /* Then tell the transferer what happened */
-               destroy_msg_headers(ast_channel_tech_pvt(transferer));
-               sprintf(buf, "Call parked on extension '%d'.", ext);
-               ast_string_field_set(ast_channel_tech_pvt(transferer), msg_body, buf);
-               transmit_message(ast_channel_tech_pvt(transferer), 0, 0);
-       }
-#endif
-
-       /* Any way back to the current call??? */
-       /* Transmit response to the REFER request */
-       if (!res)       {
-               /* Transfer succeeded */
-               append_history(ast_channel_tech_pvt(transferer), "SIPpark", "Parked call on %d", ext);
-               transmit_notify_with_sipfrag(ast_channel_tech_pvt(transferer), d->seqno, "200 OK", TRUE);
-               sip_pvt_unlock(ast_channel_tech_pvt(transferer));
-               ast_channel_hangupcause_set(transferer, AST_CAUSE_NORMAL_CLEARING);
-               ast_hangup(transferer); /* This will cause a BYE */
-               ast_debug(1, "SIP Call parked on extension '%d'\n", ext);
-       } else {
-               transmit_notify_with_sipfrag(ast_channel_tech_pvt(transferer), d->seqno, "503 Service Unavailable", TRUE);
-               append_history(ast_channel_tech_pvt(transferer), "SIPpark", "Parking failed\n");
-               sip_pvt_unlock(ast_channel_tech_pvt(transferer));
-               ast_debug(1, "SIP Call parked failed \n");
-               /* Do not hangup call */
-       }
-       deinit_req(&d->req);
-       ast_free(d->park_exten);
-       ast_free(d->park_context);
-       ast_free(d);
-       return NULL;
-}
-
-/*! DO NOT hold any locks while calling sip_park */
-static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, uint32_t seqno, const char *park_exten, const char *park_context)
-{
-       struct sip_dual *d;
-       struct ast_channel *transferee, *transferer;
-       pthread_t th;
-
-       transferee = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan1), ast_channel_exten(chan1), ast_channel_context(chan1), ast_channel_linkedid(chan1), ast_channel_amaflags(chan1), "Parking/%s", ast_channel_name(chan1));
-       transferer = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, ast_channel_accountcode(chan2), ast_channel_exten(chan2), ast_channel_context(chan2), ast_channel_linkedid(chan2), ast_channel_amaflags(chan2), "SIPPeer/%s", ast_channel_name(chan2));
-       d = ast_calloc(1, sizeof(*d));
-       if (!transferee || !transferer || !d) {
-               if (transferee) {
-                       ast_hangup(transferee);
-               }
-               if (transferer) {
-                       ast_hangup(transferer);
-               }
-               ast_free(d);
-               return -1;
-       }
-       d->park_exten = ast_strdup(park_exten);
-       d->park_context = ast_strdup(park_context);
-       if (!d->park_exten || !d->park_context) {
-               ast_hangup(transferee);
-               ast_hangup(transferer);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-
-       /* Make formats okay */
-       ast_format_copy(ast_channel_readformat(transferee), ast_channel_readformat(chan1));
-       ast_format_copy(ast_channel_writeformat(transferee), ast_channel_writeformat(chan1));
-
-       /* Prepare for taking over the channel */
-       if (ast_channel_masquerade(transferee, chan1)) {
-               ast_hangup(transferee);
-               ast_hangup(transferer);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-
-       /* Setup the extensions and such */
-       ast_channel_context_set(transferee, ast_channel_context(chan1));
-       ast_channel_exten_set(transferee, ast_channel_exten(chan1));
-       ast_channel_priority_set(transferee, ast_channel_priority(chan1));
-
-       ast_do_masquerade(transferee);
-
-       /* We make a clone of the peer channel too, so we can play
-          back the announcement */
-
-       /* Make formats okay */
-       ast_format_copy(ast_channel_readformat(transferer), ast_channel_readformat(chan2));
-       ast_format_copy(ast_channel_writeformat(transferer), ast_channel_writeformat(chan2));
-       ast_channel_parkinglot_set(transferer, ast_channel_parkinglot(chan2));
-
-       /* Prepare for taking over the channel */
-       if (ast_channel_masquerade(transferer, chan2)) {
-               ast_hangup(transferer);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);
-               return -1;
-       }
-
-       /* Setup the extensions and such */
-       ast_channel_context_set(transferer, ast_channel_context(chan2));
-       ast_channel_exten_set(transferer, ast_channel_exten(chan2));
-       ast_channel_priority_set(transferer, ast_channel_priority(chan2));
-
-       ast_do_masquerade(transferer);
-
-       /* Save original request for followup */
-       copy_request(&d->req, req);
-       d->chan1 = transferee;  /* Transferee */
-       d->chan2 = transferer;  /* Transferer */
-       d->seqno = seqno;
-       if (ast_pthread_create_detached_background(&th, NULL, sip_park_thread, d) < 0) {
-               /* Could not start thread */
-               deinit_req(&d->req);
-               ast_free(d->park_exten);
-               ast_free(d->park_context);
-               ast_free(d);    /* We don't need it anymore. If thread is created, d will be free'd
-                                  by sip_park_thread() */
-               return -1;
-       }
-       return 0;
-}
-
-
 /*! \brief SIP pickup support function
  *     Starts in a new thread, then pickup the call
  */
@@ -26170,6 +26013,10 @@ static void parse_oli(struct sip_request *req, struct ast_channel *chan)
  *     If this function is successful, only the transferer pvt lock will remain on return.  Setting nounlock indicates
  *     to handle_request_do() that the pvt's owner it locked does not require an unlock.
  */
+
+/* XXX XXX XXX XXX XXX XXX
+ * This function is COMPLETELY broken at the moment. It *will* crash if called
+ */
 static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, uint32_t seqno, int *nounlock)
 {
        struct sip_dual target;         /* Chan 1: Call from tranferer to Asterisk */
@@ -26370,6 +26217,44 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *
        return 1;
 }
 
+/*!
+ * Data to set on a channel that runs dialplan
+ * at the completion of a blind transfer
+ */
+struct blind_transfer_cb_data {
+       /*! Contents of the REFER's Referred-by header */
+       const char *referred_by;
+       /*! Domain of the URI in the REFER's Refer-To header */
+       const char *domain;
+       /*! Contents of what to place in a Replaces header of an INVITE */
+       const char *replaces;
+       /*! Redirecting information to set on the channel */
+       struct ast_party_redirecting redirecting;
+       /*! Parts of the redirecting structure that are to be updated */
+       struct ast_set_party_redirecting update_redirecting;
+};
+
+/*!
+ * \internal
+ * \brief Callback called on new outbound channel during blind transfer
+ *
+ * We use this opportunity to populate the channel with data from the REFER
+ * so that, if necessary, we can include proper information on any new INVITE
+ * we may send out.
+ *
+ * \param chan The new outbound channel
+ * \user_data A blind_transfer_cb_data struct
+ */
+static void blind_transfer_cb(struct ast_channel *chan, void *user_data)
+{
+       struct blind_transfer_cb_data *cb_data = user_data;
+
+       pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes");
+       pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REFERER", cb_data->referred_by);
+       pbx_builtin_setvar_helper(chan, "SIPTRANSFER_REPLACES", cb_data->replaces);
+       pbx_builtin_setvar_helper(chan, "SIPDOMAIN", cb_data->domain);
+       ast_channel_update_redirecting(chan, &cb_data->redirecting, &cb_data->update_redirecting);
+}
 
 /*! \brief Handle incoming REFER request */
 /*! \page SIP_REFER SIP transfer Support (REFER)
@@ -26436,22 +26321,13 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *
        */
 static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock)
 {
-       /*!
-        * Chan1: Call between asterisk and transferer
-        * Chan2: Call between asterisk and transferee
-        */
-       struct sip_dual current = { 0, };
-       struct ast_channel *chans[2] = { 0, };
        char *refer_to = NULL;
-       char *refer_to_domain = NULL;
        char *refer_to_context = NULL;
-       char *referred_by = NULL;
-       char *callid = NULL;
-       int localtransfer = 0;
-       int attendedtransfer = 0;
        int res = 0;
-       struct ast_party_redirecting redirecting;
-       struct ast_set_party_redirecting update_redirecting;
+       struct blind_transfer_cb_data cb_data;
+       enum ast_transfer_result transfer_res;
+       RAII_VAR(struct ast_channel *, transferer, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_str *, replaces_str, NULL, ast_free_ptr);
 
        if (req->debug) {
                ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n",
@@ -26469,8 +26345,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
                        sip_alreadygone(p);
                        pvt_set_needdestroy(p, "outside of dialog");
                }
-               res = 0;
-               goto handle_refer_cleanup;
+               return 0;
        }
 
        /* Check if transfer is allowed from this device */
@@ -26479,24 +26354,21 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
                transmit_response(p, "603 Declined (policy)", req);
                append_history(p, "Xfer", "Refer failed. Allowtransfer == closed.");
                /* Do not destroy SIP session */
-               res = 0;
-               goto handle_refer_cleanup;
+               return 0;
        }
 
        if (!req->ignore && ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
                /* Already have a pending REFER */
                transmit_response(p, "491 Request pending", req);
                append_history(p, "Xfer", "Refer failed. Request pending.");
-               res = 0;
-               goto handle_refer_cleanup;
+               return 0;
        }
 
        /* Allocate memory for call transfer data */
        if (!p->refer && !sip_refer_alloc(p)) {
                transmit_response(p, "500 Internal Server Error", req);
                append_history(p, "Xfer", "Refer failed. Memory allocation error.");
-               res = -3;
-               goto handle_refer_cleanup;
+               return -3;
        }
 
        res = get_refer_info(p, req);   /* Extract headers */
@@ -26530,9 +26402,9 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
                        }
                        break;
                }
-               res = 0;
-               goto handle_refer_cleanup;
+               return 0;
        }
+
        if (ast_strlen_zero(p->context)) {
                ast_string_field_set(p, context, sip_cfg.default_context);
        }
@@ -26553,70 +26425,16 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
        /* Is this a repeat of a current request? Ignore it */
        /* Don't know what else to do right now. */
        if (req->ignore) {
-               goto handle_refer_cleanup;
-       }
-
-       /* If this is a blind transfer, we have the following
-       channels to work with:
-       - chan1, chan2: The current call between transferer and transferee (2 channels)
-       - target_channel: A new call from the transferee to the target (1 channel)
-       We need to stay tuned to what happens in order to be able
-       to bring back the call to the transferer */
-
-       /* If this is a attended transfer, we should have all call legs within reach:
-       - chan1, chan2: The call between the transferer and transferee (2 channels)
-       - target_channel, targetcall_pvt: The call between the transferer and the target (2 channels)
-       We want to bridge chan2 with targetcall_pvt!
-
-       The replaces call id in the refer message points
-       to the call leg between Asterisk and the transferer.
-       So we need to connect the target and the transferee channel
-       and hangup the two other channels silently
-
-       If the target is non-local, the call ID could be on a remote
-       machine and we need to send an INVITE with replaces to the
-       target. We basically handle this as a blind transfer
-       and let the sip_call function catch that we need replaces
-       header in the INVITE.
-       */
+               return 0;
+       }
 
        /* Get the transferer's channel */
-       chans[0] = current.chan1 = p->owner;
-
-       /* Find the other part of the bridge (2) - transferee */
-       chans[1] = current.chan2 = ast_bridged_channel(current.chan1);
-
-       ast_channel_ref(current.chan1);
-       if (current.chan2) {
-               ast_channel_ref(current.chan2);
-       }
+       transferer = ast_channel_ref(p->owner);
 
        if (sipdebug) {
-               ast_debug(3, "SIP %s transfer: Transferer channel %s, transferee channel %s\n",
+               ast_debug(3, "SIP %s transfer: Transferer channel %s\n",
                        p->refer->attendedtransfer ? "attended" : "blind",
-                       ast_channel_name(current.chan1),
-                       current.chan2 ? ast_channel_name(current.chan2) : "<none>");
-       }
-
-       if (!current.chan2 && !p->refer->attendedtransfer) {
-               /* No bridged channel, propably IVR or echo or similar... */
-               /* Guess we should masquerade or something here */
-               /* Until we figure it out, refuse transfer of such calls */
-               if (sipdebug) {
-                       ast_debug(3, "Refused SIP transfer on non-bridged channel.\n");
-               }
-               p->refer->status = REFER_FAILED;
-               append_history(p, "Xfer", "Refer failed. Non-bridged channel.");
-               transmit_response(p, "603 Declined", req);
-               res = -1;
-               goto handle_refer_cleanup;
-       }
-
-       if (current.chan2) {
-               if (sipdebug) {
-                       ast_debug(4, "Got SIP transfer, applying to bridged peer '%s'\n", ast_channel_name(current.chan2));
-               }
-               ast_queue_control(current.chan1, AST_CONTROL_UNHOLD);
+                       ast_channel_name(transferer));
        }
 
        ast_set_flag(&p->flags[0], SIP_GOTREFER);
@@ -26627,8 +26445,9 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
        /* Attended transfer: Find all call legs and bridge transferee with target*/
        if (p->refer->attendedtransfer) {
                /* both p and p->owner _MUST_ be locked while calling local_attended_transfer */
-               if ((res = local_attended_transfer(p, &current, req, seqno, nounlock))) {
-                       goto handle_refer_cleanup; /* We're done with the transfer */
+               if ((res = local_attended_transfer(p, NULL, req, seqno, nounlock))) {
+                       ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+                       return res;
                }
                /* Fall through for remote transfers that we did not find locally */
                if (sipdebug) {
@@ -26639,210 +26458,74 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 
        /* Copy data we can not safely access after letting the pvt lock go. */
        refer_to = ast_strdupa(p->refer->refer_to);
-       refer_to_domain = ast_strdupa(p->refer->refer_to_domain);
        refer_to_context = ast_strdupa(p->refer->refer_to_context);
-       referred_by = ast_strdupa(p->refer->referred_by);
-       callid = ast_strdupa(p->callid);
-       localtransfer = p->refer->localtransfer;
-       attendedtransfer = p->refer->attendedtransfer;
-
-       if (!*nounlock) {
-               ast_channel_unlock(p->owner);
-               *nounlock = 1;
-       }
-       sip_pvt_unlock(p);
-
-       /* Parking a call.  DO NOT hold any locks while calling ast_parking_ext_valid() */
-       if (localtransfer && ast_parking_ext_valid(refer_to, current.chan1, refer_to_context)) {
-               sip_pvt_lock(p);
-               ast_clear_flag(&p->flags[0], SIP_GOTREFER);
-               p->refer->status = REFER_200OK;
-               append_history(p, "Xfer", "REFER to call parking.");
-               sip_pvt_unlock(p);
 
-               ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans,
-                       "TransferMethod: SIP\r\n"
-                       "TransferType: Blind\r\n"
-                       "Channel: %s\r\n"
-                       "Uniqueid: %s\r\n"
-                       "SIP-Callid: %s\r\n"
-                       "TargetChannel: %s\r\n"
-                       "TargetUniqueid: %s\r\n"
-                       "TransferExten: %s\r\n"
-                       "Transfer2Parking: Yes\r\n",
-                       ast_channel_name(current.chan1),
-                       ast_channel_uniqueid(current.chan1),
-                       callid,
-                       ast_channel_name(current.chan2),
-                       ast_channel_uniqueid(current.chan2),
-                       refer_to);
+       ast_party_redirecting_init(&cb_data.redirecting);
+       memset(&cb_data.update_redirecting, 0, sizeof(cb_data.update_redirecting));
+       change_redirecting_information(p, req, &cb_data.redirecting, &cb_data.update_redirecting, 0);
 
-               if (sipdebug) {
-                       ast_debug(4, "SIP transfer to parking: trying to park %s. Parked by %s\n", ast_channel_name(current.chan2), ast_channel_name(current.chan1));
-               }
+       cb_data.domain = ast_strdupa(p->refer->refer_to_domain);
+       cb_data.referred_by = ast_strdupa(p->refer->referred_by);
 
-               /* DO NOT hold any locks while calling sip_park */
-               if (sip_park(current.chan2, current.chan1, req, seqno, refer_to, refer_to_context)) {
-                       sip_pvt_lock(p);
-                       transmit_notify_with_sipfrag(p, seqno, "500 Internal Server Error", TRUE);
-               } else {
-                       sip_pvt_lock(p);
+       if (!ast_strlen_zero(p->refer->replaces_callid)) {
+               replaces_str = ast_str_create(128);
+               if (!replaces_str) {
+                       ast_log(LOG_NOTICE, "Unable to create Replaces string for remote attended transfer. Transfer failed\n");
+                       ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+                       ast_party_redirecting_free(&cb_data.redirecting);
+                       return -1;
                }
-               goto handle_refer_cleanup;
-       }
-
-       /* Blind transfers and remote attended xfers.
-        * Locks should not be held while calling pbx_builtin_setvar_helper. This function
-        * locks the channel being passed into it.*/
-       if (current.chan1 && current.chan2) {
-               ast_debug(3, "chan1->name: %s\n", ast_channel_name(current.chan1));
-               pbx_builtin_setvar_helper(current.chan1, "BLINDTRANSFER", ast_channel_name(current.chan2));
+               ast_str_append(&replaces_str, 0, "%s%s%s%s%s", p->refer->replaces_callid,
+                               !ast_strlen_zero(p->refer->replaces_callid_totag) ? ";to-tag=" : "",
+                               S_OR(p->refer->replaces_callid_totag, ""),
+                               !ast_strlen_zero(p->refer->replaces_callid_fromtag) ? ";from-tag=" : "",
+                               S_OR(p->refer->replaces_callid_fromtag, ""));
+               cb_data.replaces = ast_str_buffer(replaces_str);
+       } else {
+               cb_data.replaces = NULL;
        }
 
-       if (current.chan2) {
-               pbx_builtin_setvar_helper(current.chan2, "BLINDTRANSFER", ast_channel_name(current.chan1));
-               pbx_builtin_setvar_helper(current.chan2, "SIPDOMAIN", refer_to_domain);
-               pbx_builtin_setvar_helper(current.chan2, "SIPTRANSFER", "yes");
-               /* One for the new channel */
-               pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER", "yes");
-               /* Attended transfer to remote host, prepare headers for the INVITE */
-               if (!ast_strlen_zero(referred_by)) {
-                       pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REFERER", referred_by);
-               }
-
-               /* When a call is transferred to voicemail from a Digium phone, there may be
-                * a Diversion header present in the REFER with an appropriate reason parameter
-                * set. We need to update the redirecting information appropriately.
-                */
-               ast_channel_lock(p->owner);
-               sip_pvt_lock(p);
-               ast_party_redirecting_init(&redirecting);
-               memset(&update_redirecting, 0, sizeof(update_redirecting));
-               change_redirecting_information(p, req, &redirecting, &update_redirecting, FALSE);
-
-               /* Do not hold the pvt lock during a call that causes an indicate or an async_goto.
-                * Those functions lock channels which will invalidate locking order if the pvt lock
-                * is held.*/
-               sip_pvt_unlock(p);
+       if (!*nounlock) {
                ast_channel_unlock(p->owner);
-               ast_channel_update_redirecting(current.chan2, &redirecting, &update_redirecting);
-               ast_party_redirecting_free(&redirecting);
+               *nounlock = 1;
        }
 
+       sip_pvt_unlock(p);
+       transfer_res = ast_bridge_transfer_blind(transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data);
        sip_pvt_lock(p);
-       /* Generate a Replaces string to be used in the INVITE during attended transfer */
-       if (!ast_strlen_zero(p->refer->replaces_callid)) {
-               char tempheader[SIPBUFSIZE];
-               snprintf(tempheader, sizeof(tempheader), "%s%s%s%s%s", p->refer->replaces_callid,
-                       p->refer->replaces_callid_totag ? ";to-tag=" : "",
-                       p->refer->replaces_callid_totag,
-                       p->refer->replaces_callid_fromtag ? ";from-tag=" : "",
-                       p->refer->replaces_callid_fromtag);
-
-               if (current.chan2) {
-                       sip_pvt_unlock(p);
-                       pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REPLACES", tempheader);
-                       sip_pvt_lock(p);
-               }
-       }
-
-       /* Connect the call */
 
-       /* FAKE ringing if not attended transfer */
-       if (!p->refer->attendedtransfer) {
-               transmit_notify_with_sipfrag(p, seqno, "180 Ringing", FALSE);
-       }
-
-       /* For blind transfer, this will lead to a new call */
-       /* For attended transfer to remote host, this will lead to
-          a new SIP call with a replaces header, if the dial plan allows it
-       */
-       if (!current.chan2) {
-               /* We have no bridge, so we're talking with Asterisk somehow */
-               /* We need to masquerade this call */
-               /* What to do to fix this situation:
-                  * Set up the new call in a new channel
-                  * Let the new channel masq into this channel
-                  Please add that code here :-)
-               */
+       switch (transfer_res) {
+       case AST_BRIDGE_TRANSFER_INVALID:
+               res = -1;
                p->refer->status = REFER_FAILED;
                transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE);
-               ast_clear_flag(&p->flags[0], SIP_GOTREFER);
                append_history(p, "Xfer", "Refer failed (only bridged calls).");
+               break;
+       case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
                res = -1;
-               goto handle_refer_cleanup;
-       }
-       ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);  /* Delay hangup */
-
-       sip_pvt_unlock(p);
-
-       /* For blind transfers, move the call to the new extensions. For attended transfers on multiple
-        * servers - generate an INVITE with Replaces. Either way, let the dial plan decided
-        * indicate before masquerade so the indication actually makes it to the real channel
-        * when using local channels with MOH passthru */
-       ast_indicate(current.chan2, AST_CONTROL_UNHOLD);
-       res = ast_async_goto(current.chan2, refer_to_context, refer_to, 1);
-
-       if (!res) {
-               ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans,
-                       "TransferMethod: SIP\r\n"
-                       "TransferType: Blind\r\n"
-                       "Channel: %s\r\n"
-                       "Uniqueid: %s\r\n"
-                       "SIP-Callid: %s\r\n"
-                       "TargetChannel: %s\r\n"
-                       "TargetUniqueid: %s\r\n"
-                       "TransferExten: %s\r\n"
-                       "TransferContext: %s\r\n",
-                       ast_channel_name(current.chan1),
-                       ast_channel_uniqueid(current.chan1),
-                       callid,
-                       ast_channel_name(current.chan2),
-                       ast_channel_uniqueid(current.chan2),
-                       refer_to,
-                       refer_to_context);
-               /* Success  - we have a new channel */
-               ast_debug(3, "%s transfer succeeded. Telling transferer.\n", attendedtransfer? "Attended" : "Blind");
-
-               /* XXX - what to we put in CEL 'extra' for attended transfers to external systems? NULL for now */
-               ast_channel_lock(current.chan1);
-               ast_cel_report_event(current.chan1, p->refer->attendedtransfer? AST_CEL_ATTENDEDTRANSFER : AST_CEL_BLINDTRANSFER, NULL, p->refer->attendedtransfer ? NULL : p->refer->refer_to, current.chan2);
-               ast_channel_unlock(current.chan1);
-
-               sip_pvt_lock(p);
-               transmit_notify_with_sipfrag(p, seqno, "200 Ok", TRUE);
-               if (p->refer->localtransfer) {
-                       p->refer->status = REFER_200OK;
-               }
-               if (p->owner) {
-                       ast_channel_hangupcause_set(p->owner, AST_CAUSE_NORMAL_CLEARING);
-               }
-               append_history(p, "Xfer", "Refer succeeded.");
-               ast_clear_flag(&p->flags[0], SIP_GOTREFER);
-               /* Do not hangup call, the other side do that when we say 200 OK */
-               /* We could possibly implement a timer here, auto congestion */
-               res = 0;
-       } else {
-               sip_pvt_lock(p);
-               ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);        /* Don't delay hangup */
-               ast_debug(3, "%s transfer failed. Resuming original call.\n", p->refer->attendedtransfer? "Attended" : "Blind");
-               append_history(p, "Xfer", "Refer failed.");
-               /* Failure of some kind */
                p->refer->status = REFER_FAILED;
-               transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable", TRUE);
-               ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+               transmit_notify_with_sipfrag(p, seqno, "403 Forbidden", TRUE);
+               append_history(p, "Xfer", "Refer failed (bridge does not permit transfers)");
+               break;
+       case AST_BRIDGE_TRANSFER_FAIL:
                res = -1;
+               p->refer->status = REFER_FAILED;
+               transmit_notify_with_sipfrag(p, seqno, "500 Internal Server Error", TRUE);
+               append_history(p, "Xfer", "Refer failed (internal error)");
+               break;
+       case AST_BRIDGE_TRANSFER_SUCCESS:
+               res = 0;
+               p->refer->status = REFER_200OK;
+               transmit_notify_with_sipfrag(p, seqno, "200 OK", TRUE);
+               ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER);
+               append_history(p, "Xfer", "Refer succeeded.");
+               break;
+       default:
+               break;
        }
 
-handle_refer_cleanup:
-       if (current.chan1) {
-               ast_channel_unref(current.chan1);
-       }
-       if (current.chan2) {
-               ast_channel_unref(current.chan2);
-       }
-
-       /* Make sure we exit with the pvt locked */
+       ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+       ast_party_redirecting_free(&cb_data.redirecting);
        return res;
 }
 
@@ -32928,7 +32611,7 @@ static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *i
 
        /* Disable early RTP bridge  */
        if ((instance || vinstance || tinstance) &&
-               !ast_bridged_channel(chan) &&
+               !ast_channel_is_bridged(chan) &&
                !sip_cfg.directrtpsetup) {
                sip_pvt_unlock(p);
                ast_channel_unlock(chan);
@@ -35058,5 +34741,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Session Initiation Pr
                .unload = unload_module,
                .reload = reload,
                .load_pri = AST_MODPRI_CHANNEL_DRIVER,
-               .nonoptreq = "res_crypto,chan_local,res_http_websocket",
+               .nonoptreq = "res_crypto,res_http_websocket",
               );
index b3951d5e48294913b89553e018703d9a77222139..cd194d5a8e0f43d9bbcd71128fc0fbf42abae715 100644 (file)
@@ -1653,7 +1653,6 @@ static struct ast_channel_tech skinny_tech = {
        .fixup = skinny_fixup,
        .send_digit_begin = skinny_senddigit_begin,
        .send_digit_end = skinny_senddigit_end,
-       .bridge = ast_rtp_instance_bridge,
 };
 
 static int skinny_extensionstate_cb(char *context, char *id, struct ast_state_cb_info *info, void *data);
index dea796364564947c042e94330e8fb3435c0d8aa9..121e2f0b1530368502d3b8c19c6ea8603cda2b61 100644 (file)
@@ -708,7 +708,6 @@ static struct ast_channel_tech unistim_tech = {
        .send_digit_begin = unistim_senddigit_begin,
        .send_digit_end = unistim_senddigit_end,
        .send_text = unistim_sendtext,
-       .bridge = ast_rtp_instance_bridge,
 };
 
 static void send_start_rtp(struct unistim_subchannel *);
@@ -5826,7 +5825,6 @@ static char *unistim_show_info(struct ast_cli_entry *e, int cmd, struct ast_cli_
        struct unistim_line *line;
        struct unistim_subchannel *sub;
        struct unistimsession *s;
-       struct ast_channel *tmp;
 
        switch (cmd) {
        case CLI_INIT:
@@ -5870,14 +5868,9 @@ static char *unistim_show_info(struct ast_cli_entry *e, int cmd, struct ast_cli_
                        if (!sub) {
                                continue;
                        }
-                       if (!sub->owner) {
-                               tmp = (void *) -42;
-                       } else {
-                               tmp = ast_channel_internal_bridged_channel(sub->owner);
-                       }
                        ast_cli(a->fd,
-                                       "-->subtype=%s chan=%p rtp=%p bridge=%p line=%p alreadygone=%d softkey=%d\n",
-                                       subtype_tostr(sub->subtype), sub->owner, sub->rtp, tmp, sub->parent,
+                                       "-->subtype=%s chan=%p rtp=%p line=%p alreadygone=%d softkey=%d\n",
+                                       subtype_tostr(sub->subtype), sub->owner, sub->rtp, sub->parent,
                                        sub->alreadygone, sub->softkey);
                }
                AST_LIST_UNLOCK(&device->subs);
index 58fc73f699ce4c1ebccf9b24112ab78d9906396d..d1747cb066f9905dfc1bebb59980152d98b078b0 100644 (file)
@@ -2267,13 +2267,7 @@ static void *do_chanreads(void *pvt)
                        else 
                                bridgerec = 0;
                } else {
-                       ast_verb(5, "%s: chanreads: No native bridge.\n", p->dev);
-                       if (ast_channel_internal_bridged_channel(p->owner)) {
-                               ast_verb(5, "%s: chanreads: Got Asterisk bridge with [%s].\n", p->dev, ast_channel_name(ast_channel_internal_bridged_channel(p->owner)));
-                               bridgerec = 1;
-                       } else {
-                               bridgerec = 0;
-                       }
+                       bridgerec = ast_channel_is_bridged(p->owner) ? 1 : 0;
                }
 
 /*             if ((p->owner->_state != AST_STATE_UP) || !bridgerec) */
index 701ccdf37f6d26a1da5d9e345dd06d91d35938d8..12fb3151c9c00c85a065ed4381e679db729d23f7 100644 (file)
@@ -157,7 +157,7 @@ context => parkedcalls          ; Which context parked calls are in (default par
 ;
 ;    Set(__DYNAMIC_FEATURES=myfeature1#myfeature2#myfeature3)
 ;
-; (Note: The two leading underscores allow these feature settings to be set on
+; (Note: The two leading underscores allow these feature settings to be set
 ;  on the outbound channels, as well.  Otherwise, only the original channel
 ;  will have access to these features.)
 ;
@@ -176,10 +176,10 @@ context => parkedcalls          ; Which context parked calls are in (default par
 ;                   application on the same channel that activated the feature. "peer"
 ;                   means run the application on the opposite channel from the one that
 ;                   has activated the feature.
-;  ActivatedBy   -> This is which channel is allowed to activate this feature. Valid
-;                   values are "caller", "callee", and "both". "both" is the default.
-;                   The "caller" is the channel that executed the Dial application, while
-;                   the "callee" is the channel called by the Dial application.
+;  ActivatedBy   -> ActivatedBy is no longer honored.  The feature is activated by which
+;                   channel DYNAMIC_FEATURES includes the feature is on.  Use predial
+;                   to set different values of DYNAMIC_FEATURES on the channels.
+;                   Historic values are: "caller", "callee", and "both".
 ;  Application   -> This is the application to execute.
 ;  AppArguments  -> These are the arguments to be passed into the application.  If you need
 ;                   commas in your arguments, you should use either the second or third
@@ -194,8 +194,9 @@ context => parkedcalls          ; Which context parked calls are in (default par
 ;   applications. When applications are used in extensions.conf, they are executed
 ;   by the PBX core. In this case, these applications are executed outside of the
 ;   PBX core, so it does *not* make sense to use any application which has any
-;   concept of dialplan flow. Examples of this would be things like Macro, Goto,
-;   Background, WaitExten, and many more.
+;   concept of dialplan flow. Examples of this would be things like Goto,
+;   Background, WaitExten, and many more.  The exceptions to this are Gosub and
+;   Macro routines which must complete for the call to continue.
 ;
 ; Enabling these features means that the PBX needs to stay in the media flow and
 ; media will not be re-directed if DTMF is sent in the media stream.
diff --git a/configs/res_parking.conf.sample b/configs/res_parking.conf.sample
new file mode 100644 (file)
index 0000000..b8308e6
--- /dev/null
@@ -0,0 +1,48 @@
+[general]
+;parkeddynamic = yes        ; Enables dynamically created parkinglots. (default is no)
+
+
+; A parking lot named 'default' will automatically be used when no other
+; named parking lot is indicated for use by the park application or a
+; channel's parkinglot function and PARKINGLOT channel variable.
+
+[default]                                 ; based on the old default from features.conf.sample
+parkext => 700
+;parkext_exclusive=yes
+parkpos => 701-720
+context => parkedcalls
+;parkinghints = no
+;parkingtime => 45
+;comebacktoorigin = yes
+;comebackdialtime = 30
+;comebackcontext = parkedcallstimeout
+;courtesytone = beep
+;parkedplay = caller
+;parkedcalltransfers = caller
+;parkedcallreparking = caller
+;parkedcallhangup = caller
+;findslot => next
+;parkedmusicclass = default
+
+; Parking lots can now be any named configuration category aside from
+; 'general' which is reserved for general options.
+;
+; You can set parkinglot with the CHANNEL dialplan function or by setting
+; 'parkinglot' directly in the channel configuration file.
+;
+; (Note: Leading '0's and any non-numerical characters on parkpos
+; extensions will be ignored. Parkext on the other hand can be any string.)
+;
+;[edvina2]
+;context => edvina2_park
+;parkpos => 800-850
+;findslot => next
+;comebacktoorigin = no
+;comebackdialtime = 90
+;comebackcontext = edvina2_park-timeout
+;parkedmusicclass = edvina
+;
+; Since edvina2 doesn't define parkext, extensions won't automatically be
+; created for parking to it or for retrieving calls from it. These can be
+; created manually in the dial plan by using the Park and ParkedCall
+; applications.
index db9434d834a433cc7c602ee125ea53c84dca225f..93792440074295aebeb9082f5348d69ecd1a89d5 100644 (file)
@@ -343,6 +343,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
        </function>
  ***/
 
+/*
+ * BUGBUG add CHANNEL(after_bridge_goto)=<parseable-goto> Sets an after bridge goto datastore property on the channel.
+ * CHANNEL(after_bridge_goto)=<empty> Deletes any after bridge goto datastore property on the channel.
+ *
+ * BUGBUG add CHANNEL(dtmf_features)=tkhwx sets channel dtmf features to specified. (transfer, park, hangup, monitor, mixmonitor)
+ */
+
 #define locked_copy_string(chan, dest, source, len) \
        do { \
                ast_channel_lock(chan); \
index 6410d12b80211d56e025706abae1bb270e0cc70e..94a2c13d0829d64005c86de3157abfd3f17e1945 100644 (file)
@@ -376,6 +376,10 @@ static void print_frame(struct ast_frame *frame)
                ast_verbose("Digit: 0x%02X '%c'\n", frame->subclass.integer,
                        frame->subclass.integer < ' ' ? ' ' : frame->subclass.integer);
                break;
+       case AST_FRAME_BRIDGE_ACTION:
+               ast_verbose("FrameType: Bridge\n");
+               ast_verbose("SubClass: %d\n", frame->subclass.integer);
+               break;
        }
 
        ast_verbose("Src: %s\n", ast_strlen_zero(frame->src) ? "NOT PRESENT" : frame->src);
index 066d9d2f626ec9c8b1e6dd0c98409eb11b5a3f7b..a00361043c2ac6210f0a8b9975189d582e8c36bc 100644 (file)
@@ -36,6 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
 #include "asterisk/framehook.h"
+#include "asterisk/frame.h"
 #include "asterisk/pbx.h"
 #include "asterisk/abstract_jb.h"
 #include "asterisk/timing.h"
@@ -48,7 +49,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </synopsis>
                <syntax>
                        <parameter name="jitterbuffer type" required="true">
-                               <para>Jitterbuffer type can be either <literal>fixed</literal> or <literal>adaptive</literal>.</para>
+                               <para>Jitterbuffer type can be <literal>fixed</literal>, <literal>adaptive</literal>, or
+                                       <literal>disabled</literal>.</para>
                                <para>Used as follows. </para>
                                <para>Set(JITTERBUFFER(type)=max_size[,resync_threshold[,target_extra]])</para>
                                <para>Set(JITTERBUFFER(type)=default) </para>
@@ -70,85 +72,31 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>exten => 1,1,Set(JITTERBUFFER(fixed)=200,1500);Fixed with max size 200ms resync threshold 1500. </para>
                        <para>exten => 1,1,Set(JITTERBUFFER(adaptive)=default);Adaptive with defaults. </para>
                        <para>exten => 1,1,Set(JITTERBUFFER(adaptive)=200,,60);Adaptive with max size 200ms, default resync threshold and 40ms target extra. </para>
+                       <para>exten => 1,n,Set(JITTERBUFFER(disabled)=);Remove previously applied jitterbuffer </para>
+                       <note><para>If a channel specifies a jitterbuffer due to channel driver configuration and
+                       the JITTERBUFFER function has set a jitterbuffer for that channel, the jitterbuffer set by
+                       the JITTERBUFFER function will take priority and the jitterbuffer set by the channel
+                       configuration will not be applied.</para></note>
                </description>
        </function>
  ***/
 
-#define DEFAULT_TIMER_INTERVAL 20
-#define DEFAULT_SIZE  200
-#define DEFAULT_TARGET_EXTRA  40
-#define DEFAULT_RESYNC  1000
-#define DEFAULT_TYPE AST_JB_FIXED
-
-struct jb_framedata {
-       const struct ast_jb_impl *jb_impl;
-       struct ast_jb_conf jb_conf;
-       struct timeval start_tv;
-       struct ast_format last_format;
-       struct ast_timer *timer;
-       int timer_interval; /* ms between deliveries */
-       int timer_fd;
-       int first;
-       void *jb_obj;
-};
-
-static void jb_framedata_destroy(struct jb_framedata *framedata)
-{
-       if (framedata->timer) {
-               ast_timer_close(framedata->timer);
-               framedata->timer = NULL;
-       }
-       if (framedata->jb_impl && framedata->jb_obj) {
-               struct ast_frame *f;
-               while (framedata->jb_impl->remove(framedata->jb_obj, &f) == AST_JB_IMPL_OK) {
-                       ast_frfree(f);
-               }
-               framedata->jb_impl->destroy(framedata->jb_obj);
-               framedata->jb_obj = NULL;
-       }
-       ast_free(framedata);
-}
-
-static void jb_conf_default(struct ast_jb_conf *conf)
-{
-       conf->max_size = DEFAULT_SIZE;
-       conf->resync_threshold = DEFAULT_RESYNC;
-       ast_copy_string(conf->impl, "fixed", sizeof(conf->impl));
-       conf->target_extra = DEFAULT_TARGET_EXTRA;
-}
-
-/* set defaults */
-static int jb_framedata_init(struct jb_framedata *framedata, const char *data, const char *value)
+static int jb_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value)
 {
-       int jb_impl_type = DEFAULT_TYPE;
-
-       /* Initialize defaults */
-       framedata->timer_fd = -1;
-       jb_conf_default(&framedata->jb_conf);
-       if (!(framedata->jb_impl = ast_jb_get_impl(jb_impl_type))) {
-               return -1;
-       }
-       if (!(framedata->timer = ast_timer_open())) {
-               return -1;
-       }
-       framedata->timer_fd = ast_timer_fd(framedata->timer);
-       framedata->timer_interval = DEFAULT_TIMER_INTERVAL;
-       ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
-       framedata->start_tv = ast_tvnow();
-
+       struct ast_jb_conf jb_conf;
 
+       /* Initialize and set jb_conf */
+       ast_jb_conf_default(&jb_conf);
 
        /* Now check user options to see if any of the defaults need to change. */
        if (!ast_strlen_zero(data)) {
-               if (!strcasecmp(data, "fixed")) {
-                       jb_impl_type = AST_JB_FIXED;
-               } else if (!strcasecmp(data, "adaptive")) {
-                       jb_impl_type = AST_JB_ADAPTIVE;
-               } else {
+               if (strcasecmp(data, "fixed") &&
+                               strcasecmp(data, "adaptive") &&
+                               strcasecmp(data, "disabled")) {
                        ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", data);
                        return -1;
                }
-               ast_copy_string(framedata->jb_conf.impl, data, sizeof(framedata->jb_conf.impl));
+               ast_copy_string(jb_conf.impl, data, sizeof(jb_conf.impl));
        }
 
        if (!ast_strlen_zero(value) && strcasecmp(value, "default")) {
@@ -162,17 +110,17 @@ static int jb_framedata_init(struct jb_framedata *framedata, const char *data, c
 
                AST_STANDARD_APP_ARGS(args, parse);
                if (!ast_strlen_zero(args.max_size)) {
-                       res |= ast_jb_read_conf(&framedata->jb_conf,
+                       res |= ast_jb_read_conf(&jb_conf,
                                "jbmaxsize",
                                args.max_size);
                }
                if (!ast_strlen_zero(args.resync_threshold)) {
-                       res |= ast_jb_read_conf(&framedata->jb_conf,
+                       res |= ast_jb_read_conf(&jb_conf,
                                "jbresyncthreshold",
                                args.resync_threshold);
                }
                if (!ast_strlen_zero(args.target_extra)) {
-                       res |= ast_jb_read_conf(&framedata->jb_conf,
+                       res |= ast_jb_read_conf(&jb_conf,
                                "jbtargetextra",
                                args.target_extra);
                }
@@ -181,198 +129,12 @@ static int jb_framedata_init(struct jb_framedata *framedata, const char *data, c
                }
        }
 
-       /* now that all the user parsing is done and nothing will change, create the jb obj */
-       framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf);
-       return 0;
-}
-
-static void datastore_destroy_cb(void *data) {
-       ast_free(data);
-       ast_debug(1, "JITTERBUFFER datastore destroyed\n");
-}
-
-static const struct ast_datastore_info jb_datastore = {
-       .type = "jitterbuffer",
-       .destroy = datastore_destroy_cb
-};
-
-static void hook_destroy_cb(void *framedata)
-{
-       ast_debug(1, "JITTERBUFFER hook destroyed\n");
-       jb_framedata_destroy((struct jb_framedata *) framedata);
-}
-
-static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data)
-{
-       struct jb_framedata *framedata = data;
-       struct timeval now_tv;
-       unsigned long now;
-       int putframe = 0; /* signifies if audio frame was placed into the buffer or not */
-
-       switch (event) {
-       case AST_FRAMEHOOK_EVENT_READ:
-               break;
-       case AST_FRAMEHOOK_EVENT_ATTACHED:
-       case AST_FRAMEHOOK_EVENT_DETACHED:
-       case AST_FRAMEHOOK_EVENT_WRITE:
-               return frame;
-       }
-
-       if (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) {
-               if (ast_timer_ack(framedata->timer, 1) < 0) {
-                       ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n");
-                       return frame;
-               }
-       }
-
-       if (!frame) {
-               return frame;
-       }
-
-       now_tv = ast_tvnow();
-       now = ast_tvdiff_ms(now_tv, framedata->start_tv);
-
-       if (frame->frametype == AST_FRAME_VOICE) {
-               int res;
-               struct ast_frame *jbframe;
-
-               if (!ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO) || frame->len < 2 || frame->ts < 0) {
-                       /* only frames with timing info can enter the jitterbuffer */
-                       return frame;
-               }
-
-               jbframe = ast_frisolate(frame);
-               ast_format_copy(&framedata->last_format, &frame->subclass.format);
-
-               if (frame->len && (frame->len != framedata->timer_interval)) {
-                       framedata->timer_interval = frame->len;
-                       ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
-               }
-               if (!framedata->first) {
-                       framedata->first = 1;
-                       res = framedata->jb_impl->put_first(framedata->jb_obj, jbframe, now);
-               } else {
-                       res = framedata->jb_impl->put(framedata->jb_obj, jbframe, now);
-               }
-               if (res == AST_JB_IMPL_OK) {
-                       frame = &ast_null_frame;
-               }
-               putframe = 1;
-       }
-
-       if (frame->frametype == AST_FRAME_NULL) {
-               int res;
-               long next = framedata->jb_impl->next(framedata->jb_obj);
-
-               /* If now is earlier than the next expected output frame
-                * from the jitterbuffer we may choose to pass on retrieving
-                * a frame during this read iteration.  The only exception
-                * to this rule is when an audio frame is placed into the buffer
-                * and the time for the next frame to come out of the buffer is
-                * at least within the timer_interval of the next output frame. By
-                * doing this we are able to feed off the timing of the input frames
-                * and only rely on our jitterbuffer timer when frames are dropped.
-                * During testing, this hybrid form of timing gave more reliable results. */
-               if (now < next) {
-                       long int diff = next - now;
-                       if (!putframe) {
-                               return frame;
-                       } else if (diff >= framedata->timer_interval) {
-                               return frame;
-                       }
-               }
-
-               res = framedata->jb_impl->get(framedata->jb_obj, &frame, now, framedata->timer_interval);
-               switch (res) {
-               case AST_JB_IMPL_OK:
-                       /* got it, and pass it through */
-                       break;
-               case AST_JB_IMPL_DROP:
-                       ast_frfree(frame);
-                       frame = &ast_null_frame;
-                       break;
-               case AST_JB_IMPL_INTERP:
-                       if (framedata->last_format.id) {
-                               struct ast_frame tmp = { 0, };
-                               tmp.frametype = AST_FRAME_VOICE;
-                               ast_format_copy(&tmp.subclass.format, &framedata->last_format);
-                               /* example: 8000hz / (1000 / 20ms) = 160 samples */
-                               tmp.samples = ast_format_rate(&framedata->last_format) / (1000 / framedata->timer_interval);
-                               tmp.delivery = ast_tvadd(framedata->start_tv, ast_samp2tv(next, 1000));
-                               tmp.offset = AST_FRIENDLY_OFFSET;
-                               tmp.src  = "func_jitterbuffer interpolation";
-                               frame = ast_frdup(&tmp);
-                               break;
-                       }
-                       /* else fall through */
-               case AST_JB_IMPL_NOFRAME:
-                       frame = &ast_null_frame;
-                       break;
-               }
-       }
-
-       return frame;
-}
-
-static int jb_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value)
-{
-       struct jb_framedata *framedata;
-       struct ast_datastore *datastore = NULL;
-       struct ast_framehook_interface interface = {
-               .version = AST_FRAMEHOOK_INTERFACE_VERSION,
-               .event_cb = hook_event_cb,
-               .destroy_cb = hook_destroy_cb,
-       };
-       int i = 0;
-
-       if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
-               return 0;
-       }
-
-       if (jb_framedata_init(framedata, data, value)) {
-               jb_framedata_destroy(framedata);
-               return 0;
-       }
-
-       interface.data = framedata;
-
-       ast_channel_lock(chan);
-       i = ast_framehook_attach(chan, &interface);
-       if (i >= 0) {
-               int *id;
-               if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
-                       id = datastore->data;
-                       ast_framehook_detach(chan, *id);
-                       ast_channel_datastore_remove(chan, datastore);
-               }
-
-               if (!(datastore = ast_datastore_alloc(&jb_datastore, NULL))) {
-                       ast_framehook_detach(chan, i);
-                       ast_channel_unlock(chan);
-                       return 0;
-               }
-
-               if (!(id = ast_calloc(1, sizeof(int)))) {
-                       ast_datastore_free(datastore);
-                       ast_framehook_detach(chan, i);
-                       ast_channel_unlock(chan);
-                       return 0;
-               }
-
-               *id = i; /* Store off the id. The channel is still locked so it is safe to access this ptr. */
-               datastore->data = id;
-               ast_channel_datastore_add(chan, datastore);
-
-               ast_channel_set_fd(chan, AST_JITTERBUFFER_FD, framedata->timer_fd);
-       } else {
-               jb_framedata_destroy(framedata);
-               framedata = NULL;
-       }
-       ast_channel_unlock(chan);
+       ast_jb_create_framehook(chan, &jb_conf, 0);
 
        return 0;
 }
 
+
 static struct ast_custom_function jb_function = {
        .name = "JITTERBUFFER",
        .write = jb_helper,
index 7e1ef13ec490ec8336e4f7c08fbd3cef2e6210f6..3fe35e58ceecd697b25a6c683167fc97a80aaa1d 100644 (file)
@@ -51,6 +51,24 @@ int ast_msg_init(void);             /*!< Provided by message.c */
 void ast_msg_shutdown(void);        /*!< Provided by message.c */
 int aco_init(void);             /*!< Provided by config_options.c */
 
+/*!
+ * \brief Initialize the bridging system.
+ * \since 12.0.0
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_bridging_init(void);
+
+/*!
+ * \brief Initialize the local proxy channel.
+ * \since 12.0.0
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_local_init(void);
+
 /*!
  * \brief Reload asterisk modules.
  * \param name the name of the module to reload
index 3e6bedd269cc36cd3d974f40a052418115a6c621..6a4d0610d126729fd3bb82884687d654ed6b7bd9 100644 (file)
@@ -246,6 +246,14 @@ void ast_jb_destroy(struct ast_channel *chan);
  */
 int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *value);
 
+/*!
+ * \since 12.0
+ * \brief Sets a jitterbuffer frame hook on the channel based on the channel's stored
+ *        jitterbuffer configuration
+ *
+ * \param chan Which channel is being set up
+ */
+void ast_jb_enable_for_channel(struct ast_channel *chan);
 
 /*!
  * \brief Configures a jitterbuffer on a channel.
@@ -257,7 +265,6 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
  */
 void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf);
 
-
 /*!
  * \brief Copies a channel's jitterbuffer configuration.
  * \param chan channel.
@@ -274,6 +281,25 @@ void ast_jb_empty_and_reset(struct ast_channel *c0, struct ast_channel *c1);
 
 const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type);
 
+/*!
+ * \since 12
+ * \brief Sets the contents of an ast_jb_conf struct to the default jitterbuffer settings
+ *
+ * \param conf Which jitterbuffer is being set
+ */
+void ast_jb_conf_default(struct ast_jb_conf *conf);
+
+/*!
+ * \since 12
+ * \brief Applies a jitterbuffer framehook to a channel based on a provided jitterbuffer config
+ *
+ * \param chan Which channel the jitterbuffer is being set on
+ * \param jb_conf Configuration to use for the jitterbuffer
+ * \param prefer_existing If this is true and a jitterbuffer already exists for the channel,
+ *        use the existing jitterbuffer
+ */
+void ast_jb_create_framehook(struct ast_channel *chan, struct ast_jb_conf *jb_conf, int prefer_existing);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
index 2890b1dfa7d90446350d01eff299c374699458b5..77fb54e8ddbe7e4e5d0619679431f7339183b44c 100644 (file)
@@ -1,8 +1,9 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 2007 - 2009, Digium, Inc.
+ * Copyright (C) 2007 - 2009, 2013 Digium, Inc.
  *
+ * Richard Mudgett <rmudgett@digium.com>
  * Joshua Colp <jcolp@digium.com>
  *
  * See http://www.asterisk.org for more information about
  * at the top of the source tree.
  */
 
-/*! \file
+/*!
+ * \file
  * \brief Channel Bridging API
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
  * \author Joshua Colp <jcolp@digium.com>
  * \ref AstBridging
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
  */
 
 /*!
@@ -63,54 +70,41 @@ extern "C" {
 #endif
 
 #include "asterisk/bridging_features.h"
+#include "asterisk/bridging_roles.h"
 #include "asterisk/dsp.h"
+#include "asterisk/uuid.h"
 
 /*! \brief Capabilities for a bridge technology */
 enum ast_bridge_capability {
-       /*! Bridge is only capable of mixing 2 channels */
-       AST_BRIDGE_CAPABILITY_1TO1MIX = (1 << 1),
-       /*! Bridge is capable of mixing 2 or more channels */
-       AST_BRIDGE_CAPABILITY_MULTIMIX = (1 << 2),
-       /*! Bridge should natively bridge two channels if possible */
-       AST_BRIDGE_CAPABILITY_NATIVE = (1 << 3),
-       /*! Bridge should run using the multithreaded model */
-       AST_BRIDGE_CAPABILITY_MULTITHREADED = (1 << 4),
-       /*! Bridge should run a central bridge thread */
-       AST_BRIDGE_CAPABILITY_THREAD = (1 << 5),
-       /*! Bridge technology can do video mixing (or something along those lines) */
-       AST_BRIDGE_CAPABILITY_VIDEO = (1 << 6),
-       /*! Bridge technology can optimize things based on who is talking */
-       AST_BRIDGE_CAPABILITY_OPTIMIZE = (1 << 7),
+       /*! Bridge technology can service calls on hold. */
+       AST_BRIDGE_CAPABILITY_HOLDING = (1 << 0),
+       /*! Bridge waits for channel to answer.  Passes early media. (XXX Not supported yet) */
+       AST_BRIDGE_CAPABILITY_EARLY = (1 << 1),
+       /*! Bridge is capable of natively bridging two channels. (Smart bridge only) */
+       AST_BRIDGE_CAPABILITY_NATIVE = (1 << 2),
+       /*! Bridge is capable of mixing at most two channels. (Smart bridgeable) */
+       AST_BRIDGE_CAPABILITY_1TO1MIX = (1 << 3),
+       /*! Bridge is capable of mixing an arbitrary number of channels. (Smart bridgeable) */
+       AST_BRIDGE_CAPABILITY_MULTIMIX = (1 << 4),
 };
 
 /*! \brief State information about a bridged channel */
 enum ast_bridge_channel_state {
        /*! Waiting for a signal (Channel in the bridge) */
        AST_BRIDGE_CHANNEL_STATE_WAIT = 0,
-       /*! Bridged channel has ended itself (it has hung up) */
+       /*! Bridged channel was forced out and should be hung up (Bridge may dissolve.) */
        AST_BRIDGE_CHANNEL_STATE_END,
        /*! Bridged channel was forced out and should be hung up */
        AST_BRIDGE_CHANNEL_STATE_HANGUP,
-       /*! Bridged channel was ast_bridge_depart() from the bridge without being hung up */
-       AST_BRIDGE_CHANNEL_STATE_DEPART,
-       /*! Bridged channel is executing a feature hook */
-       AST_BRIDGE_CHANNEL_STATE_FEATURE,
-       /*! Bridged channel is sending a DTMF stream out */
-       AST_BRIDGE_CHANNEL_STATE_DTMF,
-       /*! Bridged channel began talking */
-       AST_BRIDGE_CHANNEL_STATE_START_TALKING,
-       /*! Bridged channel has stopped talking */
-       AST_BRIDGE_CHANNEL_STATE_STOP_TALKING,
 };
 
-/*! \brief Return values for bridge technology write function */
-enum ast_bridge_write_result {
-       /*! Bridge technology wrote out frame fine */
-       AST_BRIDGE_WRITE_SUCCESS = 0,
-       /*! Bridge technology attempted to write out the frame but failed */
-       AST_BRIDGE_WRITE_FAILED,
-       /*! Bridge technology does not support writing out a frame of this type */
-       AST_BRIDGE_WRITE_UNSUPPORTED,
+enum ast_bridge_channel_thread_state {
+       /*! Bridge channel thread is idle/waiting. */
+       AST_BRIDGE_CHANNEL_THREAD_IDLE,
+       /*! Bridge channel thread is writing a normal/simple frame. */
+       AST_BRIDGE_CHANNEL_THREAD_SIMPLE,
+       /*! Bridge channel thread is processing a frame. */
+       AST_BRIDGE_CHANNEL_THREAD_FRAME,
 };
 
 struct ast_bridge_technology;
@@ -136,6 +130,7 @@ struct ast_bridge_tech_optimizations {
  * \brief Structure that contains information regarding a channel in a bridge
  */
 struct ast_bridge_channel {
+/* BUGBUG cond is only here because of external party suspend/unsuspend support. */
        /*! Condition, used if we want to wake up a thread waiting on the bridged channel */
        ast_cond_t cond;
        /*! Current bridged channel state */
@@ -144,29 +139,103 @@ struct ast_bridge_channel {
        struct ast_channel *chan;
        /*! Asterisk channel we are swapping with (if swapping) */
        struct ast_channel *swap;
-       /*! Bridge this channel is participating in */
+       /*!
+        * \brief Bridge this channel is participating in
+        *
+        * \note The bridge pointer cannot change while the bridge or
+        * bridge_channel is locked.
+        */
        struct ast_bridge *bridge;
-       /*! Private information unique to the bridge technology */
+       /*!
+        * \brief Bridge class private channel data.
+        *
+        * \note This information is added when the channel is pushed
+        * into the bridge and removed when it is pulled from the
+        * bridge.
+        */
        void *bridge_pvt;
-       /*! Thread handling the bridged channel */
+       /*!
+        * \brief Private information unique to the bridge technology.
+        *
+        * \note This information is added when the channel joins the
+        * bridge's technology and removed when it leaves the bridge's
+        * technology.
+        */
+       void *tech_pvt;
+       /*! Thread handling the bridged channel (Needed by ast_bridge_depart) */
        pthread_t thread;
-       /*! Additional file descriptors to look at */
-       int fds[4];
-       /*! Bit to indicate whether the channel is suspended from the bridge or not */
+       /* v-- These flags change while the bridge is locked or before the channel is in the bridge. */
+       /*! TRUE if the channel is in a bridge. */
+       unsigned int in_bridge:1;
+       /*! TRUE if the channel just joined the bridge. */
+       unsigned int just_joined:1;
+       /*! TRUE if the channel is suspended from the bridge. */
        unsigned int suspended:1;
-       /*! Bit to indicate if a imparted channel is allowed to get hungup after leaving the bridge by the bridging core. */
-       unsigned int allow_impart_hangup:1;
+       /*! TRUE if the channel must wait for an ast_bridge_depart to reclaim the channel. */
+       unsigned int depart_wait:1;
+       /* ^-- These flags change while the bridge is locked or before the channel is in the bridge. */
        /*! Features structure for features that are specific to this channel */
        struct ast_bridge_features *features;
        /*! Technology optimization parameters used by bridging technologies capable of
         *  optimizing based upon talk detection. */
        struct ast_bridge_tech_optimizations tech_args;
-       /*! Queue of DTMF digits used for DTMF streaming */
-       char dtmf_stream_q[8];
+       /*! Copy of read format used by chan before join */
+       struct ast_format read_format;
+       /*! Copy of write format used by chan before join */
+       struct ast_format write_format;
        /*! Call ID associated with bridge channel */
        struct ast_callid *callid;
+       /*! A clone of the roles living on chan when the bridge channel joins the bridge. This may require some opacification */
+       struct bridge_roles_datastore *bridge_roles;
        /*! Linked list information */
        AST_LIST_ENTRY(ast_bridge_channel) entry;
+       /*! Queue of outgoing frames to the channel. */
+       AST_LIST_HEAD_NOLOCK(, ast_frame) wr_queue;
+       /*! Pipe to alert thread when frames are put into the wr_queue. */
+       int alert_pipe[2];
+       /*! TRUE if the bridge channel thread is waiting on channels (needs to be atomically settable) */
+       int waiting;
+       /*!
+        * \brief The bridge channel thread activity.
+        *
+        * \details Used by local channel optimization to determine if
+        * the thread is in an acceptable state to optimize.
+        *
+        * \note Needs to be atomically settable.
+        */
+       enum ast_bridge_channel_thread_state activity;
+};
+
+enum ast_bridge_action_type {
+       /*! Bridged channel is to detect a feature hook */
+       AST_BRIDGE_ACTION_FEATURE,
+       /*! Bridged channel is to act on an interval hook */
+       AST_BRIDGE_ACTION_INTERVAL,
+       /*! Bridged channel is to send a DTMF stream out */
+       AST_BRIDGE_ACTION_DTMF_STREAM,
+       /*! Bridged channel is to indicate talking start */
+       AST_BRIDGE_ACTION_TALKING_START,
+       /*! Bridged channel is to indicate talking stop */
+       AST_BRIDGE_ACTION_TALKING_STOP,
+       /*! Bridge channel is to play the indicated sound file. */
+       AST_BRIDGE_ACTION_PLAY_FILE,
+       /*! Bridge channel is to get parked. */
+       AST_BRIDGE_ACTION_PARK,
+       /*! Bridge channel is to run the indicated application. */
+       AST_BRIDGE_ACTION_RUN_APP,
+       /*! Bridge channel is to execute a blind transfer. */
+       AST_BRIDGE_ACTION_BLIND_TRANSFER,
+
+       /*
+        * Bridge actions put after this comment must never be put onto
+        * the bridge_channel wr_queue because they have other resources
+        * that must be freed.
+        */
+
+       /*! Bridge reconfiguration deferred technology destruction. */
+       AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY = 1000,
+       /*! Bridge deferred dissolving. */
+       AST_BRIDGE_ACTION_DEFERRED_DISSOLVING,
 };
 
 enum ast_bridge_video_mode_type {
@@ -206,14 +275,152 @@ struct ast_bridge_video_mode {
        } mode_data;
 };
 
+/*!
+ * \brief Destroy the bridge.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_destructor_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief The bridge is being dissolved.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \details
+ * The bridge is being dissolved.  Remove any external
+ * references to the bridge so it can be destroyed.
+ *
+ * \note On entry, self must NOT be locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_dissolving_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief Push this channel into the bridge.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \details
+ * Setup any channel hooks controlled by the bridge.  Allocate
+ * bridge_channel->bridge_pvt and initialize any resources put
+ * in bridge_channel->bridge_pvt if needed.  If there is a swap
+ * channel, use it as a guide to setting up the bridge_channel.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.  The channel did not get pushed.
+ */
+typedef int (*ast_bridge_push_channel_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap);
+
+/*!
+ * \brief Pull this channel from the bridge.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \details
+ * Remove any channel hooks controlled by the bridge.  Release
+ * any resources held by bridge_channel->bridge_pvt and release
+ * bridge_channel->bridge_pvt.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_pull_channel_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Notify the bridge that this channel was just masqueraded.
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \details
+ * A masquerade just happened to this channel.  The bridge needs
+ * to re-evaluate this a channel in the bridge.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_notify_masquerade_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Get the merge priority of this bridge.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Merge priority
+ */
+typedef int (*ast_bridge_merge_priority_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief Bridge virtual methods table definition.
+ *
+ * \note Any changes to this struct must be reflected in
+ * ast_bridge_alloc() validity checking.
+ */
+struct ast_bridge_methods {
+       /*! Bridge class name for log messages. */
+       const char *name;
+       /*! Destroy the bridge. */
+       ast_bridge_destructor_fn destroy;
+       /*! The bridge is being dissolved.  Remove any references to the bridge. */
+       ast_bridge_dissolving_fn dissolving;
+       /*! Push the bridge channel into the bridge. */
+       ast_bridge_push_channel_fn push;
+       /*! Pull the bridge channel from the bridge. */
+       ast_bridge_pull_channel_fn pull;
+       /*! Notify the bridge of a masquerade with the channel. */
+       ast_bridge_notify_masquerade_fn notify_masquerade;
+       /*! Get the bridge merge priority. */
+       ast_bridge_merge_priority_fn get_merge_priority;
+};
+
 /*!
  * \brief Structure that contains information about a bridge
  */
 struct ast_bridge {
-       /*! Number of channels participating in the bridge */
-       int num;
+       /*! Bridge virtual method table. */
+       const struct ast_bridge_methods *v_table;
+       /*! Immutable bridge UUID. */
+       char uniqueid[AST_UUID_STR_LEN];
+       /*! Bridge technology that is handling the bridge */
+       struct ast_bridge_technology *technology;
+       /*! Private information unique to the bridge technology */
+       void *tech_pvt;
+       /*! Call ID associated with the bridge */
+       struct ast_callid *callid;
+       /*! Linked list of channels participating in the bridge */
+       AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
+       /*! Queue of actions to perform on the bridge. */
+       AST_LIST_HEAD_NOLOCK(, ast_frame) action_queue;
        /*! The video mode this bridge is using */
        struct ast_bridge_video_mode video_mode;
+       /*! Bridge flags to tweak behavior */
+       struct ast_flags feature_flags;
+       /*! Allowed bridge technology capabilities when AST_BRIDGE_FLAG_SMART enabled. */
+       uint32_t allowed_capabilities;
+       /*! Number of channels participating in the bridge */
+       unsigned int num_channels;
+       /*! Number of active channels in the bridge. */
+       unsigned int num_active;
+       /*!
+        * \brief Count of the active temporary requests to inhibit bridge merges.
+        * Zero if merges are allowed.
+        *
+        * \note Temporary as in try again in a moment.
+        */
+       unsigned int inhibit_merge;
        /*! The internal sample rate this bridge is mixed at when multiple channels are being mixed.
         *  If this value is 0, the bridge technology may auto adjust the internal mixing rate. */
        unsigned int internal_sample_rate;
@@ -221,36 +428,83 @@ struct ast_bridge {
         * for bridge technologies that mix audio. When set to 0, the bridge tech must choose a
         * default interval for itself. */
        unsigned int internal_mixing_interval;
-       /*! Bit to indicate that the bridge thread is waiting on channels in the bridge array */
-       unsigned int waiting:1;
-       /*! Bit to indicate the bridge thread should stop */
-       unsigned int stop:1;
-       /*! Bit to indicate the bridge thread should refresh itself */
-       unsigned int refresh:1;
-       /*! Bridge flags to tweak behavior */
-       struct ast_flags feature_flags;
-       /*! Bridge technology that is handling the bridge */
-       struct ast_bridge_technology *technology;
-       /*! Private information unique to the bridge technology */
-       void *bridge_pvt;
-       /*! Thread running the bridge */
-       pthread_t thread;
-       /*! Enabled features information */
-       struct ast_bridge_features features;
-       /*! Array of channels that the bridge thread is currently handling */
-       struct ast_channel **array;
-       /*! Number of channels in the above array */
-       size_t array_num;
-       /*! Number of channels the array can handle */
-       size_t array_size;
-       /*! Call ID associated with the bridge */
-       struct ast_callid *callid;
-       /*! Linked list of channels participating in the bridge */
-       AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
-       };
+       /*! TRUE if the bridge was reconfigured. */
+       unsigned int reconfigured:1;
+       /*! TRUE if the bridge has been dissolved.  Any channel that now tries to join is immediately ejected. */
+       unsigned int dissolved:1;
+};
+
+/*!
+ * \brief Register the new bridge with the system.
+ * \since 12.0.0
+ *
+ * \param bridge What to register. (Tolerates a NULL pointer)
+ *
+ * \code
+ * struct ast_bridge *ast_bridge_basic_new(uint32_t capabilities, int flags, uint32 dtmf_features)
+ * {
+ *     void *bridge;
+ *
+ *     bridge = ast_bridge_alloc(sizeof(struct ast_bridge_basic), &ast_bridge_basic_v_table);
+ *     bridge = ast_bridge_base_init(bridge, capabilities, flags);
+ *     bridge = ast_bridge_basic_init(bridge, dtmf_features);
+ *     bridge = ast_bridge_register(bridge);
+ *     return bridge;
+ * }
+ * \endcode
+ *
+ * \note This must be done after a bridge constructor has
+ * completed setting up the new bridge but before it returns.
+ *
+ * \note After a bridge is registered, ast_bridge_destroy() must
+ * eventually be called to get rid of the bridge.
+ *
+ * \retval bridge on success.
+ * \retval NULL on error.
+ */
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge);
+
+/*!
+ * \internal
+ * \brief Allocate the bridge class object memory.
+ * \since 12.0.0
+ *
+ * \param size Size of the bridge class structure to allocate.
+ * \param v_table Bridge class virtual method table.
+ *
+ * \retval bridge on success.
+ * \retval NULL on error.
+ */
+struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table);
+
+/*! \brief Bridge base class virtual method table. */
+extern struct ast_bridge_methods ast_bridge_base_v_table;
 
 /*!
- * \brief Create a new bridge
+ * \brief Initialize the base class of the bridge.
+ *
+ * \param self Bridge to operate upon. (Tolerates a NULL pointer)
+ * \param capabilities The capabilities that we require to be used on the bridge
+ * \param flags Flags that will alter the behavior of the bridge
+ *
+ * \retval self on success
+ * \retval NULL on failure, self is already destroyed
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge *bridge;
+ * bridge = ast_bridge_alloc(sizeof(*bridge), &ast_bridge_base_v_table);
+ * bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
+ * \endcode
+ *
+ * This creates a no frills two party bridge that will be
+ * destroyed once one of the channels hangs up.
+ */
+struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags);
+
+/*!
+ * \brief Create a new base class bridge
  *
  * \param capabilities The capabilities that we require to be used on the bridge
  * \param flags Flags that will alter the behavior of the bridge
@@ -262,13 +516,27 @@ struct ast_bridge {
  *
  * \code
  * struct ast_bridge *bridge;
- * bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE);
+ * bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
  * \endcode
  *
- * This creates a simple two party bridge that will be destroyed once one of
- * the channels hangs up.
+ * This creates a no frills two party bridge that will be
+ * destroyed once one of the channels hangs up.
  */
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags);
+struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags);
+
+/*!
+ * \brief Try locking the bridge.
+ *
+ * \param bridge Bridge to try locking
+ *
+ * \retval 0 on success.
+ * \retval non-zero on error.
+ */
+#define ast_bridge_trylock(bridge)     _ast_bridge_trylock(bridge, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge)
+static inline int _ast_bridge_trylock(struct ast_bridge *bridge, const char *file, const char *function, int line, const char *var)
+{
+       return __ao2_trylock(bridge, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
 
 /*!
  * \brief Lock the bridge.
@@ -296,24 +564,18 @@ static inline void _ast_bridge_unlock(struct ast_bridge *bridge, const char *fil
        __ao2_unlock(bridge, file, function, line, var);
 }
 
-/*!
- * \brief See if it is possible to create a bridge
- *
- * \param capabilities The capabilities that the bridge will use
- *
- * \retval 1 if possible
- * \retval 0 if not possible
- *
- * Example usage:
- *
- * \code
- * int possible = ast_bridge_check(AST_BRIDGE_CAPABILITY_1TO1MIX);
- * \endcode
- *
- * This sees if it is possible to create a bridge capable of bridging two channels
- * together.
- */
-int ast_bridge_check(uint32_t capabilities);
+/*! \brief Lock two bridges. */
+#define ast_bridge_lock_both(bridge1, bridge2)         \
+       do {                                                                                    \
+               for (;;) {                                                                      \
+                       ast_bridge_lock(bridge1);                               \
+                       if (!ast_bridge_trylock(bridge2)) {             \
+                               break;                                                          \
+                       }                                                                               \
+                       ast_bridge_unlock(bridge1);                             \
+                       sched_yield();                                                  \
+               }                                                                                       \
+       } while (0)
 
 /*!
  * \brief Destroy a bridge
@@ -329,10 +591,20 @@ int ast_bridge_check(uint32_t capabilities);
  * ast_bridge_destroy(bridge);
  * \endcode
  *
- * This destroys a bridge that was previously created using ast_bridge_new.
+ * This destroys a bridge that was previously created.
  */
 int ast_bridge_destroy(struct ast_bridge *bridge);
 
+/*!
+ * \brief Notify bridging that this channel was just masqueraded.
+ * \since 12.0.0
+ *
+ * \param chan Channel just involved in a masquerade
+ *
+ * \return Nothing
+ */
+void ast_bridge_notify_masquerade(struct ast_channel *chan);
+
 /*!
  * \brief Join (blocking) a channel to a bridge
  *
@@ -341,13 +613,17 @@ int ast_bridge_destroy(struct ast_bridge *bridge);
  * \param swap Channel to swap out if swapping
  * \param features Bridge features structure
  * \param tech_args Optional Bridging tech optimization parameters for this channel.
+ * \param pass_reference TRUE if the bridge reference is being passed by the caller.
+ *
+ * \note Absolutely _NO_ locks should be held before calling
+ * this function since it blocks.
  *
  * \retval state that channel exited the bridge with
  *
  * Example usage:
  *
  * \code
- * ast_bridge_join(bridge, chan, NULL, NULL);
+ * ast_bridge_join(bridge, chan, NULL, NULL, NULL, 0);
  * \endcode
  *
  * This adds a channel pointed to by the chan pointer to the bridge pointed to by
@@ -365,16 +641,23 @@ enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
        struct ast_channel *chan,
        struct ast_channel *swap,
        struct ast_bridge_features *features,
-       struct ast_bridge_tech_optimizations *tech_args);
+       struct ast_bridge_tech_optimizations *tech_args,
+       int pass_reference);
 
 /*!
  * \brief Impart (non-blocking) a channel onto a bridge
  *
  * \param bridge Bridge to impart on
  * \param chan Channel to impart
- * \param swap Channel to swap out if swapping
- * \param features Bridge features structure
- * \param allow_hangup  Indicates if the bridge thread should manage hanging up of the channel or not.
+ * \param swap Channel to swap out if swapping.  NULL if not swapping.
+ * \param features Bridge features structure.
+ * \param independent TRUE if caller does not want to reclaim the channel using ast_bridge_depart().
+ *
+ * \note The features parameter must be NULL or obtained by
+ * ast_bridge_features_new().  You must not dereference features
+ * after calling even if the call fails.
+ *
+ * \note chan is locked by this function.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -385,42 +668,60 @@ enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
  * ast_bridge_impart(bridge, chan, NULL, NULL, 0);
  * \endcode
  *
- * This adds a channel pointed to by the chan pointer to the bridge pointed to by
- * the bridge pointer. This function will return immediately and will not wait
- * until the channel is no longer part of the bridge.
- *
- * If this channel will be replacing another channel the other channel can be specified
- * in the swap parameter. The other channel will be thrown out of the bridge in an
- * atomic fashion.
- *
- * If channel specific features are enabled a pointer to the features structure
- * can be specified in the features parameter.
+ * \details
+ * This adds a channel pointed to by the chan pointer to the
+ * bridge pointed to by the bridge pointer.  This function will
+ * return immediately and will not wait until the channel is no
+ * longer part of the bridge.
+ *
+ * If this channel will be replacing another channel the other
+ * channel can be specified in the swap parameter.  The other
+ * channel will be thrown out of the bridge in an atomic
+ * fashion.
+ *
+ * If channel specific features are enabled, a pointer to the
+ * features structure can be specified in the features
+ * parameter.
+ *
+ * \note If you impart a channel as not independent you MUST
+ * ast_bridge_depart() the channel if this call succeeds.  The
+ * bridge channel thread is created join-able.  The implication
+ * is that the channel is special and will not behave like a
+ * normal channel.
+ *
+ * \note If you impart a channel as independent you must not
+ * ast_bridge_depart() the channel.  The bridge channel thread
+ * is created non-join-able.  The channel must be treated as if
+ * it were placed into the bridge by ast_bridge_join().
+ * Channels placed into a bridge by ast_bridge_join() are
+ * removed by a third party using ast_bridge_remove().
  */
-int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup);
+int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent);
 
 /*!
  * \brief Depart a channel from a bridge
  *
- * \param bridge Bridge to depart from
  * \param chan Channel to depart
  *
+ * \note chan is locked by this function.
+ *
  * \retval 0 on success
  * \retval -1 on failure
  *
  * Example usage:
  *
  * \code
- * ast_bridge_depart(bridge, chan);
+ * ast_bridge_depart(chan);
  * \endcode
  *
- * This removes the channel pointed to by the chan pointer from the bridge
- * pointed to by the bridge pointer and gives control to the calling thread.
+ * This removes the channel pointed to by the chan pointer from any bridge
+ * it may be in and gives control to the calling thread.
  * This does not hang up the channel.
  *
  * \note This API call can only be used on channels that were added to the bridge
- *       using the ast_bridge_impart API call.
+ *       using the ast_bridge_impart API call with the independent flag FALSE.
  */
-int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan);
+int ast_bridge_depart(struct ast_channel *chan);
 
 /*!
  * \brief Remove a channel from a bridge
@@ -449,8 +750,14 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan);
 /*!
  * \brief Merge two bridges together
  *
- * \param bridge0 First bridge
- * \param bridge1 Second bridge
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param merge_best_direction TRUE if don't care about which bridge merges into the other.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \note Absolutely _NO_ bridge or channel locks should be held
+ * before calling this function.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -458,16 +765,57 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan);
  * Example usage:
  *
  * \code
- * ast_bridge_merge(bridge0, bridge1);
+ * ast_bridge_merge(dst_bridge, src_bridge, 0, NULL, 0);
  * \endcode
  *
- * This merges the bridge pointed to by bridge1 with the bridge pointed to by bridge0.
- * In reality all of the channels in bridge1 are simply moved to bridge0.
+ * This moves the channels in src_bridge into the bridge pointed
+ * to by dst_bridge.
+ */
+int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick);
+
+/*!
+ * \brief Move a channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param src_bridge Source bridge of bridge channel move.
+ * \param chan Channel to move.
+ * \param swap Channel to replace in dst_bridge.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note Absolutely _NO_ bridge or channel locks should be held
+ * before calling this function.
  *
- * \note The second bridge specified is not destroyed when this operation is
- *       completed.
+ * \retval 0 on success.
+ * \retval -1 on failure.
  */
-int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1);
+int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery);
+
+/*!
+ * \brief Adjust the bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \return Nothing
+ */
+void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request);
+
+/*!
+ * \brief Adjust the bridge_channel's bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \note This API call is meant for internal bridging operations.
+ *
+ * \retval bridge adjusted merge inhibit with reference count.
+ */
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request);
 
 /*!
  * \brief Suspend a channel temporarily from a bridge
@@ -517,7 +865,94 @@ int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan);
 int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
 
 /*!
- * \brief Change the state of a bridged channel
+ * \brief Check and optimize out the unreal channels between bridges.
+ * \since 12.0.0
+ *
+ * \param chan Unreal channel writing a frame into the channel driver.
+ * \param peer Other unreal channel in the pair.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval non-zero if unreal channels were optimized out.
+ */
+int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer);
+
+/*!
+ * \brief Try locking the bridge_channel.
+ *
+ * \param bridge_channel What to try locking
+ *
+ * \retval 0 on success.
+ * \retval non-zero on error.
+ */
+#define ast_bridge_channel_trylock(bridge_channel)     _ast_bridge_channel_trylock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline int _ast_bridge_channel_trylock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+       return __ao2_trylock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Lock the bridge_channel.
+ *
+ * \param bridge_channel What to lock
+ *
+ * \return Nothing
+ */
+#define ast_bridge_channel_lock(bridge_channel)        _ast_bridge_channel_lock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline void _ast_bridge_channel_lock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+       __ao2_lock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Unlock the bridge_channel.
+ *
+ * \param bridge_channel What to unlock
+ *
+ * \return Nothing
+ */
+#define ast_bridge_channel_unlock(bridge_channel)      _ast_bridge_channel_unlock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline void _ast_bridge_channel_unlock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+       __ao2_unlock(bridge_channel, file, function, line, var);
+}
+
+/*!
+ * \brief Lock the bridge associated with the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel that wants to lock the bridge.
+ *
+ * \details
+ * This is an upstream lock operation.  The defined locking
+ * order is bridge then bridge_channel.
+ *
+ * \note On entry, neither the bridge nor bridge_channel is locked.
+ *
+ * \note The bridge_channel->bridge pointer changes because of a
+ * bridge-merge/channel-move operation between bridges.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Set bridge channel state to leave bridge (if not leaving already) with no lock.
+ *
+ * \param bridge_channel Channel to change the state on
+ * \param new_state The new state to place the channel into
+ *
+ * \note This API call is only meant to be used within the
+ * bridging module and hook callbacks to request the channel
+ * exit the bridge.
+ *
+ * \note This function assumes the bridge_channel is locked.
+ */
+void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state);
+
+/*!
+ * \brief Set bridge channel state to leave bridge (if not leaving already).
  *
  * \param bridge_channel Channel to change the state on
  * \param new_state The new state to place the channel into
@@ -525,17 +960,265 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
  * Example usage:
  *
  * \code
- * ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
+ * ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
  * \endcode
  *
- * This places the channel pointed to by bridge_channel into the state
- * AST_BRIDGE_CHANNEL_STATE_WAIT.
+ * This places the channel pointed to by bridge_channel into the
+ * state AST_BRIDGE_CHANNEL_STATE_HANGUP if it was
+ * AST_BRIDGE_CHANNEL_STATE_WAIT before.
  *
- * \note This API call is only meant to be used in feature hook callbacks to
- *       make sure the channel either hangs up or returns to the bridge.
+ * \note This API call is only meant to be used within the
+ * bridging module and hook callbacks to request the channel
+ * exit the bridge.
  */
 void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state);
 
+/*!
+ * \brief Put an action onto the specified bridge.
+ * \since 12.0.0
+ *
+ * \param bridge What to queue the action on.
+ * \param action What to do.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ *
+ * \note This API call is meant for internal bridging operations.
+ * \note BUGBUG This may get moved.
+ */
+int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action);
+
+/*!
+ * \brief Write a frame to the specified bridge_channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to queue the frame.
+ * \param fr Frame to write.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ *
+ * \note This API call is meant for internal bridging operations.
+ * \note BUGBUG This may get moved.
+ */
+int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr);
+
+/*!
+ * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel work with.
+ * \param action Type of bridge action frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_channel_post_action_data)(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen);
+
+/*!
+ * \brief Queue an action frame onto the bridge channel with data.
+ * \since 12.0.0
+ *
+ * \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.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen);
+
+/*!
+ * \brief Write an action frame into the bridge with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge.
+ * \param action Type of bridge action frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen);
+
+/*!
+ * \brief Queue a control frame onto the bridge channel with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to queue the frame onto.
+ * \param control Type of control frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen);
+
+/*!
+ * \brief Write a control frame into the bridge with data.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge.
+ * \param control Type of control frame.
+ * \param data Frame payload data to pass.
+ * \param datalen Frame payload data length to pass.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen);
+
+/*!
+ * \brief Run an application on the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to run the application on.
+ * \param app_name Dialplan application name.
+ * \param app_args Arguments for the application. (NULL tolerant)
+ * \param moh_class MOH class to request bridge peers to hear while application is running.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+/*!
+ * \brief Write a bridge action run application frame into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge
+ * \param app_name Dialplan application name.
+ * \param app_args Arguments for the application. (NULL or empty for no arguments)
+ * \param moh_class MOH class to request bridge peers to hear while application is running.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+/*!
+ * \brief Queue a bridge action run application frame onto the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to put the frame onto
+ * \param app_name Dialplan application name.
+ * \param app_args Arguments for the application. (NULL or empty for no arguments)
+ * \param moh_class MOH class to request bridge peers to hear while application is running.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+/*!
+ * \brief Custom interpretation of the playfile name.
+ *
+ * \param bridge_channel Which channel to play the file on
+ * \param playfile Sound filename to play.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_bridge_custom_play_fn)(struct ast_bridge_channel *bridge_channel, const char *playfile);
+
+/*!
+ * \brief Play a file on the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to play the file on
+ * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play)
+ * \param playfile Sound filename to play.
+ * \param moh_class MOH class to request bridge peers to hear while file is played.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class);
+
+/*!
+ * \brief Have a bridge channel park a channel in the bridge
+ * \since 12.0.0
+ *
+ * \param bridge_channel Bridge channel performing the parking
+ * \param parkee_uuid Unique id of the channel we want to park
+ * \param parker_uuid Unique id of the channel parking the call
+ * \param app_data string indicating data used for park application (NULL allowed)
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid,
+       const char *parker_uuid, const char *app_data);
+
+/*!
+ * \brief Write a bridge action play file frame into the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is putting the frame into the bridge
+ * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play)
+ * \param playfile Sound filename to play.
+ * \param moh_class MOH class to request bridge peers to hear while file is played.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class);
+
+/*!
+ * \brief Queue a bridge action play file frame onto the bridge channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to put the frame onto.
+ * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play)
+ * \param playfile Sound filename to play.
+ * \param moh_class MOH class to request bridge peers to hear while file is played.
+ *     NULL if no MOH.
+ *     Empty if default MOH class.
+ *
+ * \note This is intended to be called by bridge hooks.
+ *
+ * \return Nothing
+ */
+void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class);
+
+/*!
+ * \brief Restore the formats of a bridge channel's channel to how they were before bridge_channel_join
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to restore
+ */
+void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Get the peer bridge channel of a two party bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to get the peer of.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \note This is an internal bridge function.
+ *
+ * \retval peer on success.
+ * \retval NULL no peer channel.
+ */
+struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel);
+
 /*!
  * \brief Adjust the internal mixing sample rate of a bridge
  * used during multimix mode.
@@ -594,8 +1277,297 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
  */
 void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan);
 
+enum ast_transfer_result {
+       /*! The transfer completed successfully */
+       AST_BRIDGE_TRANSFER_SUCCESS,
+       /*! A bridge involved does not permit transferring */
+       AST_BRIDGE_TRANSFER_NOT_PERMITTED,
+       /*! The current bridge setup makes transferring an invalid operation */
+       AST_BRIDGE_TRANSFER_INVALID,
+       /*! The transfer operation failed for a miscellaneous reason */
+       AST_BRIDGE_TRANSFER_FAIL,
+};
+
+typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data);
+
+/*!
+ * \brief Blind transfer target to the extension and context provided
+ *
+ * The channel given is bridged to one or multiple channels. Depending on
+ * the bridge and the number of participants, the entire bridge could be
+ * transferred to the given destination, or a single channel may be redirected.
+ *
+ * Callers may also provide a callback to be called on the channel that will
+ * be running dialplan. The user data passed into ast_bridge_transfer_blind
+ * will be given as the argument to the callback to be interpreted as desired.
+ * This callback is guaranteed to be called in the same thread as
+ * ast_bridge_transfer_blind() and before ast_bridge_transfer_blind() returns.
+ *
+ * \note Absolutely _NO_ channel locks should be held before
+ * calling this function.
+ *
+ * \param transferer The channel performing the blind transfer
+ * \param exten The dialplan extension to send the call to
+ * \param context The dialplan context to send the call to
+ * \param new_channel_cb A callback to be called on the channel that will
+ *        be executing dialplan
+ * \param user_data Argument for new_channel_cb
+ * \return The success or failure result of the blind transfer
+ */
+enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
+               const char *exten, const char *context,
+               transfer_channel_cb new_channel_cb, void *user_data);
+
+/*!
+ * \brief Attended transfer
+ *
+ * The two channels are both transferer channels. The first is the channel
+ * that is bridged to the transferee (or if unbridged, the 'first' call of
+ * the transfer). The second is the channel that is bridged to the transfer
+ * target (or if unbridged, the 'second' call of the transfer).
+ *
+ * Like with a blind transfer, a frame hook can be provided to monitor the
+ * resulting call after the transfer completes. If the transfer fails, the
+ * hook will not be attached to any call.
+ *
+ * \note Absolutely _NO_ channel locks should be held before
+ * calling this function.
+ *
+ * \param to_transferee Transferer channel on initial call (presumably bridged to transferee)
+ * \param to_transfer_target Transferer channel on consultation call (presumably bridged to transfer target)
+ * \param hook A frame hook to attach to the resultant call
+ * \return The success or failure of the attended transfer
+ */
+enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
+               struct ast_channel *to_transfer_target, struct ast_framehook *hook);
+/*!
+ * \brief Set channel to goto specific location after the bridge.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param context Context to goto after bridge.
+ * \param exten Exten to goto after bridge.
+ * \param priority Priority to goto after bridge.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority);
+
+/*!
+ * \brief Set channel to run the h exten after the bridge.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param context Context to goto after bridge.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_set_h(struct ast_channel *chan, const char *context);
+
+/*!
+ * \brief Set channel to go on in the dialplan after the bridge.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param context Current context of the caller channel.
+ * \param exten Current exten of the caller channel.
+ * \param priority Current priority of the caller channel
+ * \param parseable_goto User specified goto string from dialplan.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * If parseable_goto then use the given context/exten/priority
+ *   as the relative position for the parseable_goto.
+ * Else goto the given context/exten/priority+1.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto);
+
+/*!
+ * \brief Setup any after bridge goto location to begin execution.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Pull off any after bridge goto location datastore and
+ * setup for dialplan execution there.
+ *
+ * \retval 0 on success.  The goto location is set for a PBX to run it.
+ * \retval non-zero on error or no goto location.
+ *
+ * \note If the after bridge goto is set to run an h exten it is
+ * run here immediately.
+ */
+int ast_after_bridge_goto_setup(struct ast_channel *chan);
+
+/*!
+ * \brief Run a PBX on any after bridge goto location.
+ * \since 12.0.0
+ *
+ * \param chan Channel to execute after bridge goto location.
+ *
+ * \note chan is locked by this function.
+ *
+ * \details Pull off any after bridge goto location datastore
+ * and run a PBX at that location.
+ *
+ * \note On return, the chan pointer is no longer valid because
+ * the channel has hung up.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_goto_run(struct ast_channel *chan);
+
+/*!
+ * \brief Discard channel after bridge goto location.
+ * \since 12.0.0
+ *
+ * \param chan Channel to discard after bridge goto location.
+ *
+ * \note chan is locked by this function.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_goto_discard(struct ast_channel *chan);
+
+/*! Reason the the after bridge callback will not be called. */
+enum ast_after_bridge_cb_reason {
+       /*! The datastore is being destroyed.  Likely due to hangup. */
+       AST_AFTER_BRIDGE_CB_REASON_DESTROY,
+       /*! Something else replaced the callback with another. */
+       AST_AFTER_BRIDGE_CB_REASON_REPLACED,
+       /*! The callback was removed because of a masquerade. (fixup) */
+       AST_AFTER_BRIDGE_CB_REASON_MASQUERADE,
+       /*! The channel departed bridge. */
+       AST_AFTER_BRIDGE_CB_REASON_DEPART,
+       /*! Was explicitly removed by external code. */
+       AST_AFTER_BRIDGE_CB_REASON_REMOVED,
+};
+
+/*!
+ * \brief After bridge callback failed.
+ * \since 12.0.0
+ *
+ * \param reason Reason callback is failing.
+ * \param data Extra data what setup the callback wanted to pass.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_after_bridge_cb_failed)(enum ast_after_bridge_cb_reason reason, void *data);
+
+/*!
+ * \brief After bridge callback function.
+ * \since 12.0.0
+ *
+ * \param chan Channel just leaving bridging system.
+ * \param data Extra data what setup the callback wanted to pass.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_after_bridge_cb)(struct ast_channel *chan, void *data);
+
+/*!
+ * \brief Discard channel after bridge callback.
+ * \since 12.0.0
+ *
+ * \param chan Channel to discard after bridge callback.
+ * \param reason Why are we doing this.
+ *
+ * \note chan is locked by this function.
+ *
+ * \return Nothing
+ */
+void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason);
+
+/*!
+ * \brief Setup an after bridge callback for when the channel leaves the bridging system.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup an after bridge callback on.
+ * \param callback Function to call when the channel leaves the bridging system.
+ * \param failed Function to call when it will not be calling the callback.
+ * \param data Extra data to pass with the callback.
+ *
+ * \note chan is locked by this function.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data);
+
+/*!
+ * \brief Get a container of all channels in the bridge
+ * \since 12.0.0
+ *
+ * \param bridge The bridge which is already locked.
+ *
+ * \retval NULL Failed to create container
+ * \retval non-NULL Container of channels in the bridge
+ */
+struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge);
+
+/*!
+ * \brief Get a container of all channels in the bridge
+ * \since 12.0.0
+ *
+ * \param bridge The bridge
+ *
+ * \note The returned container is a snapshot of channels in the
+ * bridge when called.
+ *
+ * \retval NULL Failed to create container
+ * \retval non-NULL Container of channels in the bridge
+ */
+struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge);
+
+/*!
+ * \brief Get the channel's bridge peer only if the bridge is two-party.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge which is already locked.
+ * \param chan Channel desiring the bridge peer channel.
+ *
+ * \note The returned peer channel is the current peer in the
+ * bridge when called.
+ *
+ * \retval NULL Channel not in a bridge or the bridge is not two-party.
+ * \retval non-NULL Reffed peer channel at time of calling.
+ */
+struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \brief Get the channel's bridge peer only if the bridge is two-party.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge
+ * \param chan Channel desiring the bridge peer channel.
+ *
+ * \note The returned peer channel is the current peer in the
+ * bridge when called.
+ *
+ * \retval NULL Channel not in a bridge or the bridge is not two-party.
+ * \retval non-NULL Reffed peer channel at time of calling.
+ */
+struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
 
-#endif /* _ASTERISK_BRIDGING_H */
+#endif /* _ASTERISK_BRIDGING_H */
diff --git a/include/asterisk/bridging_basic.h b/include/asterisk/bridging_basic.h
new file mode 100644 (file)
index 0000000..cc42354
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Basic bridge subclass API.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_BRIDGING_BASIC_H
+#define _ASTERISK_BRIDGING_BASIC_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief Get DTMF feature flags from the channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to get DTMF features datastore.
+ *
+ * \note The channel should be locked before calling this function.
+ *
+ * \retval flags on success.
+ * \retval NULL on error.
+ */
+struct ast_flags *ast_bridge_features_ds_get(struct ast_channel *chan);
+
+/*!
+ * \brief Set basic bridge DTMF feature flags datastore on the channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to set DTMF features datastore.
+ * \param flags Builtin DTMF feature flags. (ast_bridge_config flags)
+ *
+ * \note The channel must be locked before calling this function.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_bridge_features_ds_set(struct ast_channel *chan, struct ast_flags *flags);
+
+/*!
+ * \brief Setup DTMF feature hooks using the channel features datastore property.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What to setup DTMF features on.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel);
+
+/*! \brief Bridge basic class virtual method table. */
+extern struct ast_bridge_methods ast_bridge_basic_v_table;
+
+/*!
+ * \brief Create a new basic class bridge
+ *
+ * \retval a pointer to a new bridge on success
+ * \retval NULL on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge *bridge;
+ * bridge = ast_bridge_basic_new();
+ * \endcode
+ *
+ * This creates a basic two party bridge with any configured
+ * DTMF features enabled that will be destroyed once one of the
+ * channels hangs up.
+ */
+struct ast_bridge *ast_bridge_basic_new(void);
+
+/*! Initialize the basic bridge class for use by the system. */
+void ast_bridging_init_basic(void);
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_BRIDGING_BASIC_H */
index 1323a6da98f1167c4fe689889e5dfd3e2c5759c1..bb792a81556c337af963cf5d00e6038202263dea 100644 (file)
@@ -30,10 +30,36 @@ extern "C" {
 
 /*! \brief Flags used for bridge features */
 enum ast_bridge_feature_flags {
-       /*! Upon hangup the bridge should be discontinued */
-       AST_BRIDGE_FLAG_DISSOLVE = (1 << 0),
+       /*! Upon channel hangup all bridge participants should be kicked out. */
+       AST_BRIDGE_FLAG_DISSOLVE_HANGUP = (1 << 0),
+       /*! The last channel to leave the bridge dissolves it. */
+       AST_BRIDGE_FLAG_DISSOLVE_EMPTY = (1 << 1),
        /*! Move between bridging technologies as needed. */
-       AST_BRIDGE_FLAG_SMART = (1 << 1),
+       AST_BRIDGE_FLAG_SMART = (1 << 2),
+       /*! Bridge channels cannot be merged from this bridge. */
+       AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM = (1 << 3),
+       /*! Bridge channels cannot be merged to this bridge. */
+       AST_BRIDGE_FLAG_MERGE_INHIBIT_TO = (1 << 4),
+       /*! Bridge channels cannot be local channel swap optimized from this bridge. */
+       AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM = (1 << 5),
+       /*! Bridge channels cannot be local channel swap optimized to this bridge. */
+       AST_BRIDGE_FLAG_SWAP_INHIBIT_TO = (1 << 6),
+       /*! Bridge channels can be moved to another bridge only by masquerade (ConfBridge) */
+       AST_BRIDGE_FLAG_MASQUERADE_ONLY = (1 << 7),
+       /*! Bridge does not allow transfers of channels out */
+       AST_BRIDGE_FLAG_TRANSFER_PROHIBITED = (1 << 6),
+       /*! Bridge transfers require transfer of entire bridge rather than individual channels */
+       AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY = (1 << 7),
+};
+
+/*! \brief Flags used for per bridge channel features */
+enum ast_bridge_channel_feature_flags {
+       /*! Upon channel hangup all bridge participants should be kicked out. */
+       AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP = (1 << 0),
+       /*! This channel leaves the bridge if all participants have this flag set. */
+       AST_BRIDGE_CHANNEL_FLAG_LONELY = (1 << 1),
+       /*! This channel cannot be moved to another bridge. */
+       AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE = (1 << 2),
 };
 
 /*! \brief Built in DTMF features */
@@ -52,32 +78,66 @@ enum ast_bridge_builtin_feature {
         * AST_BRIDGE_CHANNEL_STATE_END.
         */
        AST_BRIDGE_BUILTIN_HANGUP,
+       /*!
+        * DTMF based Park
+        *
+        * \details The bridge is parked and the channel hears the
+        * parking slot to which it was parked.
+        */
+       AST_BRIDGE_BUILTIN_PARKCALL,
+/* BUGBUG does Monitor and/or MixMonitor require a two party bridge?  MixMonitor is used by ConfBridge so maybe it doesn't. */
+       /*!
+        * DTMF one-touch-record toggle using Monitor app.
+        *
+        * \note Only valid on two party bridges.
+        */
+       AST_BRIDGE_BUILTIN_AUTOMON,
+       /*!
+        * DTMF one-touch-record toggle using MixMonitor app.
+        *
+        * \note Only valid on two party bridges.
+        */
+       AST_BRIDGE_BUILTIN_AUTOMIXMON,
 
        /*! End terminator for list of built in features. Must remain last. */
        AST_BRIDGE_BUILTIN_END
 };
 
+enum ast_bridge_builtin_interval {
+       /*! Apply Call Duration Limits */
+       AST_BRIDGE_BUILTIN_INTERVAL_LIMITS,
+
+       /*! End terminator for list of built in interval features. Must remain last. */
+       AST_BRIDGE_BUILTIN_INTERVAL_END
+};
+
 struct ast_bridge;
 struct ast_bridge_channel;
 
 /*!
- * \brief Features hook callback type
+ * \brief Hook callback type
  *
  * \param bridge The bridge that the channel is part of
  * \param bridge_channel Channel executing the feature
  * \param hook_pvt Private data passed in when the hook was created
  *
- * \retval 0 success
- * \retval -1 failure
+ * For interval hooks:
+ * \retval 0 Setup to fire again at the last interval.
+ * \retval positive Setup to fire again at the new interval returned.
+ * \retval -1 Remove the callback hook.
+ *
+ * For other hooks:
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
  */
-typedef int (*ast_bridge_features_hook_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt);
+typedef int (*ast_bridge_hook_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt);
 
 /*!
- * \brief Features hook pvt destructor callback
+ * \brief Hook pvt destructor callback
  *
- * \param hook_pvt Private data passed in when the hook was create to destroy
+ * \param hook_pvt Private data passed in when the hook was created to destroy
  */
-typedef void (*ast_bridge_features_hook_pvt_destructor)(void *hook_pvt);
+typedef void (*ast_bridge_hook_pvt_destructor)(void *hook_pvt);
 
 /*!
  * \brief Talking indicator callback
@@ -86,13 +146,13 @@ typedef void (*ast_bridge_features_hook_pvt_destructor)(void *hook_pvt);
  * to receive updates on when a bridge_channel has started and stopped
  * talking
  *
- * \param bridge The bridge that the channel is part of
  * \param bridge_channel Channel executing the feature
+ * \param talking TRUE if the channel is now talking
  *
  * \retval 0 success
  * \retval -1 failure
  */
-typedef void (*ast_bridge_talking_indicate_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data);
+typedef void (*ast_bridge_talking_indicate_callback)(struct ast_bridge_channel *bridge_channel, void *pvt_data, int talking);
 
 
 typedef void (*ast_bridge_talking_indicate_destructor)(void *pvt_data);
@@ -100,30 +160,68 @@ typedef void (*ast_bridge_talking_indicate_destructor)(void *pvt_data);
 /*!
  * \brief Maximum length of a DTMF feature string
  */
-#define MAXIMUM_DTMF_FEATURE_STRING 8
+#define MAXIMUM_DTMF_FEATURE_STRING (11 + 1)
 
-/*!
- * \brief Structure that is the essence of a features hook
- */
-struct ast_bridge_features_hook {
+/*! Extra parameters for a DTMF feature hook. */
+struct ast_bridge_hook_dtmf {
        /*! DTMF String that is examined during a feature hook lookup */
-       char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
-       /*! Callback that is called when DTMF string is matched */
-       ast_bridge_features_hook_callback callback;
+       char code[MAXIMUM_DTMF_FEATURE_STRING];
+};
+
+/*! Extra parameters for an interval timer hook. */
+struct ast_bridge_hook_timer {
+       /*! Time at which the hook should actually trip */
+       struct timeval trip_time;
+       /*! Heap index for interval hook */
+       ssize_t heap_index;
+       /*! Interval that the hook should execute at in milliseconds */
+       unsigned int interval;
+       /*! Sequence number for the hook to ensure expiration ordering */
+       unsigned int seqno;
+};
+
+/* BUGBUG Need to be able to selectively remove DTMF, hangup, and interval hooks. */
+/*! \brief Structure that is the essence of a feature hook. */
+struct ast_bridge_hook {
+       /*! Linked list information */
+       AST_LIST_ENTRY(ast_bridge_hook) entry;
+       /*! Callback that is called when hook is tripped */
+       ast_bridge_hook_callback callback;
        /*! Callback to destroy hook_pvt data right before destruction. */
-       ast_bridge_features_hook_pvt_destructor destructor;
+       ast_bridge_hook_pvt_destructor destructor;
        /*! Unique data that was passed into us */
        void *hook_pvt;
-       /*! Linked list information */
-       AST_LIST_ENTRY(ast_bridge_features_hook) entry;
+       /*! TRUE if the hook is removed when the channel is pulled from the bridge. */
+       unsigned int remove_on_pull:1;
+       /*! Extra hook parameters. */
+       union {
+               /*! Extra parameters for a DTMF feature hook. */
+               struct ast_bridge_hook_dtmf dtmf;
+               /*! Extra parameters for an interval timer hook. */
+               struct ast_bridge_hook_timer timer;
+       } parms;
 };
 
+#define BRIDGE_FEATURES_INTERVAL_RATE 10
+
 /*!
  * \brief Structure that contains features information
  */
 struct ast_bridge_features {
-       /*! Attached DTMF based feature hooks */
-       AST_LIST_HEAD_NOLOCK(, ast_bridge_features_hook) hooks;
+       /*! Attached DTMF feature hooks */
+       struct ao2_container *dtmf_hooks;
+       /*! Attached hangup interception hooks container */
+       struct ao2_container *hangup_hooks;
+       /*! Attached bridge channel join interception hooks container */
+       struct ao2_container *join_hooks;
+       /*! Attached bridge channel leave interception hooks container */
+       struct ao2_container *leave_hooks;
+       /*! Attached interval hooks */
+       struct ast_heap *interval_hooks;
+       /*! Used to determine when interval based features should be checked */
+       struct ast_timer *interval_timer;
+       /*! Limits feature data */
+       struct ast_bridge_features_limits *limits;
        /*! Callback to indicate when a bridge channel has started and stopped talking */
        ast_bridge_talking_indicate_callback talker_cb;
        /*! Callback to destroy any pvt data stored for the talker. */
@@ -132,19 +230,21 @@ struct ast_bridge_features {
        void *talker_pvt_data;
        /*! Feature flags that are enabled */
        struct ast_flags feature_flags;
-       /*! Bit to indicate that the feature_flags and hook list is setup */
+       /*! Used to assign the sequence number to the next interval hook added. */
+       unsigned int interval_sequence;
+       /*! TRUE if feature_flags is setup */
        unsigned int usable:1;
-       /*! Bit to indicate whether the channel/bridge is muted or not */
+       /*! TRUE if the channel/bridge is muted. */
        unsigned int mute:1;
-       /*! Bit to indicate whether DTMF should be passed into the bridge tech or not.  */
+       /*! TRUE if DTMF should be passed into the bridge tech.  */
        unsigned int dtmf_passthrough:1;
-
 };
 
 /*!
  * \brief Structure that contains configuration information for the blind transfer built in feature
  */
 struct ast_bridge_features_blind_transfer {
+/* BUGBUG the context should be figured out based upon TRANSFER_CONTEXT channel variable of A/B or current context of A/B. More appropriate for when channel moved to other bridges. */
        /*! Context to use for transfers */
        char context[AST_MAX_CONTEXT];
 };
@@ -153,14 +253,38 @@ struct ast_bridge_features_blind_transfer {
  * \brief Structure that contains configuration information for the attended transfer built in feature
  */
 struct ast_bridge_features_attended_transfer {
+/* BUGBUG the context should be figured out based upon TRANSFER_CONTEXT channel variable of A/B or current context of A/B. More appropriate for when channel moved to other bridges. */
+       /*! Context to use for transfers */
+       char context[AST_MAX_CONTEXT];
        /*! DTMF string used to abort the transfer */
        char abort[MAXIMUM_DTMF_FEATURE_STRING];
        /*! DTMF string used to turn the transfer into a three way conference */
        char threeway[MAXIMUM_DTMF_FEATURE_STRING];
        /*! DTMF string used to complete the transfer */
        char complete[MAXIMUM_DTMF_FEATURE_STRING];
-       /*! Context to use for transfers */
-       char context[AST_MAX_CONTEXT];
+};
+
+/*!
+ * \brief Structure that contains configuration information for the limits feature
+ */
+struct ast_bridge_features_limits {
+       /*! Maximum duration that the channel is allowed to be in the bridge (specified in milliseconds) */
+       unsigned int duration;
+       /*! Duration into the call when warnings should begin (specified in milliseconds or 0 to disable) */
+       unsigned int warning;
+       /*! Interval between the warnings (specified in milliseconds or 0 to disable) */
+       unsigned int frequency;
+
+       AST_DECLARE_STRING_FIELDS(
+               /*! Sound file to play when the maximum duration is reached (if empty, then nothing will be played) */
+               AST_STRING_FIELD(duration_sound);
+               /*! Sound file to play when the warning time is reached (if empty, then the remaining time will be played) */
+               AST_STRING_FIELD(warning_sound);
+               /*! Sound file to play when the call is first entered (if empty, then the remaining time will be played) */
+               AST_STRING_FIELD(connect_sound);
+       );
+       /*! Time when the bridge will be terminated by the limits feature */
+       struct timeval quitting_time;
 };
 
 /*!
@@ -182,7 +306,7 @@ struct ast_bridge_features_attended_transfer {
  * This registers the function bridge_builtin_attended_transfer as the function responsible for the built in
  * attended transfer feature.
  */
-int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_features_hook_callback callback, const char *dtmf);
+int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_hook_callback callback, const char *dtmf);
 
 /*!
  * \brief Unregister a handler for a built in feature
@@ -203,13 +327,154 @@ int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_br
 int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
 
 /*!
- * \brief Attach a custom hook to a bridge features structure
+ * \brief Attach interval hooks to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param limits Configured limits applicable to the channel
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+typedef int (*ast_bridge_builtin_set_limits_fn)(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull);
+
+/*!
+ * \brief Register a handler for a built in interval feature
+ *
+ * \param interval The interval feature that the handler will be responsible for
+ * \param callback the Callback function that will handle it
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * ast_bridge_interval_register(AST_BRIDGE_BUILTIN_INTERVAL_LIMITS, bridge_builtin_set_limits);
+ * \endcode
+ *
+ * This registers the function bridge_builtin_set_limits as the function responsible for the built in
+ * duration limit feature.
+ */
+int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback);
+
+/*!
+ * \brief Unregisters a handler for a built in interval feature
+ *
+ * \param interval the interval feature to unregister
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * ast_bridge_interval_unregister(AST_BRIDGE_BULTIN_INTERVAL_LIMITS)
+ * /endcode
+ *
+ * This unregisters the function that is handling the built in duration limit feature.
+ */
+int ast_bridge_interval_unregister(enum ast_bridge_builtin_interval interval);
+
+/*!
+ * \brief Attach a bridge channel join hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_join_hook(&features, join_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call join_callback when a
+ * channel successfully joins the bridging system.  A pointer to
+ * useful data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_join_hook(struct ast_bridge_features *features,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull);
+
+/*!
+ * \brief Attach a bridge channel leave hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_leave_hook(&features, leave_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call leave_callback when a
+ * channel successfully leaves the bridging system.  A pointer
+ * to useful data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_leave_hook(struct ast_bridge_features *features,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull);
+
+/*!
+ * \brief Attach a hangup hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_hangup_hook(&features, hangup_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call hangup_callback if a
+ * channel that has this hook hangs up.  A pointer to useful
+ * data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_hangup_hook(struct ast_bridge_features *features,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull);
+
+/*!
+ * \brief Attach a DTMF hook to a bridge features structure
  *
  * \param features Bridge features structure
  * \param dtmf DTMF string to be activated upon
  * \param callback Function to execute upon activation
  * \param hook_pvt Unique data
  * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -219,21 +484,48 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
  * \code
  * struct ast_bridge_features features;
  * ast_bridge_features_init(&features);
- * ast_bridge_features_hook(&features, "#", pound_callback, NULL, NULL);
+ * ast_bridge_dtmf_hook(&features, "#", pound_callback, NULL, NULL, 0);
  * \endcode
  *
  * This makes the bridging core call pound_callback if a channel that has this
  * feature structure inputs the DTMF string '#'. A pointer to useful data may be
  * provided to the hook_pvt parameter.
- *
- * \note It is important that the callback set the bridge channel state back to
- *       AST_BRIDGE_CHANNEL_STATE_WAIT or the bridge thread will not service the channel.
  */
-int ast_bridge_features_hook(struct ast_bridge_features *features,
+int ast_bridge_dtmf_hook(struct ast_bridge_features *features,
        const char *dtmf,
-       ast_bridge_features_hook_callback callback,
+       ast_bridge_hook_callback callback,
        void *hook_pvt,
-       ast_bridge_features_hook_pvt_destructor destructor);
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull);
+
+/*!
+ * \brief attach an interval hook to a bridge features structure
+ *
+ * \param features Bridge features structure
+ * \param interval The interval that the hook should execute at in milliseconds
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_interval_hook(&features, 1000, playback_callback, NULL, NULL, 0);
+ * \endcode
+ *
+ * This makes the bridging core call playback_callback every second. A pointer to useful
+ * data may be provided to the hook_pvt parameter.
+ */
+int ast_bridge_interval_hook(struct ast_bridge_features *features,
+       unsigned int interval,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull);
 
 /*!
  * \brief Set a callback on the features structure to receive talking notifications on.
@@ -257,6 +549,8 @@ void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
  * \param feature Feature to enable
  * \param dtmf Optionally the DTMF stream to trigger the feature, if not specified it will be the default
  * \param config Configuration structure unique to the built in type
+ * \param destructor Optional destructor callback for config data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
  *
  * \retval 0 on success
  * \retval -1 on failure
@@ -266,19 +560,72 @@ void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
  * \code
  * struct ast_bridge_features features;
  * ast_bridge_features_init(&features);
- * ast_bridge_features_enable(&features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, NULL);
+ * ast_bridge_features_enable(&features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, NULL, NULL, 0);
  * \endcode
  *
  * This enables the attended transfer DTMF option using the default DTMF string. An alternate
  * string may be provided using the dtmf parameter. Internally this is simply setting up a hook
  * to a built in feature callback function.
  */
-int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config);
+int ast_bridge_features_enable(struct ast_bridge_features *features,
+       enum ast_bridge_builtin_feature feature,
+       const char *dtmf,
+       void *config,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull);
+
+/*!
+ * \brief Constructor function for ast_bridge_features_limits
+ *
+ * \param limits pointer to a ast_bridge_features_limits struct that has been allocted, but not initialized
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_bridge_features_limits_construct(struct ast_bridge_features_limits *limits);
+
+/*!
+ * \brief Destructor function for ast_bridge_features_limits
+ *
+ * \param limits pointer to an ast_bridge_features_limits struct that needs to be destroyed
+ *
+ * This function does not free memory allocated to the ast_bridge_features_limits struct, it only frees elements within the struct.
+ * You must still call ast_free on the the struct if you allocated it with malloc.
+ */
+void ast_bridge_features_limits_destroy(struct ast_bridge_features_limits *limits);
 
 /*!
- * \brief Set a flag on a bridge features structure
+ * \brief Limit the amount of time a channel may stay in the bridge and optionally play warning messages as time runs out
  *
  * \param features Bridge features structure
+ * \param limits Configured limits applicable to the channel
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_bridge_features features;
+ * struct ast_bridge_features_limits limits;
+ * ast_bridge_features_init(&features);
+ * ast_bridge_features_limits_construct(&limits);
+ * ast_bridge_features_set_limits(&features, &limits, 0);
+ * ast_bridge_features_limits_destroy(&limits);
+ * \endcode
+ *
+ * This sets the maximum time the channel can be in the bridge to 10 seconds and does not play any warnings.
+ *
+ * \note This API call can only be used on a features structure that will be used in association with a bridge channel.
+ * \note The ast_bridge_features_limits structure must remain accessible for the lifetime of the features structure.
+ */
+int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull);
+
+/*!
+ * \brief Set a flag on a bridge channel features structure
+ *
+ * \param features Bridge channel features structure
  * \param flag Flag to enable
  *
  * \return Nothing
@@ -288,13 +635,13 @@ int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_br
  * \code
  * struct ast_bridge_features features;
  * ast_bridge_features_init(&features);
- * ast_bridge_features_set_flag(&features, AST_BRIDGE_FLAG_DISSOLVE);
+ * ast_bridge_features_set_flag(&features, AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP);
  * \endcode
  *
- * This sets the AST_BRIDGE_FLAG_DISSOLVE feature to be enabled on the features structure
- * 'features'.
+ * This sets the AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP feature
+ * to be enabled on the features structure 'features'.
  */
-void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag);
+void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag);
 
 /*!
  * \brief Initialize bridge features structure
@@ -341,26 +688,39 @@ int ast_bridge_features_init(struct ast_bridge_features *features);
 void ast_bridge_features_cleanup(struct ast_bridge_features *features);
 
 /*!
- * \brief Play a DTMF stream into a bridge, optionally not to a given channel
+ * \brief Allocate a new bridge features struct.
+ * \since 12.0.0
  *
- * \param bridge Bridge to play stream into
- * \param dtmf DTMF to play
- * \param chan Channel to optionally not play to
+ * Example usage:
  *
- * \retval 0 on success
- * \retval -1 on failure
+ * \code
+ * struct ast_bridge_features *features;
+ * features = ast_bridge_features_new();
+ * ast_bridge_features_destroy(features);
+ * \endcode
+ *
+ * \retval features New allocated features struct.
+ * \retval NULL on error.
+ */
+struct ast_bridge_features *ast_bridge_features_new(void);
+
+/*!
+ * \brief Destroy an allocated bridge features struct.
+ * \since 12.0.0
+ *
+ * \param features Bridge features structure
  *
  * Example usage:
  *
  * \code
- * ast_bridge_dtmf_stream(bridge, "0123456789", NULL);
+ * struct ast_bridge_features *features;
+ * features = ast_bridge_features_new();
+ * ast_bridge_features_destroy(features);
  * \endcode
  *
- * This sends the DTMF digits '0123456789' to all channels in the bridge pointed to
- * by the bridge pointer. Optionally a channel may be excluded by passing it's channel pointer
- * using the chan parameter.
+ * \return Nothing
  */
-int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct ast_channel *chan);
+void ast_bridge_features_destroy(struct ast_bridge_features *features);
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
diff --git a/include/asterisk/bridging_roles.h b/include/asterisk/bridging_roles.h
new file mode 100644 (file)
index 0000000..3acb67f
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Channel Bridging Roles API
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#ifndef _ASTERISK_BRIDGING_ROLES_H
+#define _ASTERISK_BRIDGING_ROLES_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/linkedlists.h"
+
+#define AST_ROLE_LEN 32
+
+/*!
+ * \brief Adds a bridge role to a channel
+ *
+ * \param chan Acquirer of the requested role
+ * \param role_name Name of the role being attached
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_add_bridge_role(struct ast_channel *chan, const char *role_name);
+
+/*!
+ * \brief Removes a bridge role from a channel
+ *
+ * \param chan Channel the role is being removed from
+ * \param role_name Name of the role being removed
+ */
+void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_name);
+
+/*!
+ * \brief Set a role option on a channel
+ * \param channel Channel receiving the role option
+ * \param role_name Role the role option is applied to
+ * \param option Name of the option
+ * \param value Value of the option
+ *
+ * \param 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value);
+
+/*!
+ * \brief Check to see if a bridge channel inherited a specific role from its channel
+ *
+ * \param bridge_channel The bridge channel being checked
+ * \param role_name Name of the role being checked
+ *
+ * \retval 0 The bridge channel does not have the requested role
+ * \retval 1 The bridge channel does have the requested role
+ *
+ * \note Before a bridge_channel can effectively check roles against a bridge, ast_bridge_roles_bridge_channel_establish_roles
+ *       should be called on the bridge_channel so that roles and their respective role options can be copied from the channel
+ *       datastore into the bridge_channel roles list. Otherwise this function will just return 0 because the list will be NULL.
+ */
+int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name);
+
+/*!
+ * \brief Retrieve the value of a requested role option from a bridge channel
+ *
+ * \param bridge_channel The bridge channel we are retrieving the option from
+ * \param role_name Name of the role the option will be retrieved from
+ * \param option Name of the option we are retrieving the value of
+ *
+ * \retval NULL If either the role does not exist on the bridge_channel or the role does exist but the option has not been set
+ * \retval The value of the option
+ *
+ * \note See ast_bridge_roles_channel_set_role_option note about the need to call ast_bridge_roles_bridge_channel_establish_roles.
+ *
+ * \note The returned character pointer is only valid as long as the bridge_channel is guaranteed to be alive and hasn't had
+ *       ast_bridge_roles_bridge_channel_clear_roles called against it (as this will free all roles and role options in the bridge
+ *       channel). If you need this value after one of these destruction events occurs, you must make a local copy while it is
+ *       still valid.
+ */
+const char *ast_bridge_channel_get_role_option(struct ast_bridge_channel *bridge_channel, const char *role_name, const char *option);
+
+/*!
+ * \brief Clone the roles from a bridge_channel's attached ast_channel onto the bridge_channel's role list
+ *
+ * \param bridge_channel The bridge channel that we are preparing
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ *
+ * \details
+ * This function should always be called when the bridge_channel binds to an ast_channel at some point before the bridge_channel
+ * joins or is imparted onto a bridge. Failure to do so will result in an empty role list. While the list remains established,
+ * changes to roles on the ast_channel will not propogate to the bridge channel and roles can not be re-established on the bridge
+ * channel without first clearing the roles with ast_bridge_roles_bridge_channel_clear_roles.
+ */
+int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \brief Clear all roles from a bridge_channel's role list
+ *
+ * \param bridge_channel the bridge channel that we are scrubbing
+ *
+ * \details
+ * If roles are already established on a bridge channel, ast_bridge_roles_bridge_channel_establish_roles will fail unconditionally
+ * without changing any roles. In order to update a bridge channel's roles, they must first be cleared from the bridge channel using
+ * this function.
+ *
+ * \note
+ * ast_bridge_roles_bridge_channel_clear_roles also serves as the destructor for the role list of a bridge channel.
+ */
+void ast_bridge_channel_clear_roles(struct ast_bridge_channel *bridge_channel);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_BRIDGING_ROLES_H */
index d09c4bec45384ad28ce4ef03bf46f84efe60aa53..2bb2170f0146b2799bf52a33293a6aedaa8bb01e 100644 (file)
 extern "C" {
 #endif
 
-/*! \brief Preference for choosing the bridge technology */
+/*!
+ * \brief Base preference values for choosing a bridge technology.
+ *
+ * \note Higher is more preference.
+ */
 enum ast_bridge_preference {
-       /*! Bridge technology should have high precedence over other bridge technologies */
-       AST_BRIDGE_PREFERENCE_HIGH = 0,
-       /*! Bridge technology is decent, not the best but should still be considered over low */
-       AST_BRIDGE_PREFERENCE_MEDIUM,
-       /*! Bridge technology is low, it should not be considered unless it is absolutely needed */
-       AST_BRIDGE_PREFERENCE_LOW,
+       AST_BRIDGE_PREFERENCE_BASE_HOLDING  = 50,
+       AST_BRIDGE_PREFERENCE_BASE_EARLY    = 100,
+       AST_BRIDGE_PREFERENCE_BASE_NATIVE   = 90,
+       AST_BRIDGE_PREFERENCE_BASE_1TO1MIX  = 50,
+       AST_BRIDGE_PREFERENCE_BASE_MULTIMIX = 10,
 };
 
 /*!
@@ -49,31 +52,68 @@ struct ast_bridge_technology {
        uint32_t capabilities;
        /*! Preference level that should be used when determining whether to use this bridge technology or not */
        enum ast_bridge_preference preference;
-       /*! Callback for when a bridge is being created */
+       /*!
+        * \brief Callback for when a bridge is being created.
+        *
+        * \retval 0 on success
+        * \retval -1 on failure
+        *
+        * \note On entry, bridge may or may not already be locked.
+        * However, it can be accessed as if it were locked.
+        */
        int (*create)(struct ast_bridge *bridge);
-       /*! Callback for when a bridge is being destroyed */
-       int (*destroy)(struct ast_bridge *bridge);
-       /*! Callback for when a channel is being added to a bridge */
+       /*!
+        * \brief Callback for when a bridge is being destroyed
+        *
+        * \note On entry, bridge must NOT be locked.
+        */
+       void (*destroy)(struct ast_bridge *bridge);
+       /*!
+        * \brief Callback for when a channel is being added to a bridge.
+        *
+        * \retval 0 on success
+        * \retval -1 on failure
+        *
+        * \note On entry, bridge is already locked.
+        */
        int (*join)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-       /*! Callback for when a channel is leaving a bridge */
-       int (*leave)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-       /*! Callback for when a channel is suspended from the bridge */
+       /*!
+        * \brief Callback for when a channel is leaving a bridge
+        *
+        * \note On entry, bridge is already locked.
+        */
+       void (*leave)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+       /*!
+        * \brief Callback for when a channel is suspended from the bridge
+        *
+        * \note On entry, bridge is already locked.
+        */
        void (*suspend)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-       /*! Callback for when a channel is unsuspended from the bridge */
+       /*!
+        * \brief Callback for when a channel is unsuspended from the bridge
+        *
+        * \note On entry, bridge is already locked.
+        */
        void (*unsuspend)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
-       /*! Callback to see if a channel is compatible with the bridging technology */
-       int (*compatible)(struct ast_bridge_channel *bridge_channel);
-       /*! Callback for writing a frame into the bridging technology */
-       enum ast_bridge_write_result (*write)(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame);
-       /*! Callback for when a file descriptor trips */
-       int (*fd)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int fd);
-       /*! Callback for replacement thread function */
-       int (*thread)(struct ast_bridge *bridge);
-       /*! Callback for poking a bridge thread */
-       int (*poke)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+       /*!
+        * \brief Callback to see if the bridge is compatible with the bridging technology.
+        *
+        * \retval 0 if not compatible
+        * \retval non-zero if compatible
+        */
+       int (*compatible)(struct ast_bridge *bridge);
+       /*!
+        * \brief Callback for writing a frame into the bridging technology.
+        *
+        * \retval 0 on success
+        * \retval -1 on failure
+        *
+        * \note On entry, bridge is already locked.
+        */
+       int (*write)(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame);
        /*! Formats that the bridge technology supports */
        struct ast_format_cap *format_capabilities;
-       /*! Bit to indicate whether the bridge technology is currently suspended or not */
+       /*! TRUE if the bridge technology is currently suspended. */
        unsigned int suspended:1;
        /*! Module this bridge technology belongs to. Is used for reference counting when creating/destroying a bridge. */
        struct ast_module *mod;
@@ -125,27 +165,6 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
  */
 int ast_bridge_technology_unregister(struct ast_bridge_technology *technology);
 
-/*!
- * \brief Feed notification that a frame is waiting on a channel into the bridging core
- *
- * \param bridge The bridge that the notification should influence
- * \param bridge_channel Bridge channel the notification was received on (if known)
- * \param chan Channel the notification was received on (if known)
- * \param outfd File descriptor that the notification was received on (if known)
- *
- * Example usage:
- *
- * \code
- * ast_bridge_handle_trip(bridge, NULL, chan, -1);
- * \endcode
- *
- * This tells the bridging core that a frame has been received on
- * the channel pointed to by chan and that it should be read and handled.
- *
- * \note This should only be used by bridging technologies.
- */
-void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd);
-
 /*!
  * \brief Lets the bridging indicate when a bridge channel has stopped or started talking.
  *
@@ -155,12 +174,11 @@ void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel
  * application, this function has been created to allow the bridging technology to communicate
  * that information with the bridging core.
  *
- * \param bridge The bridge that the channel is a part of.
  * \param bridge_channel The bridge channel that has either started or stopped talking.
  * \param started_talking set to 1 when this indicates the channel has started talking set to 0
  * when this indicates the channel has stopped talking.
  */
-void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking);
+void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking);
 
 /*!
  * \brief Suspend a bridge technology from consideration
index cf2f2199669b0f7dd2e5abaffcf76ee798fa51de..d8101cddf93a39d9a05fa6f54a1d2ca4fc29dba7 100644 (file)
@@ -1485,10 +1485,12 @@ int ast_cc_agent_set_interfaces_chanvar(struct ast_channel *chan);
  * \verbatim extension@context \endverbatim as a starting point
  *
  * \details
- * The CC_INTERFACES channel variable will have the interfaces that should be
- * called back for a specific PBX instance. This version of the function is used
- * mainly by chan_local, wherein we need to set CC_INTERFACES based on an extension
- * and context that appear in the middle of the tree of dialed interfaces
+ * The CC_INTERFACES channel variable will have the interfaces
+ * that should be called back for a specific PBX instance.  This
+ * version of the function is used mainly by local channels,
+ * wherein we need to set CC_INTERFACES based on an extension
+ * and context that appear in the middle of the tree of dialed
+ * interfaces.
  *
  * \note
  * This function will lock the channel as well as the list of monitors stored
index 939c8db513dc766a8be8d272570a5d4be24569a4..91373cfbda3250196fb4de41a164749d674797e2 100644 (file)
@@ -643,6 +643,7 @@ struct ast_channel_tech {
        /*! \brief Handle an exception, reading a frame */
        struct ast_frame * (* const exception)(struct ast_channel *chan);
 
+/* BUGBUG this tech callback is to be removed. */
        /*! \brief Bridge two channels of the same type together */
        enum ast_bridge_result (* const bridge)(struct ast_channel *c0, struct ast_channel *c1, int flags,
                                                struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
@@ -671,6 +672,7 @@ struct ast_channel_tech {
        /*! \brief Write a text frame, in standard format */
        int (* const write_text)(struct ast_channel *chan, struct ast_frame *frame);
 
+/* BUGBUG this tech callback is to be removed. */
        /*! \brief Find bridged channel */
        struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge);
 
@@ -686,6 +688,7 @@ struct ast_channel_tech {
         */
        int (* func_channel_write)(struct ast_channel *chan, const char *function, char *data, const char *value);
 
+/* BUGBUG this tech callback is to be removed. */
        /*! \brief Retrieve base channel (agent and local) */
        struct ast_channel* (* get_base_channel)(struct ast_channel *chan);
 
@@ -896,10 +899,6 @@ enum {
         *  a message aimed at preventing a subsequent hangup exten being run at the pbx_run
         *  level */
        AST_FLAG_BRIDGE_HANGUP_RUN = (1 << 17),
-       /*! This flag indicates that the hangup exten should NOT be run when the
-        *  bridge terminates, this will allow the hangup in the pbx loop to be run instead.
-        *  */
-       AST_FLAG_BRIDGE_HANGUP_DONT = (1 << 18),
        /*! Disable certain workarounds.  This reintroduces certain bugs, but allows
         *  some non-traditional dialplans (like AGI) to continue to function.
         */
@@ -928,7 +927,6 @@ enum {
        AST_FEATURE_AUTOMON =      (1 << 4),
        AST_FEATURE_PARKCALL =     (1 << 5),
        AST_FEATURE_AUTOMIXMON =   (1 << 6),
-       AST_FEATURE_NO_H_EXTEN =   (1 << 7),
        AST_FEATURE_WARNING_ACTIVE = (1 << 8),
 };
 
@@ -4038,6 +4036,9 @@ void ast_channel_timingfunc_set(struct ast_channel *chan, ast_timing_func_t valu
 struct ast_bridge *ast_channel_internal_bridge(const struct ast_channel *chan);
 void ast_channel_internal_bridge_set(struct ast_channel *chan, struct ast_bridge *value);
 
+struct ast_bridge_channel *ast_channel_internal_bridge_channel(const struct ast_channel *chan);
+void ast_channel_internal_bridge_channel_set(struct ast_channel *chan, struct ast_bridge_channel *value);
+
 struct ast_channel *ast_channel_internal_bridged_channel(const struct ast_channel *chan);
 void ast_channel_internal_bridged_channel_set(struct ast_channel *chan, struct ast_channel *value);
 
@@ -4138,4 +4139,69 @@ struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan);
  */
 struct stasis_topic *ast_channel_topic(struct ast_channel *chan);
 
+/*!
+ * \brief Get the bridge associated with a channel
+ * \since 12.0.0
+ *
+ * \param chan The channel whose bridge we want
+ *
+ * \details
+ * The bridge returned has its reference count incremented.  Use
+ * ao2_cleanup() or ao2_ref() in order to decrement the
+ * reference count when you are finished with the bridge.
+ *
+ * \note This function expects the channel to be locked prior to
+ * being called and will not grab the channel lock.
+ *
+ * \retval NULL No bridge present on the channel
+ * \retval non-NULL The bridge the channel is in
+ */
+struct ast_bridge *ast_channel_get_bridge(const struct ast_channel *chan);
+
+/*!
+ * \brief Determine if a channel is in a bridge
+ * \since 12.0.0
+ *
+ * \param chan The channel to test
+ *
+ * \note This function expects the channel to be locked prior to
+ * being called and will not grab the channel lock.
+ *
+ * \retval 0 The channel is not bridged
+ * \retval non-zero The channel is bridged
+ */
+int ast_channel_is_bridged(const struct ast_channel *chan);
+
+/*!
+ * \brief Get the channel's bridge peer only if the bridge is two-party.
+ * \since 12.0.0
+ *
+ * \param chan Channel desiring the bridge peer channel.
+ *
+ * \note The returned peer channel is the current peer in the
+ * bridge when called.
+ *
+ * \retval NULL Channel not in a bridge or the bridge is not two-party.
+ * \retval non-NULL Reffed peer channel at time of calling.
+ */
+struct ast_channel *ast_channel_bridge_peer(struct ast_channel *chan);
+
+/*!
+ * \brief Get a reference to the channel's bridge pointer.
+ * \since 12.0.0
+ *
+ * \param chan The channel whose bridge channel is desired
+ *
+ * \note This increases the reference count of the bridge_channel.
+ * Use ao2_ref() or ao2_cleanup() to decrement the refcount when
+ * you are finished with it.
+ *
+ * \note It is expected that the channel is locked prior to
+ * placing this call.
+ *
+ * \retval NULL The channel has no bridge_channel
+ * \retval non-NULL A reference to the bridge_channel
+ */
+struct ast_bridge_channel *ast_channel_get_bridge_channel(struct ast_channel *chan);
+
 #endif /* _ASTERISK_CHANNEL_H */
index 64d8d5089326ad55a78d8a104ffd1329567a6ec7..739d83130032bacd53fa10b836aae3382f3a6786 100644 (file)
@@ -575,6 +575,16 @@ int __aco_option_register(struct aco_info *info, const char *name, enum aco_matc
  */
 int aco_option_register_deprecated(struct aco_info *info, const char *name, struct aco_type **types, const char *aliased_to);
 
+/*!
+ * \brief Read the flags of a config option - useful when using a custom callback for a config option
+ * \since 12
+ *
+ * \param option Pointer to the aco_option struct
+ *
+ * \retval value of the flags on the config option
+ */
+unsigned int aco_option_get_flags(const struct aco_option *option);
+
 /*! \note  Everything below this point is to handle converting varargs
  * containing field names, to varargs containing a count of args, followed
  * by the offset of each of the field names in the struct type that is
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
new file mode 100644 (file)
index 0000000..693c93b
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Local proxy channel special access.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_CORE_LOCAL_H
+#define _ASTERISK_CORE_LOCAL_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* Forward declare some struct names */
+struct ast_channel;
+struct ast_bridge;
+struct ast_bridge_features;
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief Get the other local channel in the pair.
+ * \since 12.0.0
+ *
+ * \param ast Local channel to get peer.
+ *
+ * \note On entry, ast must be locked.
+ *
+ * \retval peer reffed on success.
+ * \retval NULL if no peer or error.
+ */
+struct ast_channel *ast_local_get_peer(struct ast_channel *ast);
+
+/*!
+ * \brief Setup the outgoing local channel to join a bridge on ast_call().
+ * \since 12.0.0
+ *
+ * \param ast Either channel of a local channel pair.
+ * \param bridge Bridge to join.
+ * \param swap Channel to swap with when joining.
+ * \param features Bridge features structure.
+ *
+ * \note The features parameter must be NULL or obtained by
+ * ast_bridge_features_new().  You must not dereference features
+ * after calling even if the call fails.
+ *
+ * \note Intended to be called after ast_request() and before
+ * ast_call() on a local channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_local_setup_bridge(struct ast_channel *ast, struct ast_bridge *bridge, struct ast_channel *swap, struct ast_bridge_features *features);
+
+/*!
+ * \brief Setup the outgoing local channel to masquerade into a channel on ast_call().
+ * \since 12.0.0
+ *
+ * \param ast Either channel of a local channel pair.
+ * \param masq Channel to masquerade into.
+ *
+ * \note Intended to be called after ast_request() and before
+ * ast_call() on a local channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_local_setup_masquerade(struct ast_channel *ast, struct ast_channel *masq);
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_CORE_LOCAL_H */
diff --git a/include/asterisk/core_unreal.h b/include/asterisk/core_unreal.h
new file mode 100644 (file)
index 0000000..7cb68f1
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Unreal channel derivative framework.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_CORE_UNREAL_H
+#define _ASTERISK_CORE_UNREAL_H
+
+#include "asterisk/astobj2.h"
+#include "asterisk/channel.h"
+#include "asterisk/abstract_jb.h"
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* Forward declare some struct names */
+struct ast_format_cap;
+struct ast_callid;
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief The base pvt structure for local channel derivatives.
+ *
+ * The unreal pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
+ *
+ * ast_chan owner -> ast_unreal_pvt -> ast_chan chan
+ */
+struct ast_unreal_pvt {
+       struct ast_channel *owner;      /*!< Master Channel - ;1 side */
+       struct ast_channel *chan;       /*!< Outbound channel - ;2 side */
+       struct ast_format_cap *reqcap;  /*!< Requested format capabilities */
+       struct ast_jb_conf jb_conf;     /*!< jitterbuffer configuration */
+       unsigned int flags;             /*!< Private option flags */
+       /*! Base name of the unreal channels.  exten@context or other name. */
+       char name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
+};
+
+#define AST_UNREAL_IS_OUTBOUND(a, b) ((a) == (b)->chan ? 1 : 0)
+
+#define AST_UNREAL_CARETAKER_THREAD (1 << 0) /*!< The ;2 side launched a PBX, was pushed into a bridge, or was masqueraded into an application. */
+#define AST_UNREAL_NO_OPTIMIZATION  (1 << 1) /*!< Do not optimize out the unreal channels */
+#define AST_UNREAL_MOH_INTERCEPT    (1 << 2) /*!< Intercept and act on hold/unhold control frames */
+
+/*!
+ * \brief Send an unreal pvt in with no locks held and get all locks
+ *
+ * \note NO locks should be held prior to calling this function
+ * \note The pvt must have a ref held before calling this function
+ * \note if outchan or outowner is set != NULL after calling this function
+ *       those channels are locked and reffed.
+ * \note Batman.
+ */
+void ast_unreal_lock_all(struct ast_unreal_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner);
+
+/*!
+ * \brief Hangup one end (maybe both ends) of an unreal channel derivative.
+ * \since 12.0.0
+ *
+ * \param p Private channel struct (reffed)
+ * \param ast Channel being hung up.  (locked)
+ *
+ * \note Common hangup code for unreal channels.  Derived
+ * channels will need to deal with any additional resources.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast);
+
+/*! Unreal channel framework struct ast_channel_tech.send_digit_begin callback */
+int ast_unreal_digit_begin(struct ast_channel *ast, char digit);
+
+/*! Unreal channel framework struct ast_channel_tech.send_digit_end callback */
+int ast_unreal_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+
+/*! Unreal channel framework struct ast_channel_tech.answer callback */
+int ast_unreal_answer(struct ast_channel *ast);
+
+/*! Unreal channel framework struct ast_channel_tech.read and struct ast_channel_tech.exception callback */
+struct ast_frame *ast_unreal_read(struct ast_channel *ast);
+
+/*! Unreal channel framework struct ast_channel_tech.write callback */
+int ast_unreal_write(struct ast_channel *ast, struct ast_frame *f);
+
+/*! Unreal channel framework struct ast_channel_tech.indicate callback */
+int ast_unreal_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+
+/*! Unreal channel framework struct ast_channel_tech.fixup callback */
+int ast_unreal_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+/*! Unreal channel framework struct ast_channel_tech.send_html callback */
+int ast_unreal_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
+
+/*! Unreal channel framework struct ast_channel_tech.send_text callback */
+int ast_unreal_sendtext(struct ast_channel *ast, const char *text);
+
+/*! Unreal channel framework struct ast_channel_tech.queryoption callback */
+int ast_unreal_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
+
+/*! Unreal channel framework struct ast_channel_tech.setoption callback */
+int ast_unreal_setoption(struct ast_channel *chan, int option, void *data, int datalen);
+
+/*!
+ * \brief struct ast_unreal_pvt destructor.
+ * \since 12.0.0
+ *
+ * \param vdoomed Object to destroy.
+ *
+ * \return Nothing
+ */
+void ast_unreal_destructor(void *vdoomed);
+
+/*!
+ * \brief Allocate the base unreal struct for a derivative.
+ * \since 12.0.0
+ *
+ * \param size Size of the unreal struct to allocate.
+ * \param destructor Destructor callback.
+ * \param cap Format capabilities to give the unreal private struct.
+ *
+ * \retval pvt on success.
+ * \retval NULL on error.
+ */
+struct ast_unreal_pvt *ast_unreal_alloc(size_t size, ao2_destructor_fn destructor, struct ast_format_cap *cap);
+
+/*!
+ * \brief Create the semi1 and semi2 unreal channels.
+ * \since 12.0.0
+ *
+ * \param p Unreal channel private struct.
+ * \param tech Channel technology to use.
+ * \param semi1_state State to start the semi1(owner) channel in.
+ * \param semi2_state State to start the semi2(outgoing chan) channel in.
+ * \param exten Exten to start the chennels in. (NULL if s)
+ * \param context Context to start the channels in. (NULL if default)
+ * \param requestor Channel requesting creation. (NULL if none)
+ * \param callid Thread callid to use.
+ *
+ * \retval semi1_channel on success.
+ * \retval NULL on error.
+ */
+struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
+       const struct ast_channel_tech *tech, int semi1_state, int semi2_state,
+       const char *exten, const char *context, const struct ast_channel *requestor,
+       struct ast_callid *callid);
+
+/*!
+ * \brief Setup unreal owner and chan channels before initiating call.
+ * \since 12.0.0
+ *
+ * \param semi1 Owner channel of unreal channel pair.
+ * \param semi2 Outgoing channel of unreal channel pair.
+ *
+ * \note On entry, the semi1 and semi2 channels are already locked.
+ *
+ * \return Nothing
+ */
+void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2);
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_CORE_UNREAL_H */
index abb0c2e28d4b853cde6496e712ffdab78f492a25..bedc3a25d9c022135c5954aca0b6a79e5a4560b8 100644 (file)
@@ -120,6 +120,8 @@ enum ast_frame_type {
        AST_FRAME_MODEM,
        /*! DTMF begin event, subclass is the digit */
        AST_FRAME_DTMF_BEGIN,
+       /*! Internal bridge module action. */
+       AST_FRAME_BRIDGE_ACTION,
 };
 #define AST_FRAME_DTMF AST_FRAME_DTMF_END
 
index 52993b55c9785cff426ab1d0dbda684786b86b05..10d525ca731541fa2b6d1a25a676fe2dd62b5ba4 100644 (file)
@@ -228,7 +228,7 @@ struct ast_framehook_interface {
  * provide it during the event and destruction callbacks.  It is entirely up to the
  * application using this API to manage the memory associated with the data pointer.
  *
- * \retval On success, positive id representing this hook on the channel 
+ * \retval On success, non-negative id representing this hook on the channel
  * \retval On failure, -1
  */
 int ast_framehook_attach(struct ast_channel *chan, struct ast_framehook_interface *i);
index e0160c6b576accd66635cb32828816fe83f455db..4e9b8d14a7c2074059a0e35575ccf5cad00217c5 100644 (file)
@@ -347,6 +347,55 @@ struct ast_str *ast_manager_build_channel_state_string_suffix(
 struct ast_str *ast_manager_build_channel_state_string(
                const struct ast_channel_snapshot *snapshot);
 
+/*! \brief Struct representing a snapshot of bridge state */
+struct ast_bridge_snapshot;
+
+/*!
+ * \brief Generate the AMI message body from a bridge snapshot
+ * \since 12
+ *
+ * \param snapshot the bridge snapshot for which to generate an AMI message
+ *                 body
+ *
+ * \retval NULL on error
+ * \retval ast_str* on success (must be ast_freed by caller)
+ */
+struct ast_str *ast_manager_build_bridge_state_string(
+       const struct ast_bridge_snapshot *snapshot,
+       const char *suffix);
+
+/*! \brief Struct containing info for an AMI event to send out. */
+struct ast_manager_event_blob {
+       int event_flags;                /*!< Flags the event should be raised with. */
+       const char *manager_event;      /*!< The event to be raised, should be a string literal. */
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(extra_fields); /*!< Extra fields to include in the event. */
+       );
+};
+
+/*!
+ * \since 12
+ * \brief Construct a \ref snapshot_manager_event.
+ *
+ * \param event_flags Flags the event should be raised with.
+ * \param manager_event The event to be raised, should be a string literal.
+ * \param extra_fields_fmt Format string for extra fields to include.
+ *                         Or NO_EXTRA_FIELDS for no extra fields.
+ *
+ * \return New \ref ast_manager_snapshot_event object.
+ * \return \c NULL on error.
+ */
+struct ast_manager_event_blob *
+__attribute__((format(printf, 3, 4)))
+ast_manager_event_blob_create(
+       int event_flags,
+       const char *manager_event,
+       const char *extra_fields_fmt,
+       ...);
+
+/*! GCC warns about blank or NULL format strings. So, shenanigans! */
+#define NO_EXTRA_FIELDS "%s", ""
+
 /*!
  * \brief Initialize support for AMI channel events.
  * \return 0 on success.
@@ -355,4 +404,12 @@ struct ast_str *ast_manager_build_channel_state_string(
  */
 int manager_channels_init(void);
 
+/*!
+ * \brief Initialize support for AMI channel events.
+ * \return 0 on success.
+ * \return non-zero on error.
+ * \since 12
+ */
+int manager_bridging_init(void);
+
 #endif /* _ASTERISK_MANAGER_H */
diff --git a/include/asterisk/parking.h b/include/asterisk/parking.h
new file mode 100644 (file)
index 0000000..176eddb
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Call Parking API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk/stringfields.h"
+
+#define PARK_APPLICATION "Park"
+
+/*!
+ * \brief Defines the type of parked call message being published
+ * \since 12
+ */
+enum ast_parked_call_event_type {
+       PARKED_CALL = 0,
+       PARKED_CALL_TIMEOUT,
+       PARKED_CALL_GIVEUP,
+       PARKED_CALL_UNPARKED,
+       PARKED_CALL_FAILED,
+};
+
+/*!
+ * \brief A parked call message payload
+ * \since 12
+ */
+struct ast_parked_call_payload {
+       struct ast_channel_snapshot *parkee;             /*!< Snapshot of the channel that is parked */
+       struct ast_channel_snapshot *parker;             /*!< Snapshot of the channel that parked the call */
+       struct ast_channel_snapshot *retriever;          /*!< Snapshot of the channel that retrieved the call */
+       enum ast_parked_call_event_type event_type;      /*!< Reason for issuing the parked call message */
+       long unsigned int timeout;                       /*!< Time remaining before the call times out (seconds ) */
+       long unsigned int duration;                      /*!< How long the parkee has been parked (seconds) */
+       unsigned int parkingspace;                       /*!< Which Parking Space the parkee occupies */
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(parkinglot);                /*!< Name of the parking lot used to park the parkee */
+       );
+};
+
+/*!
+ * \brief Constructor for parked_call_payload objects
+ * \since 12
+ *
+ * \param event_type What kind of parked call event is happening
+ * \param parkee_snapshot channel snapshot of the parkee
+ * \param parker_snapshot channel snapshot of the parker
+ * \param retriever_snapshot channel snapshot of the retriever (NULL allowed)
+ * \param parkinglot name of the parking lot where the parked call is parked
+ * \param parkingspace what numerical parking space the parked call is parked in
+ * \param timeout how long the parked call can remain at the point this snapshot is created before timing out
+ * \param duration how long the parked call has currently been parked
+ *
+ * \retval NULL if the parked call payload can't be allocated
+ * \retval reference to a newly created parked call payload
+ */
+struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_call_event_type event_type,
+               struct ast_channel_snapshot *parkee_snapshot, struct ast_channel_snapshot *parker_snapshot,
+               struct ast_channel_snapshot *retriever_snapshot, const char *parkinglot,
+               unsigned int parkingspace, unsigned long int timeout, unsigned long int duration);
+
+/*!
+ * \brief initialize parking stasis types
+ * \since 12
+ */
+void ast_parking_stasis_init(void);
+
+/*!
+ * \brief disable parking stasis types
+ * \since 12
+ */
+void ast_parking_stasis_disable(void);
+
+/*!
+ * \brief accessor for the parking stasis topic
+ * \since 12
+ *
+ * \retval NULL if the parking topic hasn't been created or has been disabled
+ * \retval a pointer to the parking topic
+ */
+struct stasis_topic *ast_parking_topic(void);
+
+/*!
+ * \brief accessor for the parked call stasis message type
+ * \since 12
+ *
+ * \retval NULL if the parking topic hasn't been created or has been canceled
+ * \retval a pointer to the parked call message type
+ */
+struct stasis_message_type *ast_parked_call_type(void);
+
+/*!
+ * \brief invoke an installable park callback to asynchronously park a bridge_channel in a bridge
+ * \since 12
+ *
+ * \param bridge_channel the bridge channel that initiated parking
+ * \parkee_uuid channel id of the channel being parked
+ * \parker_uuid channel id of the channel that initiated parking
+ * \param app_data string of application data that might be applied to parking
+ */
+void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel,
+       const char *parkee_uuid,
+       const char *parker_uuid,
+       const char *app_data);
+
+typedef int (*ast_park_blind_xfer_fn)(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+       struct ast_exten *park_exten);
+
+/*!
+ * \brief install a callback for handling blind transfers to a parking extension
+ * \since 12
+ *
+ * \param parking_func Function to use for transfers to 'Park' applications
+ */
+void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func);
+
+/*!
+ * \brief uninstall a callback for handling blind transfers to a parking extension
+ * \since 12
+ */
+void ast_uninstall_park_blind_xfer_func(void);
+
+/*!
+ * \brief use the installed park blind xfer func
+ * \since 12
+ *
+ * \param bridge Bridge being transferred from
+ * \param bridge_channel Bridge channel initiating the transfer
+ * \param app_data arguments to the park application
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_park_blind_xfer(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+               struct ast_exten *park_exten);
+
+typedef void (*ast_bridge_channel_park_fn)(struct ast_bridge_channel *parkee, const char *parkee_uuid,
+       const char *parker_uuid, const char *app_data);
+
+/*!
+ * \brief Install a function for ast_bridge_channel_park
+ * \since 12
+ *
+ * \param bridge_channel_park_func function callback to use for ast_bridge_channel_park
+ */
+void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func);
+
+/*!
+ * \brief Uninstall the ast_bridge_channel_park function callback
+ * \since 12
+ */
+void ast_uninstall_bridge_channel_park_func(void);
+
+
+/*!
+ * \brief Determines whether a certain extension is a park application extension or not.
+ * \since 12
+ *
+ * \param exten_str string representation of the extension sought
+ * \param chan channel the extension is sought for
+ * \param context context the extension is sought from
+ *
+ * \retval pointer to the extension if the extension is a park extension
+ * \retval NULL if the extension was not a park extension
+ */
+struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context);
index c8a144b73f8578aefea5183b60d9290ea9ae4009..e2567f50816116d40b27d447971502ed4231f2fa 100644 (file)
@@ -1540,24 +1540,6 @@ int ast_rtp_instance_fd(struct ast_rtp_instance *instance, int rtcp);
  */
 struct ast_rtp_glue *ast_rtp_instance_get_glue(const char *type);
 
-/*!
- * \brief Bridge two channels that use RTP instances
- *
- * \param c0 First channel part of the bridge
- * \param c1 Second channel part of the bridge
- * \param flags Bridging flags
- * \param fo If a frame needs to be passed up it is stored here
- * \param rc Channel that passed the above frame up
- * \param timeoutms How long the channels should be bridged for
- *
- * \retval Bridge result
- *
- * \note This should only be used by channel drivers in their technology declaration.
- *
- * \since 1.8
- */
-enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
-
 /*!
  * \brief Get the other RTP instance that an instance is bridged to
  *
@@ -1578,6 +1560,16 @@ enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct as
  */
 struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance);
 
+/*!
+ * \brief Set the other RTP instance that an instance is bridged to
+ *
+ * \param instance The RTP instance that we want to set the bridged value on
+ * \param bridged The RTP instance they are bridged to
+ *
+ * \since 12
+ */
+void ast_rtp_instance_set_bridged(struct ast_rtp_instance *instance, struct ast_rtp_instance *bridged);
+
 /*!
  * \brief Make two channels compatible for early bridging
  *
diff --git a/include/asterisk/stasis_bridging.h b/include/asterisk/stasis_bridging.h
new file mode 100644 (file)
index 0000000..1b547a7
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef _STASIS_BRIDGING_H
+#define _STASIS_BRIDGING_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include "asterisk/stringfields.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+
+/*!
+ * \brief Structure that contains a snapshot of information about a bridge
+ */
+struct ast_bridge_snapshot {
+       AST_DECLARE_STRING_FIELDS(
+               /*! Immutable bridge UUID. */
+               AST_STRING_FIELD(uniqueid);
+               /*! Bridge technology that is handling the bridge */
+               AST_STRING_FIELD(technology);
+       );
+       /*! AO2 container of bare channel uniqueid strings participating in the bridge.
+        * Allocated from ast_str_container_alloc() */
+       struct ao2_container *channels;
+       /*! Bridge flags to tweak behavior */
+       struct ast_flags feature_flags;
+       /*! Number of channels participating in the bridge */
+       unsigned int num_channels;
+       /*! Number of active channels in the bridge. */
+       unsigned int num_active;
+};
+
+/*!
+ * \since 12
+ * \brief Generate a snapshot of the bridge state. This is an ao2 object, so
+ * ao2_cleanup() to deallocate.
+ *
+ * \param bridge The bridge from which to generate a snapshot
+ *
+ * \retval AO2 refcounted snapshot on success
+ * \retval NULL on error
+ */
+struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge);
+
+/*!
+ * \since 12
+ * \brief Message type for \ref ast_bridge_snapshot.
+ *
+ * \retval Message type for \ref ast_bridge_snapshot.
+ */
+struct stasis_message_type *ast_bridge_snapshot_type(void);
+
+/*!
+ * \since 12
+ * \brief A topic which publishes the events for a particular bridge.
+ *
+ * If the given \a bridge is \c NULL, ast_bridge_topic_all() is returned.
+ *
+ * \param bridge Bridge for which to get a topic or \c NULL.
+ *
+ * \retval Topic for bridge's events.
+ * \retval ast_bridge_topic_all() if \a bridge is \c NULL.
+ */
+struct stasis_topic *ast_bridge_topic(struct ast_bridge *bridge);
+
+/*!
+ * \since 12
+ * \brief A topic which publishes the events for all bridges.
+ * \retval Topic for all bridge events.
+ */
+struct stasis_topic *ast_bridge_topic_all(void);
+
+/*!
+ * \since 12
+ * \brief A caching topic which caches \ref ast_bridge_snapshot messages from
+ * ast_bridge_events_all(void).
+ *
+ * \retval Caching topic for all bridge events.
+ */
+struct stasis_caching_topic *ast_bridge_topic_all_cached(void);
+
+/*!
+ * \since 12
+ * \brief Publish the state of a bridge
+ *
+ * \param bridge The bridge for which to publish state
+ */
+void ast_bridge_publish_state(struct ast_bridge *bridge);
+
+/*! \brief Message representing the merge of two bridges */
+struct ast_bridge_merge_message {
+       struct ast_bridge_snapshot *from;       /*!< Bridge from which channels will be removed during the merge */
+       struct ast_bridge_snapshot *to;         /*!< Bridge to which channels will be added during the merge */
+};
+
+/*!
+ * \since 12
+ * \brief Message type for \ref ast_bridge_merge_message.
+ *
+ * \retval Message type for \ref ast_bridge_merge_message.
+ */
+struct stasis_message_type *ast_bridge_merge_message_type(void);
+
+/*!
+ * \since 12
+ * \brief Publish a bridge merge
+ *
+ * \param to The bridge to which channels are being added
+ * \param from The bridge from which channels are being removed
+ */
+void ast_bridge_publish_merge(struct ast_bridge *to, struct ast_bridge *from);
+
+/*!
+ * \since 12
+ * \brief Blob of data associated with a bridge.
+ *
+ * The \c blob is actually a JSON object of structured data. It has a "type" field
+ * which contains the type string describing this blob.
+ */
+struct ast_bridge_blob {
+       /*! Bridge blob is associated with (or NULL for global/all bridges) */
+       struct ast_bridge_snapshot *bridge;
+       /*! Channel blob is associated with (may be NULL for some messages) */
+       struct ast_channel_snapshot *channel;
+       /*! JSON blob of data */
+       struct ast_json *blob;
+};
+
+/*!
+ * \since 12
+ * \brief Message type for \ref channel enter bridge blob messages.
+ *
+ * \retval Message type for \ref channel enter bridge blob messages.
+ */
+struct stasis_message_type *ast_channel_entered_bridge_type(void);
+
+/*!
+ * \since 12
+ * \brief Message type for \ref channel leave bridge blob messages.
+ *
+ * \retval Message type for \ref channel leave bridge blob messages.
+ */
+struct stasis_message_type *ast_channel_left_bridge_type(void);
+
+/*!
+ * \since 12
+ * \brief Creates a \ref ast_bridge_blob message.
+ *
+ * The \a blob JSON object requires a \c "type" field describing the blob. It
+ * should also be treated as immutable and not modified after it is put into the
+ * message.
+ *
+ * \param bridge Channel blob is associated with, or NULL for global/all bridges.
+ * \param blob JSON object representing the data.
+ * \return \ref ast_bridge_blob message.
+ * \return \c NULL on error
+ */
+struct stasis_message *ast_bridge_blob_create(struct stasis_message_type *type,
+       struct ast_bridge *bridge,
+       struct ast_channel *chan,
+       struct ast_json *blob);
+
+/*!
+ * \since 12
+ * \brief Extracts the type field from a \ref ast_bridge_blob.
+ *
+ * Returned \c char* is still owned by \a obj
+ *
+ * \param obj Channel blob object.
+ *
+ * \retval Type field value from the blob.
+ * \retval \c NULL on error.
+ */
+const char *ast_bridge_blob_json_type(struct ast_bridge_blob *obj);
+
+/*!
+ * \since 12
+ * \brief Publish a bridge channel enter event
+ *
+ * \param bridge The bridge a channel entered
+ * \param chan The channel that entered the bridge
+ */
+void ast_bridge_publish_enter(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Publish a bridge channel leave event
+ *
+ * \param bridge The bridge a channel left
+ * \param chan The channel that left the bridge
+ */
+void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *chan);
+
+/*!
+ * \brief Build a JSON object from a \ref ast_bridge_snapshot.
+ * \return JSON object representing bridge snapshot.
+ * \return \c NULL on error
+ */
+struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot);
+
+/*!
+ * \brief Dispose of the stasis bridging topics and message types
+ */
+void ast_stasis_bridging_shutdown(void);
+
+/*!
+ * \brief Initialize the stasis bridging topic and message types
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_stasis_bridging_init(void);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _STASIS_BRIDGING_H */
index 88a9b8e9171dd4e443ca7af292a2eec37ca63d0c..6e20b86cb93271ad0b5a1a60bdb27dcd13ba4eb6 100644 (file)
@@ -41,6 +41,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/channel.h"
 #include "asterisk/term.h"
 #include "asterisk/utils.h"
+#include "asterisk/pbx.h"
+#include "asterisk/timing.h"
 
 #include "asterisk/abstract_jb.h"
 #include "fixedjitterbuf.h"
@@ -567,6 +569,13 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
        return 0;
 }
 
+void ast_jb_enable_for_channel(struct ast_channel *chan)
+{
+       struct ast_jb_conf conf = ast_channel_jb(chan)->conf;
+       if (ast_test_flag(&conf, AST_JB_ENABLED)) {
+               ast_jb_create_framehook(chan, &conf, 1);
+       }
+}
 
 void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf)
 {
@@ -800,3 +809,303 @@ const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type)
        }
        return NULL;
 }
+
+#define DEFAULT_TIMER_INTERVAL 20
+#define DEFAULT_SIZE  200
+#define DEFAULT_TARGET_EXTRA  40
+#define DEFAULT_RESYNC  1000
+#define DEFAULT_TYPE AST_JB_FIXED
+
+struct jb_framedata {
+       const struct ast_jb_impl *jb_impl;
+       struct ast_jb_conf jb_conf;
+       struct timeval start_tv;
+       struct ast_format last_format;
+       struct ast_timer *timer;
+       int timer_interval; /* ms between deliveries */
+       int timer_fd;
+       int first;
+       void *jb_obj;
+};
+
+static void jb_framedata_destroy(struct jb_framedata *framedata)
+{
+       if (framedata->timer) {
+               ast_timer_close(framedata->timer);
+               framedata->timer = NULL;
+       }
+       if (framedata->jb_impl && framedata->jb_obj) {
+               struct ast_frame *f;
+               while (framedata->jb_impl->remove(framedata->jb_obj, &f) == AST_JB_IMPL_OK) {
+                       ast_frfree(f);
+               }
+               framedata->jb_impl->destroy(framedata->jb_obj);
+               framedata->jb_obj = NULL;
+       }
+       ast_free(framedata);
+}
+
+void ast_jb_conf_default(struct ast_jb_conf *conf)
+{
+       conf->max_size = DEFAULT_SIZE;
+       conf->resync_threshold = DEFAULT_RESYNC;
+       ast_copy_string(conf->impl, "fixed", sizeof(conf->impl));
+       conf->target_extra = DEFAULT_TARGET_EXTRA;
+}
+
+static void datastore_destroy_cb(void *data) {
+       ast_free(data);
+       ast_debug(1, "JITTERBUFFER datastore destroyed\n");
+}
+
+static const struct ast_datastore_info jb_datastore = {
+       .type = "jitterbuffer",
+       .destroy = datastore_destroy_cb
+};
+
+static void hook_destroy_cb(void *framedata)
+{
+       ast_debug(1, "JITTERBUFFER hook destroyed\n");
+       jb_framedata_destroy((struct jb_framedata *) framedata);
+}
+
+static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data)
+{
+       struct jb_framedata *framedata = data;
+       struct timeval now_tv;
+       unsigned long now;
+       int putframe = 0; /* signifies if audio frame was placed into the buffer or not */
+
+       switch (event) {
+       case AST_FRAMEHOOK_EVENT_READ:
+               break;
+       case AST_FRAMEHOOK_EVENT_ATTACHED:
+       case AST_FRAMEHOOK_EVENT_DETACHED:
+       case AST_FRAMEHOOK_EVENT_WRITE:
+               return frame;
+       }
+
+       if (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) {
+               if (ast_timer_ack(framedata->timer, 1) < 0) {
+                       ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n");
+                       return frame;
+               }
+       }
+
+       if (!frame) {
+               return frame;
+       }
+
+       now_tv = ast_tvnow();
+       now = ast_tvdiff_ms(now_tv, framedata->start_tv);
+
+       if (frame->frametype == AST_FRAME_VOICE) {
+               int res;
+               struct ast_frame *jbframe;
+
+               if (!ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO) || frame->len < 2 || frame->ts < 0) {
+                       /* only frames with timing info can enter the jitterbuffer */
+                       return frame;
+               }
+
+               jbframe = ast_frisolate(frame);
+               ast_format_copy(&framedata->last_format, &frame->subclass.format);
+
+               if (frame->len && (frame->len != framedata->timer_interval)) {
+                       framedata->timer_interval = frame->len;
+                       ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
+               }
+               if (!framedata->first) {
+                       framedata->first = 1;
+                       res = framedata->jb_impl->put_first(framedata->jb_obj, jbframe, now);
+               } else {
+                       res = framedata->jb_impl->put(framedata->jb_obj, jbframe, now);
+               }
+               if (res == AST_JB_IMPL_OK) {
+                       frame = &ast_null_frame;
+               }
+               putframe = 1;
+       }
+
+       if (frame->frametype == AST_FRAME_NULL) {
+               int res;
+               long next = framedata->jb_impl->next(framedata->jb_obj);
+
+               /* If now is earlier than the next expected output frame
+                * from the jitterbuffer we may choose to pass on retrieving
+                * a frame during this read iteration.  The only exception
+                * to this rule is when an audio frame is placed into the buffer
+                * and the time for the next frame to come out of the buffer is
+                * at least within the timer_interval of the next output frame. By
+                * doing this we are able to feed off the timing of the input frames
+                * and only rely on our jitterbuffer timer when frames are dropped.
+                * During testing, this hybrid form of timing gave more reliable results. */
+               if (now < next) {
+                       long int diff = next - now;
+                       if (!putframe) {
+                               return frame;
+                       } else if (diff >= framedata->timer_interval) {
+                               return frame;
+                       }
+               }
+
+               res = framedata->jb_impl->get(framedata->jb_obj, &frame, now, framedata->timer_interval);
+               switch (res) {
+               case AST_JB_IMPL_OK:
+                       /* got it, and pass it through */
+                       break;
+               case AST_JB_IMPL_DROP:
+                       ast_frfree(frame);
+                       frame = &ast_null_frame;
+                       break;
+               case AST_JB_IMPL_INTERP:
+                       if (framedata->last_format.id) {
+                               struct ast_frame tmp = { 0, };
+                               tmp.frametype = AST_FRAME_VOICE;
+                               ast_format_copy(&tmp.subclass.format, &framedata->last_format);
+                               /* example: 8000hz / (1000 / 20ms) = 160 samples */
+                               tmp.samples = ast_format_rate(&framedata->last_format) / (1000 / framedata->timer_interval);
+                               tmp.delivery = ast_tvadd(framedata->start_tv, ast_samp2tv(next, 1000));
+                               tmp.offset = AST_FRIENDLY_OFFSET;
+                               tmp.src  = "func_jitterbuffer interpolation";
+                               frame = ast_frdup(&tmp);
+                               break;
+                       }
+                       /* else fall through */
+               case AST_JB_IMPL_NOFRAME:
+                       frame = &ast_null_frame;
+                       break;
+               }
+       }
+
+       if (frame->frametype == AST_FRAME_CONTROL) {
+               switch(frame->subclass.integer) {
+               case AST_CONTROL_SRCUPDATE:
+               case AST_CONTROL_SRCCHANGE:
+                       framedata->jb_impl->force_resync(framedata->jb_obj);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return frame;
+}
+
+/* set defaults */
+static int jb_framedata_init(struct jb_framedata *framedata, struct ast_jb_conf *jb_conf)
+{
+       int jb_impl_type = DEFAULT_TYPE;
+       /* Initialize defaults */
+       framedata->timer_fd = -1;
+       memcpy(&framedata->jb_conf, jb_conf, sizeof(*jb_conf));
+
+       /* Figure out implementation type from the configuration implementation string */
+       if (!ast_strlen_zero(jb_conf->impl)) {
+               if (!strcasecmp(jb_conf->impl, "fixed")) {
+                       jb_impl_type = AST_JB_FIXED;
+               } else if (!strcasecmp(jb_conf->impl, "adaptive")) {
+                       jb_impl_type = AST_JB_ADAPTIVE;
+               } else {
+                       ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", jb_conf->impl);
+                       return -1;
+               }
+       }
+
+       if (!(framedata->jb_impl = ast_jb_get_impl(jb_impl_type))) {
+               return -1;
+       }
+
+       if (!(framedata->timer = ast_timer_open())) {
+               return -1;
+       }
+
+       framedata->timer_fd = ast_timer_fd(framedata->timer);
+       framedata->timer_interval = DEFAULT_TIMER_INTERVAL;
+       ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval);
+       framedata->start_tv = ast_tvnow();
+
+       framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf);
+       return 0;
+}
+
+
+void ast_jb_create_framehook(struct ast_channel *chan, struct ast_jb_conf *jb_conf, int prefer_existing)
+{
+       struct jb_framedata *framedata;
+       struct ast_datastore *datastore = NULL;
+       struct ast_framehook_interface interface = {
+               .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+               .event_cb = hook_event_cb,
+               .destroy_cb = hook_destroy_cb,
+       };
+       int i = 0;
+
+       /* If disabled, strip any existing jitterbuffer and don't replace it. */
+       if (!strcasecmp(jb_conf->impl, "disabled")) {
+               int *id;
+               ast_channel_lock(chan);
+               if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
+                       id = datastore->data;
+                       ast_framehook_detach(chan, *id);
+                       ast_channel_datastore_remove(chan, datastore);
+               }
+               ast_channel_unlock(chan);
+               return;
+       }
+
+       if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
+               return;
+       }
+
+       if (jb_framedata_init(framedata, jb_conf)) {
+               jb_framedata_destroy(framedata);
+               return;
+       }
+
+       interface.data = framedata;
+
+       ast_channel_lock(chan);
+       i = ast_framehook_attach(chan, &interface);
+       if (i >= 0) {
+               int *id;
+               if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
+                       /* There is already a jitterbuffer on the channel. */
+                       if (prefer_existing) {
+                               /* We prefer the existing jitterbuffer, so remove the new one and keep the old one. */
+                               ast_framehook_detach(chan, i);
+                               ast_channel_unlock(chan);
+                               return;
+                       }
+                       /* We prefer the new jitterbuffer, so strip the old one. */
+                       id = datastore->data;
+                       ast_framehook_detach(chan, *id);
+                       ast_channel_datastore_remove(chan, datastore);
+               }
+
+               if (!(datastore = ast_datastore_alloc(&jb_datastore, NULL))) {
+                       ast_framehook_detach(chan, i);
+                       ast_channel_unlock(chan);
+                       return;
+               }
+
+               if (!(id = ast_calloc(1, sizeof(int)))) {
+                       ast_datastore_free(datastore);
+                       ast_framehook_detach(chan, i);
+                       ast_channel_unlock(chan);
+                       return;
+               }
+
+               *id = i; /* Store off the id. The channel is still locked so it is safe to access this ptr. */
+               datastore->data = id;
+               ast_channel_datastore_add(chan, datastore);
+
+               ast_channel_set_fd(chan, AST_JITTERBUFFER_FD, framedata->timer_fd);
+       } else {
+               jb_framedata_destroy(framedata);
+               framedata = NULL;
+       }
+       ast_channel_unlock(chan);
+
+       return;
+}
index 933aae63d3cd8e35338a4ff6d708d7cbad68683a..d8062d3b1d2df22cd30042c04dbe8fbef867d702 100644 (file)
@@ -4277,11 +4277,6 @@ int main(int argc, char *argv[])
 
        ast_http_init();                /* Start the HTTP server, if needed */
 
-       if (init_manager()) {
-               printf("%s", term_quit());
-               exit(1);
-       }
-
        if (ast_cdr_engine_init()) {
                printf("%s", term_quit());
                exit(1);
@@ -4330,6 +4325,16 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
+       if (ast_bridging_init()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
+       if (init_manager()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        if (ast_enum_init()) {
                printf("%s", term_quit());
                exit(1);
@@ -4340,6 +4345,11 @@ int main(int argc, char *argv[])
                exit(1);
        }
 
+       if (ast_local_init()) {
+               printf("%s", term_quit());
+               exit(1);
+       }
+
        if ((moduleresult = load_modules(0))) {         /* Load modules */
                printf("%s", term_quit());
                exit(moduleresult == -2 ? 2 : 1);
index 875e8503c396926b3e233f369b58d24bc215c6c5..f332dfab21f5c24ef35c7e45d6e269514b42a738 100644 (file)
@@ -40,12 +40,28 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/lock.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
 #include "asterisk/bridging_technology.h"
+#include "asterisk/stasis_bridging.h"
 #include "asterisk/app.h"
 #include "asterisk/file.h"
 #include "asterisk/module.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
 #include "asterisk/test.h"
+#include "asterisk/_private.h"
+
+#include "asterisk/heap.h"
+#include "asterisk/say.h"
+#include "asterisk/timing.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/features.h"
+#include "asterisk/cli.h"
+#include "asterisk/parking.h"
+
+/*! All bridges container. */
+static struct ao2_container *bridges;
 
 static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
 
@@ -56,6 +72,8 @@ static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
 #define BRIDGE_ARRAY_GROW 32
 
 static void cleanup_video_mode(struct ast_bridge *bridge);
+static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void bridge_features_remove_on_pull(struct ast_bridge_features *features);
 
 /*! Default DTMF keys for built in features */
 static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_STRING];
@@ -63,13 +81,76 @@ static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_S
 /*! Function handlers for the built in features */
 static void *builtin_features_handlers[AST_BRIDGE_BUILTIN_END];
 
+/*! Function handlers for built in interval features */
+static ast_bridge_builtin_set_limits_fn builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_END];
+
+/*! Bridge manager service request */
+struct bridge_manager_request {
+       /*! List of bridge service requests. */
+       AST_LIST_ENTRY(bridge_manager_request) node;
+       /*! Refed bridge requesting service. */
+       struct ast_bridge *bridge;
+};
+
+struct bridge_manager_controller {
+       /*! Condition, used to wake up the bridge manager thread. */
+       ast_cond_t cond;
+       /*! Queue of bridge service requests. */
+       AST_LIST_HEAD_NOLOCK(, bridge_manager_request) service_requests;
+       /*! Manager thread */
+       pthread_t thread;
+       /*! TRUE if the manager needs to stop. */
+       unsigned int stop:1;
+};
+
+/*! Bridge manager controller. */
+static struct bridge_manager_controller *bridge_manager;
+
+/*!
+ * \internal
+ * \brief Request service for a bridge from the bridge manager.
+ * \since 12.0.0
+ *
+ * \param bridge Requesting service.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_service_req(struct ast_bridge *bridge)
+{
+       struct bridge_manager_request *request;
+
+       ao2_lock(bridge_manager);
+       if (bridge_manager->stop) {
+               ao2_unlock(bridge_manager);
+               return;
+       }
+
+       /* Create the service request. */
+       request = ast_calloc(1, sizeof(*request));
+       if (!request) {
+               /* Well. This isn't good. */
+               ao2_unlock(bridge_manager);
+               return;
+       }
+       ao2_ref(bridge, +1);
+       request->bridge = bridge;
+
+       /* Put request into the queue and wake the bridge manager. */
+       AST_LIST_INSERT_TAIL(&bridge_manager->service_requests, request, node);
+       ast_cond_signal(&bridge_manager->cond);
+       ao2_unlock(bridge_manager);
+}
+
 int __ast_bridge_technology_register(struct ast_bridge_technology *technology, struct ast_module *module)
 {
-       struct ast_bridge_technology *current = NULL;
+       struct ast_bridge_technology *current;
 
        /* Perform a sanity check to make sure the bridge technology conforms to our needed requirements */
-       if (ast_strlen_zero(technology->name) || !technology->capabilities || !technology->write) {
-               ast_log(LOG_WARNING, "Bridge technology %s failed registration sanity check.\n", technology->name);
+       if (ast_strlen_zero(technology->name)
+               || !technology->capabilities
+               || !technology->write) {
+               ast_log(LOG_WARNING, "Bridge technology %s failed registration sanity check.\n",
+                       technology->name);
                return -1;
        }
 
@@ -78,7 +159,8 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
        /* Look for duplicate bridge technology already using this name, or already registered */
        AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
                if ((!strcasecmp(current->name, technology->name)) || (current == technology)) {
-                       ast_log(LOG_WARNING, "A bridge technology of %s already claims to exist in our world.\n", technology->name);
+                       ast_log(LOG_WARNING, "A bridge technology of %s already claims to exist in our world.\n",
+                               technology->name);
                        AST_RWLIST_UNLOCK(&bridge_technologies);
                        return -1;
                }
@@ -99,7 +181,7 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s
 
 int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
 {
-       struct ast_bridge_technology *current = NULL;
+       struct ast_bridge_technology *current;
 
        AST_RWLIST_WRLOCK(&bridge_technologies);
 
@@ -118,127 +200,192 @@ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology)
        return current ? 0 : -1;
 }
 
+void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge *bridge;
+
+       for (;;) {
+               /* Safely get the bridge pointer */
+               ast_bridge_channel_lock(bridge_channel);
+               bridge = bridge_channel->bridge;
+               ao2_ref(bridge, +1);
+               ast_bridge_channel_unlock(bridge_channel);
+
+               /* Lock the bridge and see if it is still the bridge we need to lock. */
+               ast_bridge_lock(bridge);
+               if (bridge == bridge_channel->bridge) {
+                       ao2_ref(bridge, -1);
+                       return;
+               }
+               ast_bridge_unlock(bridge);
+               ao2_ref(bridge, -1);
+       }
+}
+
 static void bridge_channel_poke(struct ast_bridge_channel *bridge_channel)
 {
-       ao2_lock(bridge_channel);
-       pthread_kill(bridge_channel->thread, SIGURG);
-       ast_cond_signal(&bridge_channel->cond);
-       ao2_unlock(bridge_channel);
+       if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
+               while (bridge_channel->waiting) {
+                       pthread_kill(bridge_channel->thread, SIGURG);
+                       sched_yield();
+               }
+       }
 }
 
-/*! \note This function assumes the bridge_channel is locked. */
-static void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
+void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
 {
+/* BUGBUG need cause code for the bridge_channel leaving the bridge. */
+       if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               return;
+       }
+
+       ast_debug(1, "Setting %p(%s) state from:%d to:%d\n",
+               bridge_channel, ast_channel_name(bridge_channel->chan), bridge_channel->state,
+               new_state);
+
        /* Change the state on the bridge channel */
        bridge_channel->state = new_state;
 
-       /* Only poke the channel's thread if it is not us */
-       if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
-               pthread_kill(bridge_channel->thread, SIGURG);
-               ast_cond_signal(&bridge_channel->cond);
-       }
+       bridge_channel_poke(bridge_channel);
 }
 
 void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
 {
-       ao2_lock(bridge_channel);
+       ast_bridge_channel_lock(bridge_channel);
        ast_bridge_change_state_nolock(bridge_channel, new_state);
-       ao2_unlock(bridge_channel);
-}
-
-/*!
- * \brief Helper function to poke the bridge thread
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_poke(struct ast_bridge *bridge)
-{
-       /* Poke the thread just in case */
-       if (bridge->thread != AST_PTHREADT_NULL && bridge->thread != AST_PTHREADT_STOP) {
-               pthread_kill(bridge->thread, SIGURG);
-       }
+       ast_bridge_channel_unlock(bridge_channel);
 }
 
 /*!
  * \internal
- * \brief Stop the bridge.
+ * \brief Put an action onto the specified bridge. Don't dup the action frame.
  * \since 12.0.0
  *
- * \note This function assumes the bridge is locked.
+ * \param bridge What to queue the action on.
+ * \param action What to do.
  *
  * \return Nothing
  */
-static void bridge_stop(struct ast_bridge *bridge)
+static void bridge_queue_action_nodup(struct ast_bridge *bridge, struct ast_frame *action)
 {
-       pthread_t thread = bridge->thread;
+       ast_debug(1, "Bridge %s: queueing action type:%d sub:%d\n",
+               bridge->uniqueid, action->frametype, action->subclass.integer);
 
-       bridge->stop = 1;
-       bridge_poke(bridge);
-       ao2_unlock(bridge);
-       pthread_join(thread, NULL);
-       ao2_lock(bridge);
+       ast_bridge_lock(bridge);
+       AST_LIST_INSERT_TAIL(&bridge->action_queue, action, frame_list);
+       ast_bridge_unlock(bridge);
+       bridge_manager_service_req(bridge);
 }
 
-/*!
- * \brief Helper function to add a channel to the bridge array
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_array_add(struct ast_bridge *bridge, struct ast_channel *chan)
+int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action)
 {
-       /* We have to make sure the bridge thread is not using the bridge array before messing with it */
-       while (bridge->waiting) {
-               bridge_poke(bridge);
-               sched_yield();
+       struct ast_frame *dup;
+
+       dup = ast_frdup(action);
+       if (!dup) {
+               return -1;
        }
+       bridge_queue_action_nodup(bridge, dup);
+       return 0;
+}
 
-       bridge->array[bridge->array_num++] = chan;
+int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
+{
+       struct ast_frame *dup;
+       char nudge = 0;
+
+       if (bridge_channel->suspended
+               /* Also defer DTMF frames. */
+               && fr->frametype != AST_FRAME_DTMF_BEGIN
+               && fr->frametype != AST_FRAME_DTMF_END
+               && !ast_is_deferrable_frame(fr)) {
+               /* Drop non-deferable frames when suspended. */
+               return 0;
+       }
 
-       ast_debug(1, "Added channel %s(%p) to bridge array on %p, new count is %d\n",
-               ast_channel_name(chan), chan, bridge, (int) bridge->array_num);
+       dup = ast_frdup(fr);
+       if (!dup) {
+               return -1;
+       }
 
-       /* If the next addition of a channel will exceed our array size grow it out */
-       if (bridge->array_num == bridge->array_size) {
-               struct ast_channel **new_array;
+       ast_bridge_channel_lock(bridge_channel);
+       if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               /* Drop frames on channels leaving the bridge. */
+               ast_bridge_channel_unlock(bridge_channel);
+               ast_frfree(dup);
+               return 0;
+       }
 
-               ast_debug(1, "Growing bridge array on %p from %d to %d\n",
-                       bridge, (int) bridge->array_size, (int) bridge->array_size + BRIDGE_ARRAY_GROW);
-               new_array = ast_realloc(bridge->array,
-                       (bridge->array_size + BRIDGE_ARRAY_GROW) * sizeof(*bridge->array));
-               if (!new_array) {
-                       return;
-               }
-               bridge->array = new_array;
-               bridge->array_size += BRIDGE_ARRAY_GROW;
+       AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
+       if (write(bridge_channel->alert_pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
+               ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
+                       bridge_channel, ast_channel_name(bridge_channel->chan));
        }
+       ast_bridge_channel_unlock(bridge_channel);
+       return 0;
 }
 
-/*! \brief Helper function to remove a channel from the bridge array
- *
- * \note This function assumes the bridge is locked.
- */
-static void bridge_array_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+void ast_bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen)
 {
-       int idx;
+       struct ast_frame frame = {
+               .frametype = AST_FRAME_BRIDGE_ACTION,
+               .subclass.integer = action,
+               .datalen = datalen,
+               .data.ptr = (void *) data,
+       };
 
-       /* We have to make sure the bridge thread is not using the bridge array before messing with it */
-       while (bridge->waiting) {
-               bridge_poke(bridge);
-               sched_yield();
-       }
+       ast_bridge_channel_queue_frame(bridge_channel, &frame);
+}
 
-       for (idx = 0; idx < bridge->array_num; ++idx) {
-               if (bridge->array[idx] == chan) {
-                       --bridge->array_num;
-                       bridge->array[idx] = bridge->array[bridge->array_num];
-                       ast_debug(1, "Removed channel %p from bridge array on %p, new count is %d\n",
-                               chan, bridge, (int) bridge->array_num);
-                       break;
+void ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
+{
+       struct ast_frame frame = {
+               .frametype = AST_FRAME_CONTROL,
+               .subclass.integer = control,
+               .datalen = datalen,
+               .data.ptr = (void *) data,
+       };
+
+       ast_bridge_channel_queue_frame(bridge_channel, &frame);
+}
+
+void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel)
+{
+       /* 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) {
+               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_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));
+               }
+       }
+       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_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));
                }
        }
 }
 
-/*! \brief Helper function to find a bridge channel given a channel */
+/*!
+ * \internal
+ * \brief Helper function to find a bridge channel given a channel.
+ *
+ * \param bridge What to search
+ * \param chan What to search for.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \retval bridge_channel if channel is in the bridge.
+ * \retval NULL if not in bridge.
+ */
 static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan)
 {
        struct ast_bridge_channel *bridge_channel;
@@ -254,1513 +401,5443 @@ static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge,
 
 /*!
  * \internal
- * \brief Force out all channels that are not already going out of the bridge.
+ * \brief Dissolve the bridge.
  * \since 12.0.0
  *
  * \param bridge Bridge to eject all channels
  *
+ * \details
+ * Force out all channels that are not already going out of the
+ * bridge.  Any new channels joining will leave immediately.
+ *
  * \note On entry, bridge is already locked.
  *
  * \return Nothing
  */
-static void bridge_force_out_all(struct ast_bridge *bridge)
+static void bridge_dissolve(struct ast_bridge *bridge)
 {
        struct ast_bridge_channel *bridge_channel;
+       struct ast_frame action = {
+               .frametype = AST_FRAME_BRIDGE_ACTION,
+               .subclass.integer = AST_BRIDGE_ACTION_DEFERRED_DISSOLVING,
+       };
 
-       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-               ao2_lock(bridge_channel);
-               switch (bridge_channel->state) {
-               case AST_BRIDGE_CHANNEL_STATE_END:
-               case AST_BRIDGE_CHANNEL_STATE_HANGUP:
-               case AST_BRIDGE_CHANNEL_STATE_DEPART:
-                       break;
-               default:
-                       ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-                       break;
-               }
-               ao2_unlock(bridge_channel);
+       if (bridge->dissolved) {
+               return;
        }
-}
+       bridge->dissolved = 1;
 
-/*! \brief Internal function to see whether a bridge should dissolve, and if so do it */
-static void bridge_check_dissolve(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       if (!ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE)
-               && (!bridge_channel->features
-                       || !bridge_channel->features->usable
-                       || !ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_FLAG_DISSOLVE))) {
-               return;
+       ast_debug(1, "Bridge %s: dissolving bridge\n", bridge->uniqueid);
+
+/* BUGBUG need a cause code on the bridge for the later ejected channels. */
+       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
        }
 
-       ast_debug(1, "Dissolving bridge %p\n", bridge);
-       bridge_force_out_all(bridge);
+       /* Must defer dissolving bridge because it is already locked. */
+       ast_bridge_queue_action(bridge, &action);
 }
 
-/*! \brief Internal function to handle DTMF from a channel */
-static struct ast_frame *bridge_handle_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+/*!
+ * \internal
+ * \brief Check if a bridge should dissolve and do it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel causing the check.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_dissolve_check(struct ast_bridge_channel *bridge_channel)
 {
-       struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
-       struct ast_bridge_features_hook *hook;
+       struct ast_bridge *bridge = bridge_channel->bridge;
 
-       /* If the features structure we grabbed is not usable immediately return the frame */
-       if (!features->usable) {
-               return frame;
+       if (bridge->dissolved) {
+               return;
        }
 
-       /* 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 */
-       AST_LIST_TRAVERSE(&features->hooks, hook, entry) {
-               if (hook->dtmf[0] == frame->subclass.integer) {
-                       ast_frfree(frame);
-                       frame = NULL;
-                       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_FEATURE);
-                       break;
-               }
+       if (!bridge->num_channels
+               && ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) {
+               /* Last channel leaving the bridge turns off the lights. */
+               bridge_dissolve(bridge);
+               return;
        }
 
-       return frame;
-}
-
-/*! \brief Internal function used to determine whether a control frame should be dropped or not */
-static int bridge_drop_control_frame(int subclass)
-{
-       switch (subclass) {
-       case AST_CONTROL_ANSWER:
-       case -1:
-               return 1;
+       switch (bridge_channel->state) {
+       case AST_BRIDGE_CHANNEL_STATE_END:
+               /* Do we need to dissolve the bridge because this channel hung up? */
+               if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP)
+                       || (bridge_channel->features->usable
+                               && ast_test_flag(&bridge_channel->features->feature_flags,
+                                       AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP))) {
+                       bridge_dissolve(bridge);
+                       return;
+               }
+               break;
        default:
-               return 0;
+               break;
        }
+/* BUGBUG need to implement AST_BRIDGE_CHANNEL_FLAG_LONELY support here */
 }
 
-void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking)
+/*!
+ * \internal
+ * \brief Pull the bridge channel out of its current bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to pull.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
 {
-       if (started_talking) {
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_START_TALKING);
-       } else {
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_STOP_TALKING);
-       }
-}
+       struct ast_bridge *bridge = bridge_channel->bridge;
 
-void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd)
-{
-       /* If no bridge channel has been provided and the actual channel has been provided find it */
-       if (chan && !bridge_channel) {
-               bridge_channel = find_bridge_channel(bridge, chan);
+       if (!bridge_channel->in_bridge) {
+               return;
        }
-
-       /* If a bridge channel with actual channel is present read a frame and handle it */
-       if (chan && bridge_channel) {
-               struct ast_frame *frame;
-
-               if (bridge->features.mute
-                       || (bridge_channel->features && bridge_channel->features->mute)) {
-                       frame = ast_read_noaudio(chan);
-               } else {
-                       frame = ast_read(chan);
-               }
-               /* This is pretty simple... see if they hung up */
-               if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) {
-                       /* Signal the thread that is handling the bridged channel that it should be ended */
-                       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
-               } else if (frame->frametype == AST_FRAME_CONTROL && bridge_drop_control_frame(frame->subclass.integer)) {
-                       ast_debug(1, "Dropping control frame %d from bridge channel %p\n",
-                               frame->subclass.integer, bridge_channel);
-               } else if (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) {
-                       int dtmf_passthrough = bridge_channel->features ?
-                               bridge_channel->features->dtmf_passthrough :
-                               bridge->features.dtmf_passthrough;
-
-                       if (frame->frametype == AST_FRAME_DTMF_BEGIN) {
-                               frame = bridge_handle_dtmf(bridge, bridge_channel, frame);
-                       }
-
-                       if (frame && dtmf_passthrough) {
-                               bridge->technology->write(bridge, bridge_channel, frame);
-                       }
-               } else {
-                       /* Simply write the frame out to the bridge technology if it still exists */
-                       bridge->technology->write(bridge, bridge_channel, frame);
-               }
-
-               if (frame) {
-                       ast_frfree(frame);
+       bridge_channel->in_bridge = 0;
+
+       ast_debug(1, "Bridge %s: pulling %p(%s)\n",
+               bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+
+/* 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",
+                       bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                       bridge->technology->name);
+               if (bridge->technology->leave) {
+                       bridge->technology->leave(bridge, bridge_channel);
                }
-               return;
        }
 
-       /* If a file descriptor actually tripped pass it off to the bridge technology */
-       if (outfd > -1 && bridge->technology->fd) {
-               bridge->technology->fd(bridge, bridge_channel, outfd);
-               return;
+       /* Remove channel from the bridge */
+       if (!bridge_channel->suspended) {
+               --bridge->num_active;
        }
+       --bridge->num_channels;
+       AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
+       bridge->v_table->pull(bridge, bridge_channel);
 
-       /* If all else fails just poke the bridge */
-       if (bridge->technology->poke && bridge_channel) {
-               bridge->technology->poke(bridge, bridge_channel);
-               return;
-       }
+       ast_bridge_channel_clear_roles(bridge_channel);
+
+       bridge_dissolve_check(bridge_channel);
+
+       bridge->reconfigured = 1;
+       ast_bridge_publish_leave(bridge, bridge_channel->chan);
 }
 
-/*! \brief Generic thread loop, TODO: Rethink this/improve it */
-static int generic_thread_loop(struct ast_bridge *bridge)
+/*!
+ * \internal
+ * \brief Push the bridge channel into its specified bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to push.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.  The channel did not get pushed.
+ */
+static int bridge_channel_push(struct ast_bridge_channel *bridge_channel)
 {
-       while (!bridge->stop && !bridge->refresh && bridge->array_num) {
-               struct ast_channel *winner;
-               int to = -1;
+       struct ast_bridge *bridge = bridge_channel->bridge;
+       struct ast_bridge_channel *swap;
 
-               /* Move channels around for priority reasons if we have more than one channel in our array */
-               if (bridge->array_num > 1) {
-                       struct ast_channel *first = bridge->array[0];
-                       memmove(bridge->array, bridge->array + 1, sizeof(struct ast_channel *) * (bridge->array_num - 1));
-                       bridge->array[(bridge->array_num - 1)] = first;
-               }
+       ast_assert(!bridge_channel->in_bridge);
+
+       swap = find_bridge_channel(bridge, bridge_channel->swap);
+       bridge_channel->swap = NULL;
 
-               /* Wait on the channels */
-               bridge->waiting = 1;
-               ao2_unlock(bridge);
-               winner = ast_waitfor_n(bridge->array, (int) bridge->array_num, &to);
-               bridge->waiting = 0;
-               ao2_lock(bridge);
+       if (swap) {
+               ast_debug(1, "Bridge %s: pushing %p(%s) by swapping with %p(%s)\n",
+                       bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                       swap, ast_channel_name(swap->chan));
+       } else {
+               ast_debug(1, "Bridge %s: pushing %p(%s)\n",
+                       bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+       }
 
-               /* Process whatever they did */
-               ast_bridge_handle_trip(bridge, NULL, winner, -1);
+       /* Add channel to the bridge */
+       if (bridge->dissolved
+               || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+               || (swap && swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT)
+               || bridge->v_table->push(bridge, bridge_channel, swap)
+               || 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));
+               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 (!bridge_channel->suspended) {
+               ++bridge->num_active;
+       }
+       if (swap) {
+               ast_bridge_change_state(swap, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+               bridge_channel_pull(swap);
        }
 
+       bridge->reconfigured = 1;
+       ast_bridge_publish_enter(bridge, bridge_channel->chan);
        return 0;
 }
 
-/*! \brief Bridge thread function */
-static void *bridge_thread(void *data)
+/*! \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 *bridge = data;
-       int res = 0;
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook;
+       char dtmf[2];
 
-       ao2_lock(bridge);
-
-       if (bridge->callid) {
-               ast_callid_threadassoc_add(bridge->callid);
+/* 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 */
+       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 = AST_BRIDGE_ACTION_FEATURE,
+               };
+
+               ast_frfree(frame);
+               frame = NULL;
+               ast_bridge_channel_queue_frame(bridge_channel, &action);
+               ao2_ref(hook, -1);
        }
 
-       ast_debug(1, "Started bridge thread for %p\n", bridge);
-
-       /* Loop around until we are told to stop */
-       while (!bridge->stop && bridge->array_num && !res) {
-               /* In case the refresh bit was set simply set it back to off */
-               bridge->refresh = 0;
-
-               ast_debug(1, "Launching bridge thread function %p for bridge %p\n",
-                       bridge->technology->thread ? bridge->technology->thread : generic_thread_loop,
-                       bridge);
+       return frame;
+}
 
-               /*
-                * Execute the appropriate thread function.  If the technology
-                * does not provide one we use the generic one.
-                */
-               res = bridge->technology->thread
-                       ? bridge->technology->thread(bridge)
-                       : generic_thread_loop(bridge);
+/*!
+ * \internal
+ * \brief Handle bridge hangup event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is hanging up.
+ *
+ * \return Nothing
+ */
+static void bridge_handle_hangup(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook;
+       struct ao2_iterator iter;
+
+       /* Run any hangup hooks. */
+       iter = ao2_iterator_init(features->hangup_hooks, 0);
+       for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
+               int failed;
+
+               failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+               if (failed) {
+                       ast_debug(1, "Hangup hook %p is being removed from %p(%s)\n",
+                               hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+                       ao2_unlink(features->hangup_hooks, hook);
+               }
        }
+       ao2_iterator_destroy(&iter);
 
-       ast_debug(1, "Ending bridge thread for %p\n", bridge);
+       /* Default hangup action. */
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+}
 
-       /* Indicate the bridge thread is no longer active */
-       bridge->thread = AST_PTHREADT_NULL;
-       ao2_unlock(bridge);
+static int bridge_channel_interval_ready(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook;
+       int ready;
 
-       ao2_ref(bridge, -1);
+       ast_heap_wrlock(features->interval_hooks);
+       hook = ast_heap_peek(features->interval_hooks, 1);
+       ready = hook && ast_tvdiff_ms(hook->parms.timer.trip_time, ast_tvnow()) <= 0;
+       ast_heap_unlock(features->interval_hooks);
 
-       return NULL;
+       return ready;
 }
 
-/*! \brief Helper function used to find the "best" bridge technology given a specified capabilities */
-static struct ast_bridge_technology *find_best_technology(uint32_t capabilities)
+void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking)
 {
-       struct ast_bridge_technology *current = NULL, *best = NULL;
+       struct ast_frame action = {
+               .frametype = AST_FRAME_BRIDGE_ACTION,
+               .subclass.integer = started_talking
+                       ? AST_BRIDGE_ACTION_TALKING_START : AST_BRIDGE_ACTION_TALKING_STOP,
+       };
 
-       AST_RWLIST_RDLOCK(&bridge_technologies);
-       AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
-               if (current->suspended) {
-                       ast_debug(1, "Bridge technology %s is suspended. Skipping.\n", current->name);
-                       continue;
-               }
-               if (!(current->capabilities & capabilities)) {
-                       ast_debug(1, "Bridge technology %s does not have the capabilities we need.\n", current->name);
-                       continue;
-               }
-               if (best && best->preference < current->preference) {
-                       ast_debug(1, "Bridge technology %s has preference %d while %s has preference %d. Skipping.\n", current->name, current->preference, best->name, best->preference);
-                       continue;
-               }
-               best = current;
-       }
+       ast_bridge_channel_queue_frame(bridge_channel, &action);
+}
 
-       if (best) {
-               /* Increment it's module reference count if present so it does not get unloaded while in use */
-               ast_module_ref(best->mod);
-               ast_debug(1, "Chose bridge technology %s\n", best->name);
-       }
+static void bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+       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).
+ *
+ * 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);
+       ast_bridge_unlock(bridge_channel->bridge);
+}
 
-       AST_RWLIST_UNLOCK(&bridge_technologies);
+void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen)
+{
+       struct ast_frame frame = {
+               .frametype = AST_FRAME_BRIDGE_ACTION,
+               .subclass.integer = action,
+               .datalen = datalen,
+               .data.ptr = (void *) data,
+       };
 
-       return best;
+       bridge_channel_write_frame(bridge_channel, &frame);
 }
 
-static void destroy_bridge(void *obj)
+void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen)
 {
-       struct ast_bridge *bridge = obj;
+       struct ast_frame frame = {
+               .frametype = AST_FRAME_CONTROL,
+               .subclass.integer = control,
+               .datalen = datalen,
+               .data.ptr = (void *) data,
+       };
 
-       ast_debug(1, "Actually destroying bridge %p, nobody wants it anymore\n", bridge);
+       bridge_channel_write_frame(bridge_channel, &frame);
+}
 
-       /* There should not be any channels left in the bridge. */
-       ast_assert(AST_LIST_EMPTY(&bridge->channels));
+static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args)
+{
+       int res = 0;
 
-       /* Pass off the bridge to the technology to destroy if needed */
-       if (bridge->technology->destroy) {
-               ast_debug(1, "Giving bridge technology %s the bridge structure %p to destroy\n", bridge->technology->name, bridge);
-               if (bridge->technology->destroy(bridge)) {
-                       ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p... some memory may have leaked\n",
-                               bridge->technology->name, bridge);
+       if (!strcasecmp("Gosub", app_name)) {
+               ast_app_exec_sub(NULL, chan, app_args, 0);
+       } else if (!strcasecmp("Macro", app_name)) {
+               ast_app_exec_macro(NULL, chan, app_args);
+       } else {
+               struct ast_app *app;
+
+               app = pbx_findapp(app_name);
+               if (!app) {
+                       ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
+               } else {
+                       res = pbx_exec(chan, app, app_args);
                }
        }
-
-       cleanup_video_mode(bridge);
-
-       /* Clean up the features configuration */
-       ast_bridge_features_cleanup(&bridge->features);
-
-       /* We are no longer using the bridge technology so decrement the module reference count on it */
-       ast_module_unref(bridge->technology->mod);
-
-       /* Drop the array of channels */
-       ast_free(bridge->array);
+       return res;
 }
 
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags)
+void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
 {
-       struct ast_bridge *bridge;
-       struct ast_bridge_technology *bridge_technology;
-
-       /* If we need to be a smart bridge see if we can move between 1to1 and multimix bridges */
-       if (flags & AST_BRIDGE_FLAG_SMART) {
-               if (!ast_bridge_check((capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)
-                       ? AST_BRIDGE_CAPABILITY_MULTIMIX : AST_BRIDGE_CAPABILITY_1TO1MIX)) {
-                       return NULL;
+       if (moh_class) {
+               if (ast_strlen_zero(moh_class)) {
+                       ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+                               NULL, 0);
+               } else {
+                       ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+                               moh_class, strlen(moh_class) + 1);
                }
        }
+       if (run_app_helper(bridge_channel->chan, app_name, S_OR(app_args, ""))) {
+               /* Break the bridge if the app returns non-zero. */
+               bridge_handle_hangup(bridge_channel);
+       }
+       if (moh_class) {
+               ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD,
+                       NULL, 0);
+       }
+}
 
-       /*
-        * If capabilities were provided use our helper function to find
-        * the "best" bridge technology, otherwise we can just look for
-        * the most basic capability needed, single 1to1 mixing.
-        */
-       bridge_technology = capabilities
-               ? find_best_technology(capabilities)
-               : find_best_technology(AST_BRIDGE_CAPABILITY_1TO1MIX);
+struct bridge_run_app {
+       /*! Offset into app_name[] where the MOH class name starts.  (zero if no MOH) */
+       int moh_offset;
+       /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+       int app_args_offset;
+       /*! Application name to run. */
+       char app_name[0];
+};
 
-       /* If no bridge technology was found we can't possibly do bridging so fail creation of the bridge */
-       if (!bridge_technology) {
-               return NULL;
-       }
+/*!
+ * \internal
+ * \brief Handle the run application 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.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, struct bridge_run_app *data)
+{
+       ast_bridge_channel_run_app(bridge_channel, data->app_name,
+               data->app_args_offset ? &data->app_name[data->app_args_offset] : NULL,
+               data->moh_offset ? &data->app_name[data->moh_offset] : NULL);
+}
 
-       /* We have everything we need to create this bridge... so allocate the memory, link things together, and fire her up! */
-       bridge = ao2_alloc(sizeof(*bridge), destroy_bridge);
-       if (!bridge) {
-               ast_module_unref(bridge_technology->mod);
-               return NULL;
+static void payload_helper_app(ast_bridge_channel_post_action_data post_it,
+       struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+       struct bridge_run_app *app_data;
+       size_t len_name = strlen(app_name) + 1;
+       size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+       size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
+       size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
+
+       /* Fill in application run frame data. */
+       app_data = alloca(len_data);
+       app_data->app_args_offset = len_args ? len_name : 0;
+       app_data->moh_offset = len_moh ? len_name + len_args : 0;
+       strcpy(app_data->app_name, app_name);/* Safe */
+       if (len_args) {
+               strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
        }
-
-       bridge->technology = bridge_technology;
-       bridge->thread = AST_PTHREADT_NULL;
-
-       /* Create an array of pointers for the channels that will be joining us */
-       bridge->array = ast_malloc(BRIDGE_ARRAY_START * sizeof(*bridge->array));
-       if (!bridge->array) {
-               ao2_ref(bridge, -1);
-               return NULL;
+       if (moh_class) {
+               strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
        }
-       bridge->array_size = BRIDGE_ARRAY_START;
 
-       ast_set_flag(&bridge->feature_flags, flags);
+       post_it(bridge_channel, AST_BRIDGE_ACTION_RUN_APP, app_data, len_data);
+}
 
-       /* Pass off the bridge to the technology to manipulate if needed */
-       if (bridge->technology->create) {
-               ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n", bridge->technology->name, bridge);
-               if (bridge->technology->create(bridge)) {
-                       ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n", bridge->technology->name, bridge);
-                       ao2_ref(bridge, -1);
-                       return NULL;
-               }
-       }
+void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+       payload_helper_app(ast_bridge_channel_write_action_data,
+               bridge_channel, app_name, app_args, moh_class);
+}
 
-       return bridge;
+void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+       payload_helper_app(ast_bridge_channel_queue_action_data,
+               bridge_channel, app_name, app_args, moh_class);
 }
 
-int ast_bridge_check(uint32_t capabilities)
+void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
 {
-       struct ast_bridge_technology *bridge_technology;
+       if (moh_class) {
+               if (ast_strlen_zero(moh_class)) {
+                       ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+                               NULL, 0);
+               } else {
+                       ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+                               moh_class, strlen(moh_class) + 1);
+               }
+       }
+       if (custom_play) {
+               custom_play(bridge_channel, playfile);
+       } else {
+               ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE);
+       }
+       if (moh_class) {
+               ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD,
+                       NULL, 0);
+       }
 
-       if (!(bridge_technology = find_best_technology(capabilities))) {
-               return 0;
+       /*
+        * 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.
+        */
+       if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) {
+               ast_moh_start(bridge_channel->chan, NULL, NULL);
        }
+}
 
-       ast_module_unref(bridge_technology->mod);
+struct bridge_playfile {
+       /*! Call this function to play the playfile. (NULL if normal sound file to play) */
+       ast_bridge_custom_play_fn custom_play;
+       /*! Offset into playfile[] where the MOH class name starts.  (zero if no MOH)*/
+       int moh_offset;
+       /*! Filename to play. */
+       char playfile[0];
+};
 
-       return 1;
+/*!
+ * \internal
+ * \brief Handle the playfile bridge action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel to play a file on.
+ * \param payload Action frame payload to play a file.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, struct bridge_playfile *payload)
+{
+       ast_bridge_channel_playfile(bridge_channel, payload->custom_play, payload->playfile,
+               payload->moh_offset ? &payload->playfile[payload->moh_offset] : NULL);
 }
 
-int ast_bridge_destroy(struct ast_bridge *bridge)
+static void payload_helper_playfile(ast_bridge_channel_post_action_data post_it,
+       struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
 {
-       ao2_lock(bridge);
-
-       if (bridge->callid) {
-               bridge->callid = ast_callid_unref(bridge->callid);
+       struct bridge_playfile *payload;
+       size_t len_name = strlen(playfile) + 1;
+       size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1;
+       size_t len_payload = sizeof(*payload) + len_name + len_moh;
+
+       /* Fill in play file frame data. */
+       payload = alloca(len_payload);
+       payload->custom_play = custom_play;
+       payload->moh_offset = len_moh ? len_name : 0;
+       strcpy(payload->playfile, playfile);/* Safe */
+       if (moh_class) {
+               strcpy(&payload->playfile[payload->moh_offset], moh_class);/* Safe */
        }
 
-       if (bridge->thread != AST_PTHREADT_NULL) {
-               bridge_stop(bridge);
-       }
+       post_it(bridge_channel, AST_BRIDGE_ACTION_PLAY_FILE, payload, len_payload);
+}
 
-       ast_debug(1, "Telling all channels in bridge %p to leave the party\n", bridge);
+void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+       payload_helper_playfile(ast_bridge_channel_write_action_data,
+               bridge_channel, custom_play, playfile, moh_class);
+}
+
+void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class)
+{
+       payload_helper_playfile(ast_bridge_channel_queue_action_data,
+               bridge_channel, custom_play, playfile, moh_class);
+}
 
-       /* Drop every bridged channel, the last one will cause the bridge thread (if it exists) to exit */
-       bridge_force_out_all(bridge);
+struct bridge_park {
+       int parker_uuid_offset;
+       int app_data_offset;
+       /* buffer used for holding those strings */
+       char parkee_uuid[0];
+};
 
-       ao2_unlock(bridge);
+static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload)
+{
+       ast_bridge_channel_park(bridge_channel, payload->parkee_uuid,
+               &payload->parkee_uuid[payload->parker_uuid_offset],
+               payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL);
+}
 
-       ao2_ref(bridge, -1);
+static void payload_helper_park(ast_bridge_channel_post_action_data post_it,
+       struct ast_bridge_channel *bridge_channel,
+       const char *parkee_uuid,
+       const char *parker_uuid,
+       const char *app_data)
+{
+       struct bridge_park *payload;
+       size_t len_parkee_uuid = strlen(parkee_uuid) + 1;
+       size_t len_parker_uuid = strlen(parker_uuid) + 1;
+       size_t len_app_data = !app_data ? 0 : strlen(app_data) + 1;
+       size_t len_payload = sizeof(*payload) + len_parker_uuid + len_parkee_uuid + len_app_data;
+
+       payload = alloca(len_payload);
+       payload->app_data_offset = len_app_data ? len_parkee_uuid + len_parker_uuid : 0;
+       payload->parker_uuid_offset = len_parkee_uuid;
+       strcpy(payload->parkee_uuid, parkee_uuid);
+       strcpy(&payload->parkee_uuid[payload->parker_uuid_offset], parker_uuid);
+       if (app_data) {
+               strcpy(&payload->parkee_uuid[payload->app_data_offset], app_data);
+       }
 
-       return 0;
+       post_it(bridge_channel, AST_BRIDGE_ACTION_PARK, payload, len_payload);
 }
 
-static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
 {
-       struct ast_format formats[2];
-       ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan));
-       ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan));
+       payload_helper_park(ast_bridge_channel_write_action_data,
+               bridge_channel, parkee_uuid, parker_uuid, app_data);
+}
 
-       /* Are the formats currently in use something this bridge can handle? */
-       if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) {
-               struct ast_format best_format;
+/*!
+ * \internal
+ * \brief Feed notification that a frame is waiting on a channel into the bridging core
+ *
+ * \param bridge_channel Bridge channel the notification was received on
+ */
+static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_frame *frame;
 
-               ast_best_codec(bridge->technology->format_capabilities, &best_format);
+       if (bridge_channel->features->mute) {
+               frame = ast_read_noaudio(bridge_channel->chan);
+       } else {
+               frame = ast_read(bridge_channel->chan);
+       }
 
-               /* Read format is a no go... */
-               if (option_debug) {
-                       char codec_buf[512];
-                       ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n", bridge->technology->name,
-                               ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
-                               ast_getformatname(&formats[0]));
+       if (!frame) {
+               bridge_handle_hangup(bridge_channel);
+               return;
+       }
+       switch (frame->frametype) {
+       case AST_FRAME_NULL:
+               /* Just discard it. */
+               ast_frfree(frame);
+               return;
+       case AST_FRAME_CONTROL:
+               switch (frame->subclass.integer) {
+               case AST_CONTROL_HANGUP:
+                       bridge_handle_hangup(bridge_channel);
+                       ast_frfree(frame);
+                       return;
+/* BUGBUG This is where incoming HOLD/UNHOLD memory should register.  Write UNHOLD into bridge when this channel is pulled. */
+               default:
+                       break;
                }
-               /* Switch read format to the best one chosen */
-               if (ast_set_read_format(bridge_channel->chan, &best_format)) {
-                       ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-                       return -1;
+               break;
+       case AST_FRAME_DTMF_BEGIN:
+               frame = bridge_handle_dtmf(bridge_channel, frame);
+               if (!frame) {
+                       return;
                }
-               ast_debug(1, "Bridge %p put channel %s into read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-       } else {
-               ast_debug(1, "Bridge %p is happy that channel %s already has read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[0]));
+               /* Fall through */
+       case AST_FRAME_DTMF_END:
+               if (!bridge_channel->features->dtmf_passthrough) {
+                       ast_frfree(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;
        }
 
-       if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &formats[1])) {
-               struct ast_format best_format;
+/* BUGBUG bridge join or impart needs to do CONNECTED_LINE updates if the channels are being swapped and it is a 1-1 bridge. */
 
-               ast_best_codec(bridge->technology->format_capabilities, &best_format);
+       /* 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);
+}
 
-               /* Write format is a no go... */
-               if (option_debug) {
-                       char codec_buf[512];
-                       ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n", bridge->technology->name,
-                               ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
-                               ast_getformatname(&formats[1]));
+/*!
+ * \internal
+ * \brief Complete joining new channels to the bridge.
+ * \since 12.0.0
+ *
+ * \param bridge Check for new channels on this bridge.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_complete_join(struct ast_bridge *bridge)
+{
+       struct ast_bridge_channel *bridge_channel;
+
+       if (bridge->dissolved) {
+               /*
+                * No sense in completing the join on channels for a dissolved
+                * bridge.  They are just going to be removed soon anyway.
+                * However, we do have reason to abort here because the bridge
+                * technology may not be able to handle the number of channels
+                * still in the bridge.
+                */
+               return;
+       }
+
+       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+               if (!bridge_channel->just_joined) {
+                       continue;
                }
-               /* Switch write format to the best one chosen */
-               if (ast_set_write_format(bridge_channel->chan, &best_format)) {
-                       ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-                       return -1;
+
+               /* Make the channel compatible with the bridge */
+               bridge_make_compatible(bridge, bridge_channel);
+
+               /* Tell the bridge technology we are joining so they set us up */
+               ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
+                       bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                       bridge->technology->name);
+               if (bridge->technology->join
+                       && bridge->technology->join(bridge, bridge_channel)) {
+                       ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+                               bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                               bridge->technology->name);
                }
-               ast_debug(1, "Bridge %p put channel %s into write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
-       } else {
-               ast_debug(1, "Bridge %p is happy that channel %s already has write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[1]));
-       }
 
-       return 0;
+               bridge_channel->just_joined = 0;
+       }
 }
 
-/*! \brief Perform the smart bridge operation. Basically sees if a new bridge technology should be used instead of the current one. */
-static int smart_bridge_operation(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int count)
+/*! \brief Helper function used to find the "best" bridge technology given specified capabilities */
+static struct ast_bridge_technology *find_best_technology(uint32_t capabilities, struct ast_bridge *bridge)
 {
-       uint32_t new_capabilities = 0;
-       struct ast_bridge_technology *new_technology;
-       struct ast_bridge_technology *old_technology = bridge->technology;
-       struct ast_bridge temp_bridge = {
-               .technology = bridge->technology,
-               .bridge_pvt = bridge->bridge_pvt,
-       };
-       struct ast_bridge_channel *bridge_channel2;
+       struct ast_bridge_technology *current;
+       struct ast_bridge_technology *best = NULL;
 
-       /* Based on current feature determine whether we want to change bridge technologies or not */
-       if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
-               if (count <= 2) {
-                       ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n",
-                               bridge, count, bridge->technology->name);
-                       return 0;
+       AST_RWLIST_RDLOCK(&bridge_technologies);
+       AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) {
+               if (current->suspended) {
+                       ast_debug(1, "Bridge technology %s is suspended. Skipping.\n",
+                               current->name);
+                       continue;
                }
-               new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
-       } else if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
-               if (count > 2) {
-                       ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n",
-                               bridge, count, bridge->technology->name);
-                       return 0;
+               if (!(current->capabilities & capabilities)) {
+                       ast_debug(1, "Bridge technology %s does not have any capabilities we want.\n",
+                               current->name);
+                       continue;
+               }
+               if (best && current->preference <= best->preference) {
+                       ast_debug(1, "Bridge technology %s has less preference than %s (%d <= %d). Skipping.\n",
+                               current->name, best->name, current->preference, best->preference);
+                       continue;
                }
-               new_capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX;
+               if (current->compatible && !current->compatible(bridge)) {
+                       ast_debug(1, "Bridge technology %s is not compatible with properties of existing bridge.\n",
+                               current->name);
+                       continue;
+               }
+               best = current;
        }
 
-       if (!new_capabilities) {
-               ast_debug(1, "Bridge '%p' has no new capabilities, not performing smart bridge operation.\n", bridge);
-               return 0;
+       if (best) {
+               /* Increment it's module reference count if present so it does not get unloaded while in use */
+               ast_module_ref(best->mod);
+               ast_debug(1, "Chose bridge technology %s\n", best->name);
        }
 
-       /* Attempt to find a new bridge technology to satisfy the capabilities */
-       if (!(new_technology = find_best_technology(new_capabilities))) {
-               return -1;
-       }
+       AST_RWLIST_UNLOCK(&bridge_technologies);
 
-       ast_debug(1, "Performing smart bridge operation on bridge %p, moving from bridge technology %s to %s\n",
-               bridge, old_technology->name, new_technology->name);
+       return best;
+}
 
-       /* If a thread is currently executing for the current technology tell it to stop */
-       if (bridge->thread != AST_PTHREADT_NULL) {
-               /*
-                * If the new bridge technology also needs a thread simply tell
-                * the bridge thread to refresh itself.  This has the benefit of
-                * not incurring the cost/time of tearing down and bringing up a
-                * new thread.
-                */
-               if (new_technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD) {
-                       ast_debug(1, "Telling current bridge thread for bridge %p to refresh\n", bridge);
-                       bridge->refresh = 1;
-                       bridge_poke(bridge);
-               } else {
-                       ast_debug(1, "Telling current bridge thread for bridge %p to stop\n", bridge);
-                       bridge_stop(bridge);
-               }
-       }
+struct tech_deferred_destroy {
+       struct ast_bridge_technology *tech;
+       void *tech_pvt;
+};
 
-       /*
-        * Since we are soon going to pass this bridge to a new
-        * technology we need to NULL out the bridge_pvt pointer but
-        * don't worry as it still exists in temp_bridge, ditto for the
-        * old technology.
-        */
-       bridge->bridge_pvt = NULL;
-       bridge->technology = new_technology;
+/*!
+ * \internal
+ * \brief Deferred destruction of bridge tech private structure.
+ * \since 12.0.0
+ *
+ * \param bridge What to execute the action on.
+ * \param action Deferred bridge tech destruction.
+ *
+ * \note On entry, bridge must not be locked.
+ *
+ * \return Nothing
+ */
+static void bridge_tech_deferred_destroy(struct ast_bridge *bridge, struct ast_frame *action)
+{
+       struct tech_deferred_destroy *deferred = action->data.ptr;
+       struct ast_bridge dummy_bridge = {
+               .technology = deferred->tech,
+               .tech_pvt = deferred->tech_pvt,
+               };
+
+       ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
+       ast_debug(1, "Bridge %s: calling %s technology destructor (deferred, dummy)\n",
+               dummy_bridge.uniqueid, dummy_bridge.technology->name);
+       dummy_bridge.technology->destroy(&dummy_bridge);
+       ast_module_unref(dummy_bridge.technology->mod);
+}
 
-       /* Pass the bridge to the new bridge technology so it can set it up */
-       if (new_technology->create) {
-               ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n",
-                       new_technology->name, bridge);
-               if (new_technology->create(bridge)) {
-                       ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n",
-                               new_technology->name, bridge);
-               }
+/*!
+ * \internal
+ * \brief Handle bridge action frame.
+ * \since 12.0.0
+ *
+ * \param bridge What to execute the action on.
+ * \param action What to do.
+ *
+ * \note On entry, bridge is already locked.
+ * \note Can be called by the bridge destructor.
+ *
+ * \return Nothing
+ */
+static void bridge_action_bridge(struct ast_bridge *bridge, struct ast_frame *action)
+{
+#if 0  /* In case we need to know when the destructor is calling us. */
+       int in_destructor = !ao2_ref(bridge, 0);
+#endif
+
+       switch (action->subclass.integer) {
+       case AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY:
+               ast_bridge_unlock(bridge);
+               bridge_tech_deferred_destroy(bridge, action);
+               ast_bridge_lock(bridge);
+               break;
+       case AST_BRIDGE_ACTION_DEFERRED_DISSOLVING:
+               ast_bridge_unlock(bridge);
+               bridge->v_table->dissolving(bridge);
+               ast_bridge_lock(bridge);
+               break;
+       default:
+               /* Unexpected deferred action type.  Should never happen. */
+               ast_assert(0);
+               break;
        }
+}
 
-       /* Move existing channels over to the new technology, while taking them away from the old one */
-       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel2, entry) {
-               /* Skip over channel that initiated the smart bridge operation */
-               if (bridge_channel == bridge_channel2) {
-                       continue;
-               }
+/*!
+ * \internal
+ * \brief Do any pending bridge actions.
+ * \since 12.0.0
+ *
+ * \param bridge What to do actions on.
+ *
+ * \note On entry, bridge is already locked.
+ * \note Can be called by the bridge destructor.
+ *
+ * \return Nothing
+ */
+static void bridge_handle_actions(struct ast_bridge *bridge)
+{
+       struct ast_frame *action;
 
-               /* First we part them from the old technology */
-               if (old_technology->leave) {
-                       ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p (really %p)\n",
-                               old_technology->name, bridge_channel2, &temp_bridge, bridge);
-                       if (old_technology->leave(&temp_bridge, bridge_channel2)) {
-                               ast_debug(1, "Bridge technology %s failed to allow %p (really %p) to leave bridge %p\n",
-                                       old_technology->name, bridge_channel2, &temp_bridge, bridge);
-                       }
+       while ((action = AST_LIST_REMOVE_HEAD(&bridge->action_queue, frame_list))) {
+               switch (action->frametype) {
+               case AST_FRAME_BRIDGE_ACTION:
+                       bridge_action_bridge(bridge, action);
+                       break;
+               default:
+                       /* Unexpected deferred frame type.  Should never happen. */
+                       ast_assert(0);
+                       break;
                }
+               ast_frfree(action);
+       }
+}
 
-               /* Second we make them compatible again with the bridge */
-               bridge_make_compatible(bridge, bridge_channel2);
+static void destroy_bridge(void *obj)
+{
+       struct ast_bridge *bridge = obj;
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 
-               /* Third we join them to the new technology */
-               if (new_technology->join) {
-                       ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n",
-                               new_technology->name, bridge_channel2, bridge);
-                       if (new_technology->join(bridge, bridge_channel2)) {
-                               ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n",
-                                       new_technology->name, bridge_channel2, bridge);
-                       }
-               }
+       ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
+               bridge->uniqueid, bridge->v_table->name);
 
-               /* Fourth we tell them to wake up so they become aware that the above has happened */
-               bridge_channel_poke(bridge_channel2);
+       msg = stasis_cache_clear_create(ast_bridge_snapshot_type(), bridge->uniqueid);
+       if (msg) {
+               stasis_publish(ast_bridge_topic(bridge), msg);
        }
 
-       /* Now that all the channels have been moved over we need to get rid of all the information the old technology may have left around */
-       if (old_technology->destroy) {
-               ast_debug(1, "Giving bridge technology %s the bridge structure %p (really %p) to destroy\n",
-                       old_technology->name, &temp_bridge, bridge);
-               if (old_technology->destroy(&temp_bridge)) {
-                       ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p (really %p)... some memory may have leaked\n",
-                               old_technology->name, &temp_bridge, bridge);
+       /* Do any pending actions in the context of destruction. */
+       ast_bridge_lock(bridge);
+       bridge_handle_actions(bridge);
+       ast_bridge_unlock(bridge);
+
+       /* There should not be any channels left in the bridge. */
+       ast_assert(AST_LIST_EMPTY(&bridge->channels));
+
+       ast_debug(1, "Bridge %s: calling %s bridge destructor\n",
+               bridge->uniqueid, bridge->v_table->name);
+       bridge->v_table->destroy(bridge);
+
+       /* Pass off the bridge to the technology to destroy if needed */
+       if (bridge->technology) {
+               ast_debug(1, "Bridge %s: calling %s technology destructor\n",
+                       bridge->uniqueid, bridge->technology->name);
+               if (bridge->technology->destroy) {
+                       bridge->technology->destroy(bridge);
                }
+               ast_module_unref(bridge->technology->mod);
+               bridge->technology = NULL;
        }
 
-       /* Finally if the old technology has module referencing remove our reference, we are no longer going to use it */
-       ast_module_unref(old_technology->mod);
+       if (bridge->callid) {
+               bridge->callid = ast_callid_unref(bridge->callid);
+       }
 
-       return 0;
+       cleanup_video_mode(bridge);
 }
 
-/*! \brief Run in a multithreaded model. Each joined channel does writing/reading in their own thread. TODO: Improve */
-static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct ast_bridge_channel *bridge_channel)
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge)
 {
-       int fds[4] = { -1, }, nfds = 0, i = 0, outfd = -1, ms = -1;
-       struct ast_channel *chan;
-
-       /* Add any file descriptors we may want to monitor */
-       if (bridge_channel->bridge->technology->fd) {
-               for (i = 0; i < 4; i ++) {
-                       if (bridge_channel->fds[i] >= 0) {
-                               fds[nfds++] = bridge_channel->fds[i];
-                       }
+       if (bridge) {
+               ast_bridge_publish_state(bridge);
+               if (!ao2_link(bridges, bridge)) {
+                       ast_bridge_destroy(bridge);
+                       bridge = NULL;
                }
        }
+       return bridge;
+}
 
-       ao2_unlock(bridge_channel->bridge);
+struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table)
+{
+       struct ast_bridge *bridge;
 
-       ao2_lock(bridge_channel);
-       /* Wait for data to either come from the channel or us to be signalled */
-       if (!bridge_channel->suspended) {
-               ao2_unlock(bridge_channel);
-               ast_debug(10, "Going into a multithreaded waitfor for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
-               chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, fds, nfds, NULL, &outfd, &ms);
-       } else {
-               ast_debug(10, "Going into a multithreaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
-               ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
-               ao2_unlock(bridge_channel);
-               chan = NULL;
+       /* Check v_table that all methods are present. */
+       if (!v_table
+               || !v_table->name
+               || !v_table->destroy
+               || !v_table->dissolving
+               || !v_table->push
+               || !v_table->pull
+               || !v_table->notify_masquerade
+               || !v_table->get_merge_priority) {
+               ast_log(LOG_ERROR, "Virtual method table for bridge class %s not complete.\n",
+                       v_table && v_table->name ? v_table->name : "<unknown>");
+               ast_assert(0);
+               return NULL;
        }
 
-       ao2_lock(bridge_channel->bridge);
-
-       if (!bridge_channel->suspended) {
-               ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd);
+       bridge = ao2_alloc(size, destroy_bridge);
+       if (bridge) {
+               bridge->v_table = v_table;
        }
-
-       return bridge_channel->state;
+       return bridge;
 }
 
-/*! \brief Run in a singlethreaded model. Each joined channel yields itself to the main bridge thread. TODO: Improve */
-static enum ast_bridge_channel_state bridge_channel_join_singlethreaded(struct ast_bridge_channel *bridge_channel)
+struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags)
 {
-       ao2_unlock(bridge_channel->bridge);
-       ao2_lock(bridge_channel);
-       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-               ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
-               ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
+       if (!self) {
+               return NULL;
        }
-       ao2_unlock(bridge_channel);
-       ao2_lock(bridge_channel->bridge);
 
-       return bridge_channel->state;
-}
+       ast_uuid_generate_str(self->uniqueid, sizeof(self->uniqueid));
+       ast_set_flag(&self->feature_flags, flags);
+       self->allowed_capabilities = capabilities;
 
-/*! \brief Internal function that suspends a channel from a bridge */
-static void bridge_channel_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       ao2_lock(bridge_channel);
-       bridge_channel->suspended = 1;
-       bridge_array_remove(bridge, bridge_channel->chan);
-       ao2_unlock(bridge_channel);
+       /* Use our helper function to find the "best" bridge technology. */
+       self->technology = find_best_technology(capabilities, self);
+       if (!self->technology) {
+               ast_debug(1, "Bridge %s: Could not create.  No technology available to support it.\n",
+                       self->uniqueid);
+               ao2_ref(self, -1);
+               return NULL;
+       }
 
-       if (bridge->technology->suspend) {
-               bridge->technology->suspend(bridge, bridge_channel);
+       /* Pass off the bridge to the technology to manipulate if needed */
+       ast_debug(1, "Bridge %s: calling %s technology constructor\n",
+               self->uniqueid, self->technology->name);
+       if (self->technology->create && self->technology->create(self)) {
+               ast_debug(1, "Bridge %s: failed to setup %s technology\n",
+                       self->uniqueid, self->technology->name);
+               ao2_ref(self, -1);
+               return NULL;
        }
-}
 
-/*! \brief Internal function that unsuspends a channel from a bridge */
-static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
-{
-       ao2_lock(bridge_channel);
-       bridge_channel->suspended = 0;
-       bridge_array_add(bridge, bridge_channel->chan);
-       ast_cond_signal(&bridge_channel->cond);
-       ao2_unlock(bridge_channel);
-
-       if (bridge->technology->unsuspend) {
-               bridge->technology->unsuspend(bridge, bridge_channel);
+       if (!ast_bridge_topic(self)) {
+               ao2_ref(self, -1);
+               return NULL;
        }
+
+       return self;
 }
 
 /*!
- * \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.
+ * \internal
+ * \brief ast_bridge base class destructor.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note Stub because of nothing to do.
+ *
+ * \return Nothing
  */
-static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void bridge_base_destroy(struct ast_bridge *self)
 {
-       struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
-       struct ast_bridge_features_hook *hook = NULL;
-       char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
-       int look_for_dtmf = 1, dtmf_len = 0;
+}
 
-       /* 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);
+/*!
+ * \internal
+ * \brief The bridge is being dissolved.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \return Nothing
+ */
+static void bridge_base_dissolving(struct ast_bridge *self)
+{
+       ao2_unlink(bridges, self);
+}
 
-       /* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */
-       while (look_for_dtmf) {
-               int res = ast_waitfordigit(bridge_channel->chan, 3000);
+/*!
+ * \internal
+ * \brief ast_bridge base push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_base_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+       return 0;
+}
 
-               /* If the above timed out simply exit */
-               if (!res) {
-                       ast_debug(1, "DTMF feature string collection on bridge channel %p timed out\n", bridge_channel);
-                       break;
-               } else if (res < 0) {
-                       ast_debug(1, "DTMF feature string collection failed on bridge channel %p for some reason\n", bridge_channel);
-                       break;
-               }
+/*!
+ * \internal
+ * \brief ast_bridge base pull method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_base_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
+{
+       bridge_features_remove_on_pull(bridge_channel->features);
+}
 
-               /* Add the above DTMF into the DTMF string so we can do our matching */
-               dtmf[dtmf_len++] = res;
+/*!
+ * \internal
+ * \brief ast_bridge base notify_masquerade method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_base_notify_masquerade(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
+{
+       self->reconfigured = 1;
+}
 
-               ast_debug(1, "DTMF feature string on bridge channel %p is now '%s'\n", bridge_channel, dtmf);
+/*!
+ * \internal
+ * \brief Get the merge priority of this bridge.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Merge priority
+ */
+static int bridge_base_get_merge_priority(struct ast_bridge *self)
+{
+       return 0;
+}
 
-               /* Assume that we do not want to look for DTMF any longer */
-               look_for_dtmf = 0;
+struct ast_bridge_methods ast_bridge_base_v_table = {
+       .name = "base",
+       .destroy = bridge_base_destroy,
+       .dissolving = bridge_base_dissolving,
+       .push = bridge_base_push,
+       .pull = bridge_base_pull,
+       .notify_masquerade = bridge_base_notify_masquerade,
+       .get_merge_priority = bridge_base_get_merge_priority,
+};
+
+struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags)
+{
+       void *bridge;
 
-               /* See if a DTMF feature hook matches or can match */
-               AST_LIST_TRAVERSE(&features->hooks, hook, entry) {
-                       /* If this hook matches just break out now */
-                       if (!strcmp(hook->dtmf, dtmf)) {
-                               ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on bridge channel %p\n", hook, dtmf, bridge_channel);
-                               look_for_dtmf = 0;
-                               break;
-                       } else if (!strncmp(hook->dtmf, dtmf, dtmf_len)) {
-                               ast_debug(1, "DTMF feature hook %p can match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
-                               look_for_dtmf = 1;
-                       } else {
-                               ast_debug(1, "DTMF feature hook %p does not match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
-                       }
-               }
+       bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_base_v_table);
+       bridge = ast_bridge_base_init(bridge, capabilities, flags);
+       bridge = ast_bridge_register(bridge);
+       return bridge;
+}
 
-               /* If we have reached the maximum length of a DTMF feature string bail out */
-               if (dtmf_len == MAXIMUM_DTMF_FEATURE_STRING) {
-                       break;
-               }
-       }
+int ast_bridge_destroy(struct ast_bridge *bridge)
+{
+       ast_debug(1, "Bridge %s: telling all channels to leave the party\n", bridge->uniqueid);
+       ast_bridge_lock(bridge);
+       bridge_dissolve(bridge);
+       ast_bridge_unlock(bridge);
 
-       /* 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);
+       ao2_ref(bridge, -1);
 
-       /* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */
-       if (hook) {
-               hook->callback(bridge, bridge_channel, hook->hook_pvt);
-               /*
-                * 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_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
-               }
-       } else {
-               ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan);
-       }
+       return 0;
 }
 
-static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
 {
-       struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
+       struct ast_format read_format;
+       struct ast_format write_format;
+       struct ast_format best_format;
+       char codec_buf[512];
+
+       ast_format_copy(&read_format, ast_channel_readformat(bridge_channel->chan));
+       ast_format_copy(&write_format, ast_channel_writeformat(bridge_channel->chan));
+
+       /* Are the formats currently in use something this bridge can handle? */
+       if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) {
+               ast_best_codec(bridge->technology->format_capabilities, &best_format);
+
+               /* Read format is a no go... */
+               ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n",
+                       bridge->technology->name,
+                       ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+                       ast_getformatname(&read_format));
 
-       if (features && features->talker_cb) {
-               features->talker_cb(bridge, bridge_channel, features->talker_pvt_data);
+               /* Switch read format to the best one chosen */
+               if (ast_set_read_format(bridge_channel->chan, &best_format)) {
+                       ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n",
+                               ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+                       return -1;
+               }
+               ast_debug(1, "Bridge %s put channel %s into read format %s\n",
+                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+                       ast_getformatname(&best_format));
+       } else {
+               ast_debug(1, "Bridge %s is happy that channel %s already has read format %s\n",
+                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+                       ast_getformatname(&read_format));
        }
-}
 
-/*! \brief Internal function that plays back DTMF on a bridge channel */
-static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel)
-{
-       char dtmf_q[8] = "";
+       if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &write_format)) {
+               ast_best_codec(bridge->technology->format_capabilities, &best_format);
+
+               /* Write format is a no go... */
+               ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n",
+                       bridge->technology->name,
+                       ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+                       ast_getformatname(&write_format));
 
-       ast_copy_string(dtmf_q, bridge_channel->dtmf_stream_q, sizeof(dtmf_q));
-       bridge_channel->dtmf_stream_q[0] = '\0';
+               /* Switch write format to the best one chosen */
+               if (ast_set_write_format(bridge_channel->chan, &best_format)) {
+                       ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n",
+                               ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+                       return -1;
+               }
+               ast_debug(1, "Bridge %s put channel %s into write format %s\n",
+                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+                       ast_getformatname(&best_format));
+       } else {
+               ast_debug(1, "Bridge %s is happy that channel %s already has write format %s\n",
+                       bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+                       ast_getformatname(&write_format));
+       }
 
-       ast_debug(1, "Playing DTMF stream '%s' out to bridge channel %p\n", dtmf_q, bridge_channel);
-       ast_dtmf_stream(bridge_channel->chan, NULL, dtmf_q, 250, 0);
+       return 0;
 }
 
-/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
-static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_channel *bridge_channel)
+/*!
+ * \internal
+ * \brief Perform the smart bridge operation.
+ * \since 12.0.0
+ *
+ * \param bridge Work on this bridge.
+ *
+ * \details
+ * Basically see if a new bridge technology should be used instead
+ * of the current one.
+ *
+ * \note On entry, bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int smart_bridge_operation(struct ast_bridge *bridge)
 {
-       struct ast_format formats[2];
-       enum ast_bridge_channel_state state;
-       ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan));
-       ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan));
+       uint32_t new_capabilities;
+       struct ast_bridge_technology *new_technology;
+       struct ast_bridge_technology *old_technology = bridge->technology;
+       struct ast_bridge_channel *bridge_channel;
+       struct ast_frame *deferred_action;
+       struct ast_bridge dummy_bridge = {
+               .technology = bridge->technology,
+               .tech_pvt = bridge->tech_pvt,
+       };
 
-       /* Record the thread that will be the owner of us */
-       bridge_channel->thread = pthread_self();
+       if (bridge->dissolved) {
+               ast_debug(1, "Bridge %s is dissolved, not performing smart bridge operation.\n",
+                       bridge->uniqueid);
+               return 0;
+       }
 
-       ast_debug(1, "Joining bridge channel %p to bridge %p\n", bridge_channel, bridge_channel->bridge);
+       /* Determine new bridge technology capabilities needed. */
+       if (2 < bridge->num_channels) {
+               new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
+               new_capabilities &= bridge->allowed_capabilities;
+       } else {
+               new_capabilities = AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX;
+               new_capabilities &= bridge->allowed_capabilities;
+               if (!new_capabilities
+                       && (bridge->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+                       /* Allow switching between different multimix bridge technologies. */
+                       new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX;
+               }
+       }
 
-       ao2_lock(bridge_channel->bridge);
+       /* Find a bridge technology to satisfy the new capabilities. */
+       new_technology = find_best_technology(new_capabilities, bridge);
+       if (!new_technology) {
+               int is_compatible = 0;
+
+               if (old_technology->compatible) {
+                       is_compatible = old_technology->compatible(bridge);
+               } else if (old_technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+                       is_compatible = 1;
+               } else if (bridge->num_channels <= 2
+                       && (old_technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)) {
+                       is_compatible = 1;
+               }
 
-       if (!bridge_channel->bridge->callid) {
-               bridge_channel->bridge->callid = ast_read_threadstorage_callid();
+               if (is_compatible) {
+                       ast_debug(1, "Bridge %s could not get a new technology, staying with old technology.\n",
+                               bridge->uniqueid);
+                       return 0;
+               }
+               ast_log(LOG_WARNING, "Bridge %s has no technology available to support it.\n",
+                       bridge->uniqueid);
+               return -1;
+       }
+       if (new_technology == old_technology) {
+               ast_debug(1, "Bridge %s is already using the new technology.\n",
+                       bridge->uniqueid);
+               ast_module_unref(old_technology->mod);
+               return 0;
        }
 
-       /* Add channel into the bridge */
-       AST_LIST_INSERT_TAIL(&bridge_channel->bridge->channels, bridge_channel, entry);
-       bridge_channel->bridge->num++;
-
-       bridge_array_add(bridge_channel->bridge, bridge_channel->chan);
+       ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
 
-       if (bridge_channel->swap) {
-               struct ast_bridge_channel *bridge_channel2;
+       if (old_technology->destroy) {
+               struct tech_deferred_destroy deferred_tech_destroy = {
+                       .tech = dummy_bridge.technology,
+                       .tech_pvt = dummy_bridge.tech_pvt,
+               };
+               struct ast_frame action = {
+                       .frametype = AST_FRAME_BRIDGE_ACTION,
+                       .subclass.integer = AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY,
+                       .data.ptr = &deferred_tech_destroy,
+                       .datalen = sizeof(deferred_tech_destroy),
+               };
 
                /*
-                * If we are performing a swap operation we do not need to
-                * execute the smart bridge operation as the actual number of
-                * channels involved will not have changed, we just need to tell
-                * the other channel to leave.
+                * We need to defer the bridge technology destroy callback
+                * because we have the bridge locked.
                 */
-               bridge_channel2 = find_bridge_channel(bridge_channel->bridge, bridge_channel->swap);
-               bridge_channel->swap = NULL;
-               if (bridge_channel2) {
-                       ast_debug(1, "Swapping bridge channel %p out from bridge %p so bridge channel %p can slip in\n", bridge_channel2, bridge_channel->bridge, bridge_channel);
-                       ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-               }
-       } else if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
-               /* Perform the smart bridge operation, basically see if we need to move around between technologies */
-               smart_bridge_operation(bridge_channel->bridge, bridge_channel, bridge_channel->bridge->num);
-       }
-
-       /* Make the channel compatible with the bridge */
-       bridge_make_compatible(bridge_channel->bridge, bridge_channel);
-
-       /* Tell the bridge technology we are joining so they set us up */
-       if (bridge_channel->bridge->technology->join) {
-               ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
-               if (bridge_channel->bridge->technology->join(bridge_channel->bridge, bridge_channel)) {
-                       ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
-               }
-       }
-
-       /* Actually execute the respective threading model, and keep our bridge thread alive */
-       while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
-               /* Update bridge pointer on channel */
-               ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
-               /* If the technology requires a thread and one is not running, start it up */
-               if (bridge_channel->bridge->thread == AST_PTHREADT_NULL
-                       && (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD)) {
-                       bridge_channel->bridge->stop = 0;
-                       ast_debug(1, "Starting a bridge thread for bridge %p\n", bridge_channel->bridge);
-                       ao2_ref(bridge_channel->bridge, +1);
-                       if (ast_pthread_create(&bridge_channel->bridge->thread, NULL, bridge_thread, bridge_channel->bridge)) {
-                               ast_debug(1, "Failed to create a bridge thread for bridge %p, giving it another go.\n", bridge_channel->bridge);
-                               ao2_ref(bridge_channel->bridge, -1);
-                               continue;
-                       }
-               }
-               /* Execute the threading model */
-               state = (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTITHREADED)
-                       ? bridge_channel_join_multithreaded(bridge_channel)
-                       : bridge_channel_join_singlethreaded(bridge_channel);
-               /* Depending on the above state see what we need to do */
-               switch (state) {
-               case AST_BRIDGE_CHANNEL_STATE_FEATURE:
-                       bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
-                       ao2_unlock(bridge_channel->bridge);
-                       bridge_channel_feature(bridge_channel->bridge, bridge_channel);
-                       ao2_lock(bridge_channel->bridge);
-                       ao2_lock(bridge_channel);
-                       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_FEATURE) {
-                               ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
-                       }
-                       ao2_unlock(bridge_channel);
-                       bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
-                       break;
-               case AST_BRIDGE_CHANNEL_STATE_DTMF:
-                       bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
-                       ao2_unlock(bridge_channel->bridge);
-                       bridge_channel_dtmf_stream(bridge_channel);
-                       ao2_lock(bridge_channel->bridge);
-                       ao2_lock(bridge_channel);
-                       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_DTMF) {
-                               ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
-                       }
-                       ao2_unlock(bridge_channel);
-                       bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
-                       break;
-               case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
-               case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
-                       ao2_unlock(bridge_channel->bridge);
-                       bridge_channel_talking(bridge_channel->bridge, bridge_channel);
-                       ao2_lock(bridge_channel->bridge);
-                       ao2_lock(bridge_channel);
-                       switch (bridge_channel->state) {
-                       case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
-                       case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
-                               ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
-                               break;
-                       default:
-                               break;
-                       }
-                       ao2_unlock(bridge_channel);
-                       break;
-               default:
-                       break;
+               deferred_action = ast_frdup(&action);
+               if (!deferred_action) {
+                       ast_module_unref(new_technology->mod);
+                       return -1;
                }
+       } else {
+               deferred_action = NULL;
        }
 
-       ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+       /*
+        * We are now committed to changing the bridge technology.  We
+        * must not release the bridge lock until we have installed the
+        * new bridge technology.
+        */
+       ast_debug(1, "Bridge %s: switching %s technology to %s\n",
+               bridge->uniqueid, old_technology->name, new_technology->name);
 
-       /* See if we need to dissolve the bridge itself if they hung up */
-       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_END) {
-               bridge_check_dissolve(bridge_channel->bridge, bridge_channel);
+       /*
+        * Since we are soon going to pass this bridge to a new
+        * technology we need to NULL out the tech_pvt pointer but
+        * don't worry as it still exists in dummy_bridge, ditto for the
+        * old technology.
+        */
+       bridge->tech_pvt = NULL;
+       bridge->technology = new_technology;
+
+       /* Setup the new bridge technology. */
+       ast_debug(1, "Bridge %s: calling %s technology constructor\n",
+               bridge->uniqueid, new_technology->name);
+       if (new_technology->create && new_technology->create(bridge)) {
+               ast_log(LOG_WARNING, "Bridge %s: failed to setup bridge technology %s\n",
+                       bridge->uniqueid, new_technology->name);
+               bridge->tech_pvt = dummy_bridge.tech_pvt;
+               bridge->technology = dummy_bridge.technology;
+               ast_module_unref(new_technology->mod);
+               return -1;
        }
 
-       /* Tell the bridge technology we are leaving so they tear us down */
-       if (bridge_channel->bridge->technology->leave) {
-               ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
-               if (bridge_channel->bridge->technology->leave(bridge_channel->bridge, bridge_channel)) {
-                       ast_debug(1, "Bridge technology %s failed to leave %p from bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge);
+       /* Move existing channels over to the new technology. */
+       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+               if (bridge_channel->just_joined) {
+                       /*
+                        * This channel has not completed joining the bridge so it is
+                        * not in the old bridge technology.
+                        */
+                       continue;
+               }
+
+               /* First we part them from the old technology */
+               ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology (dummy)\n",
+                       dummy_bridge.uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                       old_technology->name);
+               if (old_technology->leave) {
+                       old_technology->leave(&dummy_bridge, bridge_channel);
                }
-       }
 
-       /* Remove channel from the bridge */
-       bridge_channel->bridge->num--;
-       AST_LIST_REMOVE(&bridge_channel->bridge->channels, bridge_channel, entry);
+               /* Second we make them compatible again with the bridge */
+               bridge_make_compatible(bridge, bridge_channel);
 
-       bridge_array_remove(bridge_channel->bridge, bridge_channel->chan);
+               /* Third we join them to the new technology */
+               ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n",
+                       bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                       new_technology->name);
+               if (new_technology->join && new_technology->join(bridge, bridge_channel)) {
+                       ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+                               bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
+                               new_technology->name);
+               }
+       }
 
-       /* Perform the smart bridge operation if needed since a channel has left */
-       if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) {
-               smart_bridge_operation(bridge_channel->bridge, NULL, bridge_channel->bridge->num);
+       /*
+        * Now that all the channels have been moved over we need to get
+        * rid of all the information the old technology may have left
+        * around.
+        */
+       if (old_technology->destroy) {
+               ast_debug(1, "Bridge %s: deferring %s technology destructor\n",
+                       bridge->uniqueid, old_technology->name);
+               bridge_queue_action_nodup(bridge, deferred_action);
+       } else {
+               ast_debug(1, "Bridge %s: calling %s technology destructor\n",
+                       bridge->uniqueid, old_technology->name);
+               ast_module_unref(old_technology->mod);
        }
 
-       ao2_unlock(bridge_channel->bridge);
+       return 0;
+}
 
-       /* Restore original formats of the channel as they came in */
-       if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &formats[0]) == AST_FORMAT_CMP_NOT_EQUAL) {
-               ast_debug(1, "Bridge is returning %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id);
-               if (ast_set_read_format(bridge_channel->chan, &formats[0])) {
-                       ast_debug(1, "Bridge failed to return channel %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id);
-               }
+/*!
+ * \internal
+ * \brief Notify the bridge that it has been reconfigured.
+ * \since 12.0.0
+ *
+ * \param bridge Reconfigured bridge.
+ *
+ * \details
+ * After a series of bridge_channel_push and
+ * bridge_channel_pull calls, you need to call this function
+ * to cause the bridge to complete restruturing for the change
+ * in the channel makeup of the bridge.
+ *
+ * \note On entry, the bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_reconfigured(struct ast_bridge *bridge)
+{
+       if (!bridge->reconfigured) {
+               return;
        }
-       if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &formats[1]) == AST_FORMAT_CMP_NOT_EQUAL) {
-               ast_debug(1, "Bridge is returning %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id);
-               if (ast_set_write_format(bridge_channel->chan, &formats[1])) {
-                       ast_debug(1, "Bridge failed to return channel %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id);
-               }
+       bridge->reconfigured = 0;
+       if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_SMART)
+               && smart_bridge_operation(bridge)) {
+               /* Smart bridge failed. */
+               bridge_dissolve(bridge);
+               return;
+       }
+       bridge_complete_join(bridge);
+}
+
+/*!
+ * \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
+ */
+static void bridge_channel_suspend_nolock(struct ast_bridge_channel *bridge_channel)
+{
+       bridge_channel->suspended = 1;
+       if (bridge_channel->in_bridge) {
+               --bridge_channel->bridge->num_active;
        }
 
-       return bridge_channel->state;
+       /* Get technology bridge threads off of the channel. */
+       if (bridge_channel->bridge->technology->suspend) {
+               bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel);
+       }
 }
 
-static void bridge_channel_destroy(void *obj)
+/*!
+ * \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)
 {
-       struct ast_bridge_channel *bridge_channel = obj;
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       bridge_channel_suspend_nolock(bridge_channel);
+       ast_bridge_unlock(bridge_channel->bridge);
+}
 
-       if (bridge_channel->callid) {
-               bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
+/*!
+ * \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
+ */
+static void bridge_channel_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_unsuspend_nolock(bridge_channel);
+       ast_bridge_unlock(bridge_channel->bridge);
+}
+
+/*! \brief Internal function that activates interval hooks on a bridge channel */
+static void bridge_channel_interval(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_hook *hook;
+       struct timeval start;
+
+       ast_heap_wrlock(bridge_channel->features->interval_hooks);
+       start = ast_tvnow();
+       while ((hook = ast_heap_peek(bridge_channel->features->interval_hooks, 1))) {
+               int interval;
+               unsigned int execution_time;
+
+               if (ast_tvdiff_ms(hook->parms.timer.trip_time, start) > 0) {
+                       ast_debug(1, "Hook %p on %p(%s) wants to happen in the future, stopping our traversal\n",
+                               hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+                       break;
+               }
+               ao2_ref(hook, +1);
+               ast_heap_unlock(bridge_channel->features->interval_hooks);
+
+               ast_debug(1, "Executing hook %p on %p(%s)\n",
+                       hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+               interval = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+
+               ast_heap_wrlock(bridge_channel->features->interval_hooks);
+               if (ast_heap_peek(bridge_channel->features->interval_hooks,
+                       hook->parms.timer.heap_index) != hook
+                       || !ast_heap_remove(bridge_channel->features->interval_hooks, hook)) {
+                       /* Interval hook is already removed from the bridge_channel. */
+                       ao2_ref(hook, -1);
+                       continue;
+               }
+               ao2_ref(hook, -1);
+
+               if (interval < 0) {
+                       ast_debug(1, "Removed interval hook %p from %p(%s)\n",
+                               hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+                       ao2_ref(hook, -1);
+                       continue;
+               }
+               if (interval) {
+                       /* Set new interval for the hook. */
+                       hook->parms.timer.interval = interval;
+               }
+
+               ast_debug(1, "Updating interval hook %p with interval %u on %p(%s)\n",
+                       hook, hook->parms.timer.interval, bridge_channel,
+                       ast_channel_name(bridge_channel->chan));
+
+               /* resetting start */
+               start = ast_tvnow();
+
+               /*
+                * Resetup the interval hook for the next interval.  We may need
+                * to skip over any missed intervals because the hook was
+                * delayed or took too long.
+                */
+               execution_time = ast_tvdiff_ms(start, hook->parms.timer.trip_time);
+               while (hook->parms.timer.interval < execution_time) {
+                       execution_time -= hook->parms.timer.interval;
+               }
+               hook->parms.timer.trip_time = ast_tvadd(start, ast_samp2tv(hook->parms.timer.interval - execution_time, 1000));
+               hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &bridge_channel->features->interval_sequence, +1);
+
+               if (ast_heap_push(bridge_channel->features->interval_hooks, hook)) {
+                       /* Could not push the hook back onto the heap. */
+                       ao2_ref(hook, -1);
+               }
+       }
+       ast_heap_unlock(bridge_channel->features->interval_hooks);
+}
+
+static void bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
+{
+       ast_bridge_channel_write_action_data(bridge_channel,
+               AST_BRIDGE_ACTION_DTMF_STREAM, dtmf, strlen(dtmf) + 1);
+}
+
+/*!
+ * \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)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook = NULL;
+       char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
+       size_t dtmf_len = 0;
+
+       /* 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);
+
+       /* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */
+       do {
+               int res;
+
+               /* If the above timed out simply exit */
+               res = ast_waitfordigit(bridge_channel->chan, 3000);
+               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;
+               }
+
+/* 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->parms.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);
+
+       /* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */
+       if (hook) {
+               int failed;
+
+               failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+               if (failed) {
+                       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);
+               }
+               ao2_ref(hook, -1);
+
+               /*
+                * 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)) {
+                       bridge_handle_hangup(bridge_channel);
+               }
+       } else if (features->dtmf_passthrough) {
+               bridge_channel_write_dtmf_stream(bridge_channel, dtmf);
+       }
+}
+
+static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+
+       if (features->talker_cb) {
+               features->talker_cb(bridge_channel, features->talker_pvt_data, talking);
+       }
+}
+
+/*! \brief Internal function that plays back DTMF on a bridge channel */
+static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf)
+{
+       ast_debug(1, "Playing DTMF stream '%s' out to %p(%s)\n",
+               dtmf, bridge_channel, ast_channel_name(bridge_channel->chan));
+       ast_dtmf_stream(bridge_channel->chan, NULL, dtmf, 0, 0);
+}
+
+struct blind_transfer_data {
+       char exten[AST_MAX_EXTENSION];
+       char context[AST_MAX_CONTEXT];
+};
+
+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_handle_hangup(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel bridge action frame.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to execute the action on.
+ * \param action What to do.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel, struct ast_frame *action)
+{
+       switch (action->subclass.integer) {
+       case AST_BRIDGE_ACTION_INTERVAL:
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_interval(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+               break;
+       case AST_BRIDGE_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;
+       case AST_BRIDGE_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);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+               break;
+       case AST_BRIDGE_ACTION_TALKING_START:
+       case AST_BRIDGE_ACTION_TALKING_STOP:
+               bridge_channel_talking(bridge_channel,
+                       action->subclass.integer == AST_BRIDGE_ACTION_TALKING_START);
+               break;
+       case AST_BRIDGE_ACTION_PLAY_FILE:
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_playfile(bridge_channel, action->data.ptr);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+               break;
+       case AST_BRIDGE_ACTION_PARK:
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_park(bridge_channel, action->data.ptr);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+               break;
+       case AST_BRIDGE_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);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+               break;
+       case AST_BRIDGE_ACTION_BLIND_TRANSFER:
+               bridge_channel_blind_transfer(bridge_channel, action->data.ptr);
+               break;
+       default:
+               break;
+       }
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel control frame action.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to execute the control frame action on.
+ * \param fr Control frame to handle.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr)
+{
+       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) {
+                       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) {
+                       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
+                * are ONLY sent by chan_iax2 and I'm not convinced that they
+                * are useful. I haven't deleted them entirely because I just am
+                * not sure of the ramifications of removing them.
+                */
+               aoh = fr->data.ptr;
+               if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
+                       switch (ntohs(aoh->option)) {
+                       case AST_OPTION_TONE_VERIFY:
+                       case AST_OPTION_TDD:
+                       case AST_OPTION_RELAXDTMF:
+                       case AST_OPTION_AUDIO_MODE:
+                       case AST_OPTION_DIGIT_DETECT:
+                       case AST_OPTION_FAX_DETECT:
+                               ast_channel_setoption(chan, ntohs(aoh->option), aoh->data,
+                                       fr->datalen - sizeof(*aoh), 0);
+                               break;
+                       default:
+                               break;
+                       }
+               }
+               break;
+       case AST_CONTROL_ANSWER:
+               if (ast_channel_state(chan) != AST_STATE_UP) {
+                       ast_answer(chan);
+               } else {
+                       ast_indicate(chan, -1);
+               }
+               break;
+       default:
+               ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+               break;
+       }
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel write frame to channel.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to write outgoing frame.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_frame *fr;
+       char nudge;
+
+       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));
+               }
+       }
+       fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list);
+       ast_bridge_channel_unlock(bridge_channel);
+       if (!fr) {
+               return;
+       }
+       switch (fr->frametype) {
+       case AST_FRAME_BRIDGE_ACTION:
+               bridge_channel_handle_action(bridge_channel, fr);
+               break;
+       case AST_FRAME_CONTROL:
+               bridge_channel_handle_control(bridge_channel, fr);
+               break;
+       case AST_FRAME_NULL:
+               break;
+       default:
+               /* Write the frame to the channel. */
+               bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_SIMPLE;
+               ast_write(bridge_channel->chan, fr);
+               break;
+       }
+       ast_frfree(fr);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel interval expiration.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to check interval on.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_timer *interval_timer;
+
+       interval_timer = bridge_channel->features->interval_timer;
+       if (interval_timer) {
+               if (ast_wait_for_input(ast_timer_fd(interval_timer), 0) == 1) {
+                       ast_timer_ack(interval_timer, 1);
+                       if (bridge_channel_interval_ready(bridge_channel)) {
+/* BUGBUG since this is now only run by the channel thread, there is no need to queue the action once this intervals become a first class wait item in bridge_channel_wait(). */
+                               struct ast_frame interval_action = {
+                                       .frametype = AST_FRAME_BRIDGE_ACTION,
+                                       .subclass.integer = AST_BRIDGE_ACTION_INTERVAL,
+                               };
+
+                               ast_bridge_channel_queue_frame(bridge_channel, &interval_action);
+                       }
+               }
+       }
+}
+
+/*!
+ * \internal
+ * \brief Wait for something to happen on the bridge channel and handle it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel to wait.
+ *
+ * \note Each channel does writing/reading in their own thread.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel)
+{
+       int ms = -1;
+       int outfd;
+       struct ast_channel *chan;
+
+       /* Wait for data to either come from the channel or us to be signaled */
+       ast_bridge_channel_lock(bridge_channel);
+       if (bridge_channel->state != AST_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 */
+               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;
+/* BUGBUG need to make the next expiring active interval setup ms timeout rather than holding up the chan reads. */
+               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);
+                       bridge_channel->bridge->reconfigured = 1;
+                       bridge_reconfigured(bridge_channel->bridge);
+                       ast_bridge_unlock(bridge_channel->bridge);
+               }
+               ast_bridge_channel_lock(bridge_channel);
+               bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_FRAME;
+               ast_bridge_channel_unlock(bridge_channel);
+               if (!bridge_channel->suspended
+                       && bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                       if (chan) {
+                               bridge_channel_handle_interval(bridge_channel);
+                               bridge_handle_trip(bridge_channel);
+                       } else if (-1 < outfd) {
+                               bridge_channel_handle_write(bridge_channel);
+                       }
+               }
+               bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_IDLE;
+               return;
+       }
+       ast_bridge_channel_unlock(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel join event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is joining.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_join(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook;
+       struct ao2_iterator iter;
+
+       /* Run any join hooks. */
+       iter = ao2_iterator_init(features->join_hooks, AO2_ITERATOR_UNLINK);
+       hook = ao2_iterator_next(&iter);
+       if (hook) {
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               do {
+                       hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+                       ao2_ref(hook, -1);
+               } while ((hook = ao2_iterator_next(&iter)));
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+       }
+       ao2_iterator_destroy(&iter);
+}
+
+/*!
+ * \internal
+ * \brief Handle bridge channel leave event.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Which channel is leaving.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_handle_leave(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge_features *features = bridge_channel->features;
+       struct ast_bridge_hook *hook;
+       struct ao2_iterator iter;
+
+       /* Run any leave hooks. */
+       iter = ao2_iterator_init(features->leave_hooks, AO2_ITERATOR_UNLINK);
+       hook = ao2_iterator_next(&iter);
+       if (hook) {
+               bridge_channel_suspend(bridge_channel);
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               do {
+                       hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt);
+                       ao2_ref(hook, -1);
+               } while ((hook = ao2_iterator_next(&iter)));
+               ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE);
+               bridge_channel_unsuspend(bridge_channel);
+       }
+       ao2_iterator_destroy(&iter);
+}
+
+/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
+static void bridge_channel_join(struct ast_bridge_channel *bridge_channel)
+{
+       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));
+
+       ast_debug(1, "Bridge %s: %p(%s) is joining\n",
+               bridge_channel->bridge->uniqueid,
+               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.
+        */
+       ast_channel_lock(bridge_channel->chan);
+       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_push(bridge_channel)) {
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+       }
+       bridge_reconfigured(bridge_channel->bridge);
+
+       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               /*
+                * Indicate a source change since this channel is entering the
+                * bridge system only if the bridge technology is not MULTIMIX
+                * capable.  The MULTIMIX technology has already done it.
+                */
+               if (!(bridge_channel->bridge->technology->capabilities
+                       & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+                       ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
+               }
+
+               ast_bridge_unlock(bridge_channel->bridge);
+               bridge_channel_handle_join(bridge_channel);
+               while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                       /* Wait for something to do. */
+                       bridge_channel_wait(bridge_channel);
+               }
+               bridge_channel_handle_leave(bridge_channel);
+               ast_bridge_channel_lock_bridge(bridge_channel);
+       }
+
+       bridge_channel_pull(bridge_channel);
+       bridge_reconfigured(bridge_channel->bridge);
+
+       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);
+
+/* 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_bridge_end_dtmf(bridge_channel->chan,
+                       ast_channel_sending_dtmf_digit(bridge_channel->chan),
+                       ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end");
+       }
+
+       /*
+        * Wait for any dual redirect to complete.
+        *
+        * Must be done while "still in the bridge" for ast_async_goto()
+        * to work right.
+        */
+       while (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
+               sched_yield();
+       }
+       ast_channel_lock(bridge_channel->chan);
+       ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+       ast_channel_unlock(bridge_channel->chan);
+
+       ast_bridge_channel_restore_formats(bridge_channel);
+}
+
+/*!
+ * \internal
+ * \brief Close a pipe.
+ * \since 12.0.0
+ *
+ * \param my_pipe What to close.
+ *
+ * \return Nothing
+ */
+static void pipe_close(int *my_pipe)
+{
+       if (my_pipe[0] > -1) {
+               close(my_pipe[0]);
+               my_pipe[0] = -1;
+       }
+       if (my_pipe[1] > -1) {
+               close(my_pipe[1]);
+               my_pipe[1] = -1;
+       }
+}
+
+/*!
+ * \internal
+ * \brief Initialize a pipe as non-blocking.
+ * \since 12.0.0
+ *
+ * \param my_pipe What to initialize.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int pipe_init_nonblock(int *my_pipe)
+{
+       int flags;
+
+       my_pipe[0] = -1;
+       my_pipe[1] = -1;
+       if (pipe(my_pipe)) {
+               ast_log(LOG_WARNING, "Can't create pipe! Try increasing max file descriptors with ulimit -n\n");
+               return -1;
+       }
+       flags = fcntl(my_pipe[0], F_GETFL);
+       if (fcntl(my_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
+               ast_log(LOG_WARNING, "Unable to set read pipe nonblocking! (%d: %s)\n",
+                       errno, strerror(errno));
+               return -1;
+       }
+       flags = fcntl(my_pipe[1], F_GETFL);
+       if (fcntl(my_pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) {
+               ast_log(LOG_WARNING, "Unable to set write pipe nonblocking! (%d: %s)\n",
+                       errno, strerror(errno));
+               return -1;
+       }
+       return 0;
+}
+
+/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
+static void bridge_channel_destroy(void *obj)
+{
+       struct ast_bridge_channel *bridge_channel = obj;
+       struct ast_frame *fr;
+
+       if (bridge_channel->callid) {
+               bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
+       }
+
+       if (bridge_channel->bridge) {
+               ao2_ref(bridge_channel->bridge, -1);
+               bridge_channel->bridge = NULL;
+       }
+
+       /* Flush any unhandled wr_queue frames. */
+       while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list))) {
+               ast_frfree(fr);
+       }
+       pipe_close(bridge_channel->alert_pipe);
+
+       ast_cond_destroy(&bridge_channel->cond);
+}
+
+static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
+{
+       struct ast_bridge_channel *bridge_channel;
+
+       bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy);
+       if (!bridge_channel) {
+               return NULL;
+       }
+       ast_cond_init(&bridge_channel->cond, NULL);
+       if (pipe_init_nonblock(bridge_channel->alert_pipe)) {
+               ao2_ref(bridge_channel, -1);
+               return NULL;
+       }
+       if (bridge) {
+               bridge_channel->bridge = bridge;
+               ao2_ref(bridge_channel->bridge, +1);
+       }
+
+       return bridge_channel;
+}
+
+struct after_bridge_cb_ds {
+       /*! Desired callback function. */
+       ast_after_bridge_cb callback;
+       /*! After bridge callback will not be called and destroy any resources data may contain. */
+       ast_after_bridge_cb_failed failed;
+       /*! Extra data to pass to the callback. */
+       void *data;
+};
+
+/*!
+ * \internal
+ * \brief Destroy the after bridge callback datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge callback data to destroy.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_destroy(void *data)
+{
+       struct after_bridge_cb_ds *after_bridge = data;
+
+       if (after_bridge->failed) {
+               after_bridge->failed(AST_AFTER_BRIDGE_CB_REASON_DESTROY, after_bridge->data);
+               after_bridge->failed = NULL;
+       }
+}
+
+/*!
+ * \internal
+ * \brief Fixup the after bridge callback datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge callback data to fixup.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void after_bridge_cb_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+       /* There can be only one.  Discard any already on the new channel. */
+       ast_after_bridge_callback_discard(new_chan, AST_AFTER_BRIDGE_CB_REASON_MASQUERADE);
+}
+
+static const struct ast_datastore_info after_bridge_cb_info = {
+       .type = "after-bridge-cb",
+       .destroy = after_bridge_cb_destroy,
+       .chan_fixup = after_bridge_cb_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Remove channel after the bridge callback and return it.
+ * \since 12.0.0
+ *
+ * \param chan Channel to remove after bridge callback.
+ *
+ * \retval datastore on success.
+ * \retval NULL on error or not found.
+ */
+static struct ast_datastore *after_bridge_cb_remove(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       ast_channel_lock(chan);
+       datastore = ast_channel_datastore_find(chan, &after_bridge_cb_info, NULL);
+       if (datastore && ast_channel_datastore_remove(chan, datastore)) {
+               datastore = NULL;
+       }
+       ast_channel_unlock(chan);
+
+       return datastore;
+}
+
+void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason)
+{
+       struct ast_datastore *datastore;
+
+       datastore = after_bridge_cb_remove(chan);
+       if (datastore) {
+               struct after_bridge_cb_ds *after_bridge = datastore->data;
+
+               if (after_bridge && after_bridge->failed) {
+                       after_bridge->failed(reason, after_bridge->data);
+                       after_bridge->failed = NULL;
+               }
+               ast_datastore_free(datastore);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Run any after bridge callback if possible.
+ * \since 12.0.0
+ *
+ * \param chan Channel to run after bridge callback.
+ *
+ * \return Nothing
+ */
+static void after_bridge_callback_run(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+       struct after_bridge_cb_ds *after_bridge;
+
+       if (ast_check_hangup(chan)) {
+               return;
+       }
+
+       /* Get after bridge goto datastore. */
+       datastore = after_bridge_cb_remove(chan);
+       if (!datastore) {
+               return;
+       }
+
+       after_bridge = datastore->data;
+       if (after_bridge) {
+               after_bridge->failed = NULL;
+               after_bridge->callback(chan, after_bridge->data);
+       }
+
+       /* Discard after bridge callback datastore. */
+       ast_datastore_free(datastore);
+}
+
+int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data)
+{
+       struct ast_datastore *datastore;
+       struct after_bridge_cb_ds *after_bridge;
+
+       /* Sanity checks. */
+       ast_assert(chan != NULL);
+       if (!chan || !callback) {
+               return -1;
+       }
+
+       /* Create a new datastore. */
+       datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL);
+       if (!datastore) {
+               return -1;
+       }
+       after_bridge = ast_calloc(1, sizeof(*after_bridge));
+       if (!after_bridge) {
+               ast_datastore_free(datastore);
+               return -1;
+       }
+
+       /* Initialize it. */
+       after_bridge->callback = callback;
+       after_bridge->failed = failed;
+       after_bridge->data = data;
+       datastore->data = after_bridge;
+
+       /* Put it on the channel replacing any existing one. */
+       ast_channel_lock(chan);
+       ast_after_bridge_callback_discard(chan, AST_AFTER_BRIDGE_CB_REASON_REPLACED);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+
+       return 0;
+}
+
+struct after_bridge_goto_ds {
+       /*! Goto string that can be parsed by ast_parseable_goto(). */
+       const char *parseable_goto;
+       /*! Specific goto context or default context for parseable_goto. */
+       const char *context;
+       /*! Specific goto exten or default exten for parseable_goto. */
+       const char *exten;
+       /*! Specific goto priority or default priority for parseable_goto. */
+       int priority;
+       /*! TRUE if the peer should run the h exten. */
+       unsigned int run_h_exten:1;
+       /*! Specific goto location */
+       unsigned int specific:1;
+};
+
+/*!
+ * \internal
+ * \brief Destroy the after bridge goto datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge goto data to destroy.
+ *
+ * \return Nothing
+ */
+static void after_bridge_goto_destroy(void *data)
+{
+       struct after_bridge_goto_ds *after_bridge = data;
+
+       ast_free((char *) after_bridge->parseable_goto);
+       ast_free((char *) after_bridge->context);
+       ast_free((char *) after_bridge->exten);
+}
+
+/*!
+ * \internal
+ * \brief Fixup the after bridge goto datastore.
+ * \since 12.0.0
+ *
+ * \param data After bridge goto data to fixup.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void after_bridge_goto_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+       /* There can be only one.  Discard any already on the new channel. */
+       ast_after_bridge_goto_discard(new_chan);
+}
+
+static const struct ast_datastore_info after_bridge_goto_info = {
+       .type = "after-bridge-goto",
+       .destroy = after_bridge_goto_destroy,
+       .chan_fixup = after_bridge_goto_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Remove channel goto location after the bridge and return it.
+ * \since 12.0.0
+ *
+ * \param chan Channel to remove after bridge goto location.
+ *
+ * \retval datastore on success.
+ * \retval NULL on error or not found.
+ */
+static struct ast_datastore *after_bridge_goto_remove(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       ast_channel_lock(chan);
+       datastore = ast_channel_datastore_find(chan, &after_bridge_goto_info, NULL);
+       if (datastore && ast_channel_datastore_remove(chan, datastore)) {
+               datastore = NULL;
+       }
+       ast_channel_unlock(chan);
+
+       return datastore;
+}
+
+void ast_after_bridge_goto_discard(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       datastore = after_bridge_goto_remove(chan);
+       if (datastore) {
+               ast_datastore_free(datastore);
+       }
+}
+
+int ast_after_bridge_goto_setup(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+       struct after_bridge_goto_ds *after_bridge;
+       int goto_failed = -1;
+
+       /* Determine if we are going to setup a dialplan location and where. */
+       if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+               /* An async goto has already setup a location. */
+               ast_channel_clear_softhangup(chan, AST_SOFTHANGUP_ASYNCGOTO);
+               if (!ast_check_hangup(chan)) {
+                       goto_failed = 0;
+               }
+               return goto_failed;
+       }
+
+       /* Get after bridge goto datastore. */
+       datastore = after_bridge_goto_remove(chan);
+       if (!datastore) {
+               return goto_failed;
+       }
+
+       after_bridge = datastore->data;
+       if (after_bridge->run_h_exten) {
+               if (ast_exists_extension(chan, after_bridge->context, "h", 1,
+                       S_COR(ast_channel_caller(chan)->id.number.valid,
+                               ast_channel_caller(chan)->id.number.str, NULL))) {
+                       ast_debug(1, "Running after bridge goto h exten %s,h,1\n",
+                               ast_channel_context(chan));
+                       ast_pbx_h_exten_run(chan, after_bridge->context);
+               }
+       } else if (!ast_check_hangup(chan)) {
+               if (after_bridge->specific) {
+                       goto_failed = ast_explicit_goto(chan, after_bridge->context,
+                               after_bridge->exten, after_bridge->priority);
+               } else if (!ast_strlen_zero(after_bridge->parseable_goto)) {
+                       char *context;
+                       char *exten;
+                       int priority;
+
+                       /* Option F(x) for Bridge(), Dial(), and Queue() */
+
+                       /* Save current dialplan location in case of failure. */
+                       context = ast_strdupa(ast_channel_context(chan));
+                       exten = ast_strdupa(ast_channel_exten(chan));
+                       priority = ast_channel_priority(chan);
+
+                       /* Set current dialplan position to default dialplan position */
+                       ast_explicit_goto(chan, after_bridge->context, after_bridge->exten,
+                               after_bridge->priority);
+
+                       /* Then perform the goto */
+                       goto_failed = ast_parseable_goto(chan, after_bridge->parseable_goto);
+                       if (goto_failed) {
+                               /* Restore original dialplan location. */
+                               ast_channel_context_set(chan, context);
+                               ast_channel_exten_set(chan, exten);
+                               ast_channel_priority_set(chan, priority);
+                       }
+               } else {
+                       /* Option F() for Bridge(), Dial(), and Queue() */
+                       goto_failed = ast_goto_if_exists(chan, after_bridge->context,
+                               after_bridge->exten, after_bridge->priority + 1);
+               }
+               if (!goto_failed) {
+                       ast_debug(1, "Setup after bridge goto location to %s,%s,%d.\n",
+                               ast_channel_context(chan),
+                               ast_channel_exten(chan),
+                               ast_channel_priority(chan));
+               }
+       }
+
+       /* Discard after bridge goto datastore. */
+       ast_datastore_free(datastore);
+
+       return goto_failed;
+}
+
+void ast_after_bridge_goto_run(struct ast_channel *chan)
+{
+       int goto_failed;
+
+       goto_failed = ast_after_bridge_goto_setup(chan);
+       if (goto_failed || ast_pbx_run(chan)) {
+               ast_hangup(chan);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Set after bridge goto location of channel.
+ * \since 12.0.0
+ *
+ * \param chan Channel to setup after bridge goto location.
+ * \param run_h_exten TRUE if the h exten should be run.
+ * \param specific TRUE if the context/exten/priority is exactly specified.
+ * \param context Context to goto after bridge.
+ * \param exten Exten to goto after bridge. (Could be NULL if run_h_exten)
+ * \param priority Priority to goto after bridge.
+ * \param parseable_goto User specified goto string. (Could be NULL)
+ *
+ * \details Add a channel datastore to setup the goto location
+ * when the channel leaves the bridge and run a PBX from there.
+ *
+ * If run_h_exten then execute the h exten found in the given context.
+ * Else if specific then goto the given context/exten/priority.
+ * Else if parseable_goto then use the given context/exten/priority
+ *   as the relative position for the parseable_goto.
+ * Else goto the given context/exten/priority+1.
+ *
+ * \return Nothing
+ */
+static void __after_bridge_set_goto(struct ast_channel *chan, int run_h_exten, int specific, const char *context, const char *exten, int priority, const char *parseable_goto)
+{
+       struct ast_datastore *datastore;
+       struct after_bridge_goto_ds *after_bridge;
+
+       /* Sanity checks. */
+       ast_assert(chan != NULL);
+       if (!chan) {
+               return;
+       }
+       if (run_h_exten) {
+               ast_assert(run_h_exten && context);
+               if (!context) {
+                       return;
+               }
+       } else {
+               ast_assert(context && exten && 0 < priority);
+               if (!context || !exten || priority < 1) {
+                       return;
+               }
+       }
+
+       /* Create a new datastore. */
+       datastore = ast_datastore_alloc(&after_bridge_goto_info, NULL);
+       if (!datastore) {
+               return;
+       }
+       after_bridge = ast_calloc(1, sizeof(*after_bridge));
+       if (!after_bridge) {
+               ast_datastore_free(datastore);
+               return;
+       }
+
+       /* Initialize it. */
+       after_bridge->parseable_goto = ast_strdup(parseable_goto);
+       after_bridge->context = ast_strdup(context);
+       after_bridge->exten = ast_strdup(exten);
+       after_bridge->priority = priority;
+       after_bridge->run_h_exten = run_h_exten ? 1 : 0;
+       after_bridge->specific = specific ? 1 : 0;
+       datastore->data = after_bridge;
+       if ((parseable_goto && !after_bridge->parseable_goto)
+               || (context && !after_bridge->context)
+               || (exten && !after_bridge->exten)) {
+               ast_datastore_free(datastore);
+               return;
+       }
+
+       /* Put it on the channel replacing any existing one. */
+       ast_channel_lock(chan);
+       ast_after_bridge_goto_discard(chan);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+}
+
+void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
+{
+       __after_bridge_set_goto(chan, 0, 1, context, exten, priority, NULL);
+}
+
+void ast_after_bridge_set_h(struct ast_channel *chan, const char *context)
+{
+       __after_bridge_set_goto(chan, 1, 0, context, NULL, 1, NULL);
+}
+
+void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto)
+{
+       char *p_goto;
+
+       if (!ast_strlen_zero(parseable_goto)) {
+               p_goto = ast_strdupa(parseable_goto);
+               ast_replace_subargument_delimiter(p_goto);
+       } else {
+               p_goto = NULL;
+       }
+       __after_bridge_set_goto(chan, 0, 0, context, exten, priority, p_goto);
+}
+
+void ast_bridge_notify_masquerade(struct ast_channel *chan)
+{
+       struct ast_bridge_channel *bridge_channel;
+       struct ast_bridge *bridge;
+
+       /* Safely get the bridge_channel pointer for the chan. */
+       ast_channel_lock(chan);
+       bridge_channel = ast_channel_get_bridge_channel(chan);
+       ast_channel_unlock(chan);
+       if (!bridge_channel) {
+               /* Not in a bridge */
+               return;
+       }
+
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       bridge = bridge_channel->bridge;
+       if (bridge_channel == find_bridge_channel(bridge, chan)) {
+/* BUGBUG this needs more work.  The channels need to be made compatible again if the formats change. The bridge_channel thread needs to monitor for this case. */
+               /* The channel we want to notify is still in a bridge. */
+               bridge->v_table->notify_masquerade(bridge, bridge_channel);
+               bridge_reconfigured(bridge);
+       }
+       ast_bridge_unlock(bridge);
+       ao2_ref(bridge_channel, -1);
+}
+
+/*
+ * BUGBUG make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back.
+ *
+ * This change is really going to break ConfBridge.  All other
+ * users are easily changed.  However, it is needed so the
+ * bridging code can manipulate features on all channels
+ * consistently no matter how they joined.
+ *
+ * Need to update the features parameter doxygen when this
+ * change is made to be like ast_bridge_impart().
+ */
+enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
+       struct ast_channel *chan,
+       struct ast_channel *swap,
+       struct ast_bridge_features *features,
+       struct ast_bridge_tech_optimizations *tech_args,
+       int pass_reference)
+{
+       struct ast_bridge_channel *bridge_channel;
+       enum ast_bridge_channel_state state;
+
+       bridge_channel = bridge_channel_alloc(bridge);
+       if (pass_reference) {
+               ao2_ref(bridge, -1);
+       }
+       if (!bridge_channel) {
+               state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+               goto join_exit;
+       }
+/* BUGBUG features cannot be NULL when passed in. When it is changed to allocated we can do like ast_bridge_impart() and allocate one. */
+       ast_assert(features != NULL);
+       if (!features) {
+               ao2_ref(bridge_channel, -1);
+               state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+               goto join_exit;
+       }
+       if (tech_args) {
+               bridge_channel->tech_args = *tech_args;
+       }
+
+       /* Initialize various other elements of the bridge channel structure that we can't do above */
+       ast_channel_lock(chan);
+       ast_channel_internal_bridge_channel_set(chan, bridge_channel);
+       ast_channel_unlock(chan);
+       bridge_channel->thread = pthread_self();
+       bridge_channel->chan = chan;
+       bridge_channel->swap = swap;
+       bridge_channel->features = features;
+
+       bridge_channel_join(bridge_channel);
+       state = bridge_channel->state;
+
+       /* Cleanup all the data in the bridge channel after it leaves the bridge. */
+       ast_channel_lock(chan);
+       ast_channel_internal_bridge_channel_set(chan, NULL);
+       ast_channel_unlock(chan);
+       bridge_channel->chan = NULL;
+       bridge_channel->swap = NULL;
+       bridge_channel->features = NULL;
+
+       ao2_ref(bridge_channel, -1);
+
+join_exit:;
+/* BUGBUG this is going to cause problems for DTMF atxfer attended bridge between B & C.  Maybe an ast_bridge_join_internal() that does not do the after bridge goto for this case. */
+       after_bridge_callback_run(chan);
+       if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO)
+               && !ast_after_bridge_goto_setup(chan)) {
+               /* Claim the after bridge goto is an async goto destination. */
+               ast_channel_lock(chan);
+               ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
+               ast_channel_unlock(chan);
+       }
+       return state;
+}
+
+/*! \brief Thread responsible for imparted bridged channels to be departed */
+static void *bridge_channel_depart_thread(void *data)
+{
+       struct ast_bridge_channel *bridge_channel = data;
+
+       if (bridge_channel->callid) {
+               ast_callid_threadassoc_add(bridge_channel->callid);
+       }
+
+       bridge_channel_join(bridge_channel);
+
+       /* cleanup */
+       bridge_channel->swap = NULL;
+       ast_bridge_features_destroy(bridge_channel->features);
+       bridge_channel->features = NULL;
+
+       ast_after_bridge_callback_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART);
+       ast_after_bridge_goto_discard(bridge_channel->chan);
+
+       return NULL;
+}
+
+/*! \brief Thread responsible for independent imparted bridged channels */
+static void *bridge_channel_ind_thread(void *data)
+{
+       struct ast_bridge_channel *bridge_channel = data;
+       struct ast_channel *chan;
+
+       if (bridge_channel->callid) {
+               ast_callid_threadassoc_add(bridge_channel->callid);
+       }
+
+       bridge_channel_join(bridge_channel);
+       chan = bridge_channel->chan;
+
+       /* cleanup */
+       ast_channel_lock(chan);
+       ast_channel_internal_bridge_channel_set(chan, NULL);
+       ast_channel_unlock(chan);
+       bridge_channel->chan = NULL;
+       bridge_channel->swap = NULL;
+       ast_bridge_features_destroy(bridge_channel->features);
+       bridge_channel->features = NULL;
+
+       ao2_ref(bridge_channel, -1);
+
+       after_bridge_callback_run(chan);
+       ast_after_bridge_goto_run(chan);
+       return NULL;
+}
+
+int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent)
+{
+       int res;
+       struct ast_bridge_channel *bridge_channel;
+
+       /* Supply an empty features structure if the caller did not. */
+       if (!features) {
+               features = ast_bridge_features_new();
+               if (!features) {
+                       return -1;
+               }
+       }
+
+       /* Try to allocate a structure for the bridge channel */
+       bridge_channel = bridge_channel_alloc(bridge);
+       if (!bridge_channel) {
+               ast_bridge_features_destroy(features);
+               return -1;
+       }
+
+       /* Setup various parameters */
+       ast_channel_lock(chan);
+       ast_channel_internal_bridge_channel_set(chan, bridge_channel);
+       ast_channel_unlock(chan);
+       bridge_channel->chan = chan;
+       bridge_channel->swap = swap;
+       bridge_channel->features = features;
+       bridge_channel->depart_wait = independent ? 0 : 1;
+       bridge_channel->callid = ast_read_threadstorage_callid();
+
+       /* Actually create the thread that will handle the channel */
+       if (independent) {
+               /* Independently imparted channels cannot have a PBX. */
+               ast_assert(!ast_channel_pbx(chan));
+
+               res = ast_pthread_create_detached(&bridge_channel->thread, NULL,
+                       bridge_channel_ind_thread, bridge_channel);
+       } else {
+               /* Imparted channels to be departed should not have a PBX either. */
+               ast_assert(!ast_channel_pbx(chan));
+
+               res = ast_pthread_create(&bridge_channel->thread, NULL,
+                       bridge_channel_depart_thread, bridge_channel);
+       }
+
+       if (res) {
+               /* cleanup */
+               ast_channel_lock(chan);
+               ast_channel_internal_bridge_channel_set(chan, NULL);
+               ast_channel_unlock(chan);
+               bridge_channel->chan = NULL;
+               bridge_channel->swap = NULL;
+               ast_bridge_features_destroy(bridge_channel->features);
+               bridge_channel->features = NULL;
+
+               ao2_ref(bridge_channel, -1);
+               return -1;
+       }
+
+       return 0;
+}
+
+int ast_bridge_depart(struct ast_channel *chan)
+{
+       struct ast_bridge_channel *bridge_channel;
+       int departable;
+
+       ast_channel_lock(chan);
+       bridge_channel = ast_channel_internal_bridge_channel(chan);
+       departable = bridge_channel && bridge_channel->depart_wait;
+       ast_channel_unlock(chan);
+       if (!departable) {
+               ast_log(LOG_ERROR, "Channel %s cannot be departed.\n",
+                       ast_channel_name(chan));
+               /*
+                * Should never happen.  It likely means that
+                * ast_bridge_depart() is called by two threads for the same
+                * channel, the channel was never imparted to be departed, or it
+                * has already been departed.
+                */
+               ast_assert(0);
+               return -1;
+       }
+
+       /*
+        * We are claiming the reference held by the depart bridge
+        * channel thread.
+        */
+
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+       /* Wait for the depart thread to die */
+       ast_debug(1, "Waiting for %p(%s) bridge thread to die.\n",
+               bridge_channel, ast_channel_name(bridge_channel->chan));
+       pthread_join(bridge_channel->thread, NULL);
+
+       ast_channel_lock(chan);
+       ast_channel_internal_bridge_channel_set(chan, NULL);
+       ast_channel_unlock(chan);
+
+       /* We can get rid of the bridge_channel after the depart thread has died. */
+       ao2_ref(bridge_channel, -1);
+       return 0;
+}
+
+int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       struct ast_bridge_channel *bridge_channel;
+
+       ast_bridge_lock(bridge);
+
+       /* Try to find the channel that we want to remove */
+       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+               ast_bridge_unlock(bridge);
+               return -1;
+       }
+
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+       ast_bridge_unlock(bridge);
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Point the bridge_channel to a new bridge.
+ * \since 12.0.0
+ *
+ * \param bridge_channel What is to point to a new bridge.
+ * \param new_bridge Where the bridge channel should point.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_channel, struct ast_bridge *new_bridge)
+{
+       struct ast_bridge *old_bridge;
+
+       ao2_ref(new_bridge, +1);
+       ast_bridge_channel_lock(bridge_channel);
+       ast_channel_lock(bridge_channel->chan);
+       old_bridge = bridge_channel->bridge;
+       bridge_channel->bridge = new_bridge;
+       ast_channel_internal_bridge_set(bridge_channel->chan, new_bridge);
+       ast_channel_unlock(bridge_channel->chan);
+       ast_bridge_channel_unlock(bridge_channel);
+       ao2_ref(old_bridge, -1);
+}
+
+/*!
+ * \internal
+ * \brief Do the merge of two bridges.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \return Nothing
+ *
+ * \note The two bridges are assumed already locked.
+ *
+ * This moves the channels in src_bridge into the bridge pointed
+ * to by dst_bridge.
+ */
+static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick)
+{
+       struct ast_bridge_channel *bridge_channel;
+       unsigned int idx;
+
+       ast_debug(1, "Merging bridge %s into bridge %s\n",
+               src_bridge->uniqueid, dst_bridge->uniqueid);
+
+       ast_bridge_publish_merge(dst_bridge, src_bridge);
+
+       /*
+        * Move channels from src_bridge over to dst_bridge.
+        *
+        * We must use AST_LIST_TRAVERSE_SAFE_BEGIN() because
+        * bridge_channel_pull() alters the list we are traversing.
+        */
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&src_bridge->channels, bridge_channel, entry) {
+               if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                       /*
+                        * The channel is already leaving let it leave normally because
+                        * pulling it may delete hooks that should run for this channel.
+                        */
+                       continue;
+               }
+               if (ast_test_flag(&bridge_channel->features->feature_flags,
+                       AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) {
+                       continue;
+               }
+
+               if (kick_me) {
+                       for (idx = 0; idx < num_kick; ++idx) {
+                               if (bridge_channel == kick_me[idx]) {
+                                       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+                                       break;
+                               }
+                       }
+               }
+               bridge_channel_pull(bridge_channel);
+               if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                       /*
+                        * The channel died as a result of being pulled or it was
+                        * kicked.  Leave it pointing to the original bridge.
+                        */
+                       continue;
+               }
+
+               /* Point to new bridge.*/
+               bridge_channel_change_bridge(bridge_channel, dst_bridge);
+
+               if (bridge_channel_push(bridge_channel)) {
+                       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+
+       if (kick_me) {
+               /*
+                * Now we can kick any channels in the dst_bridge without
+                * potentially dissolving the bridge.
+                */
+               for (idx = 0; idx < num_kick; ++idx) {
+                       bridge_channel = kick_me[idx];
+                       ast_bridge_channel_lock(bridge_channel);
+                       if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                               ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+                               bridge_channel_pull(bridge_channel);
+                       }
+                       ast_bridge_channel_unlock(bridge_channel);
+               }
+       }
+
+       bridge_reconfigured(dst_bridge);
+       bridge_reconfigured(src_bridge);
+
+       ast_debug(1, "Merged bridge %s into bridge %s\n",
+               src_bridge->uniqueid, dst_bridge->uniqueid);
+}
+
+struct merge_direction {
+       /*! Destination merge bridge. */
+       struct ast_bridge *dest;
+       /*! Source merge bridge. */
+       struct ast_bridge *src;
+};
+
+/*!
+ * \internal
+ * \brief Determine which bridge should merge into the other.
+ * \since 12.0.0
+ *
+ * \param bridge1 A bridge for merging
+ * \param bridge2 A bridge for merging
+ *
+ * \note The two bridges are assumed already locked.
+ *
+ * \return Which bridge merges into which or NULL bridges if cannot merge.
+ */
+static struct merge_direction bridge_merge_determine_direction(struct ast_bridge *bridge1, struct ast_bridge *bridge2)
+{
+       struct merge_direction merge = { NULL, NULL };
+       int bridge1_priority;
+       int bridge2_priority;
+
+       if (!ast_test_flag(&bridge1->feature_flags,
+                       AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)
+               && !ast_test_flag(&bridge2->feature_flags,
+                       AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+               /*
+                * Can merge either way.  Merge to the higher priority merge
+                * bridge.  Otherwise merge to the larger bridge.
+                */
+               bridge1_priority = bridge1->v_table->get_merge_priority(bridge1);
+               bridge2_priority = bridge2->v_table->get_merge_priority(bridge2);
+               if (bridge2_priority < bridge1_priority) {
+                       merge.dest = bridge1;
+                       merge.src = bridge2;
+               } else if (bridge1_priority < bridge2_priority) {
+                       merge.dest = bridge2;
+                       merge.src = bridge1;
+               } else {
+                       /* Merge to the larger bridge. */
+                       if (bridge2->num_channels <= bridge1->num_channels) {
+                               merge.dest = bridge1;
+                               merge.src = bridge2;
+                       } else {
+                               merge.dest = bridge2;
+                               merge.src = bridge1;
+                       }
+               }
+       } else if (!ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+               && !ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+               /* Can merge only one way. */
+               merge.dest = bridge1;
+               merge.src = bridge2;
+       } else if (!ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+               && !ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+               /* Can merge only one way. */
+               merge.dest = bridge2;
+               merge.src = bridge1;
+       }
+
+       return merge;
+}
+
+/*!
+ * \internal
+ * \brief Merge two bridges together
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of merge.
+ * \param src_bridge Source bridge of merge.
+ * \param merge_best_direction TRUE if don't care about which bridge merges into the other.
+ * \param kick_me Array of channels to kick from the bridges.
+ * \param num_kick Number of channels in the kick_me array.
+ *
+ * \note The dst_bridge and src_bridge are assumed already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_merge_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick)
+{
+       struct merge_direction merge;
+       struct ast_bridge_channel **kick_them = NULL;
+
+       /* Sanity check. */
+       ast_assert(dst_bridge && src_bridge && dst_bridge != src_bridge && (!num_kick || kick_me));
+
+       if (dst_bridge->dissolved || src_bridge->dissolved) {
+               ast_debug(1, "Can't merge bridges %s and %s, at least one bridge is dissolved.\n",
+                       src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+               || ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+               ast_debug(1, "Can't merge bridges %s and %s, masquerade only.\n",
+                       src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) {
+               ast_debug(1, "Can't merge bridges %s and %s, merging temporarily inhibited.\n",
+                       src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+
+       if (merge_best_direction) {
+               merge = bridge_merge_determine_direction(dst_bridge, src_bridge);
+       } else {
+               merge.dest = dst_bridge;
+               merge.src = src_bridge;
+       }
+
+       if (!merge.dest
+               || ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO)
+               || ast_test_flag(&merge.src->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) {
+               ast_debug(1, "Can't merge bridges %s and %s, merging inhibited.\n",
+                       src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (merge.src->num_channels < 2) {
+               /*
+                * For a two party bridge, a channel may be temporarily removed
+                * from the source bridge or the initial bridge members have not
+                * joined yet.
+                */
+               ast_debug(1, "Can't merge bridge %s into bridge %s, not enough channels in source bridge.\n",
+                       merge.src->uniqueid, merge.dest->uniqueid);
+               return -1;
+       }
+       if (2 + num_kick < merge.dest->num_channels + merge.src->num_channels
+               && !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+               && (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART)
+                       || !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) {
+               ast_debug(1, "Can't merge bridge %s into bridge %s, multimix is needed and it cannot be acquired.\n",
+                       merge.src->uniqueid, merge.dest->uniqueid);
+               return -1;
+       }
+
+       if (num_kick) {
+               unsigned int num_to_kick = 0;
+               unsigned int idx;
+
+               kick_them = ast_alloca(num_kick * sizeof(*kick_them));
+               for (idx = 0; idx < num_kick; ++idx) {
+                       kick_them[num_to_kick] = find_bridge_channel(merge.src, kick_me[idx]);
+                       if (!kick_them[num_to_kick]) {
+                               kick_them[num_to_kick] = find_bridge_channel(merge.dest, kick_me[idx]);
+                       }
+                       if (kick_them[num_to_kick]) {
+                               ++num_to_kick;
+                       }
+               }
+
+               if (num_to_kick != num_kick) {
+                       ast_debug(1, "Can't merge bridge %s into bridge %s, at least one kicked channel is not in either bridge.\n",
+                               merge.src->uniqueid, merge.dest->uniqueid);
+                       return -1;
+               }
+       }
+
+       bridge_merge_do(merge.dest, merge.src, kick_them, num_kick);
+       return 0;
+}
+
+int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick)
+{
+       int res;
+
+       /* Sanity check. */
+       ast_assert(dst_bridge && src_bridge);
+
+       ast_bridge_lock_both(dst_bridge, src_bridge);
+       res = bridge_merge_locked(dst_bridge, src_bridge, merge_best_direction, kick_me, num_kick);
+       ast_bridge_unlock(src_bridge);
+       ast_bridge_unlock(dst_bridge);
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Move a bridge channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param bridge_channel Channel moving from one bridge to another.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note The dst_bridge and bridge_channel->bridge are assumed already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery)
+{
+       struct ast_bridge *orig_bridge;
+       int was_in_bridge;
+       int res = 0;
+
+/* BUGBUG need bridge move stasis event and a success/fail event. */
+       if (bridge_channel->swap) {
+               ast_debug(1, "Moving %p(%s) into bridge %s swapping with %s\n",
+                       bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid,
+                       ast_channel_name(bridge_channel->swap));
+       } else {
+               ast_debug(1, "Moving %p(%s) into bridge %s\n",
+                       bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid);
+       }
+
+       orig_bridge = bridge_channel->bridge;
+       was_in_bridge = bridge_channel->in_bridge;
+
+       bridge_channel_pull(bridge_channel);
+       if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               /*
+                * The channel died as a result of being pulled.  Leave it
+                * pointing to the original bridge.
+                */
+               bridge_reconfigured(orig_bridge);
+               return -1;
+       }
+
+       /* Point to new bridge.*/
+       ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */
+       bridge_channel_change_bridge(bridge_channel, dst_bridge);
+
+       if (bridge_channel_push(bridge_channel)) {
+               /* Try to put the channel back into the original bridge. */
+               if (attempt_recovery && was_in_bridge) {
+                       /* Point back to original bridge. */
+                       bridge_channel_change_bridge(bridge_channel, orig_bridge);
+
+                       if (bridge_channel_push(bridge_channel)) {
+                               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+                       }
+               } else {
+                       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+               }
+               res = -1;
+       }
+
+       bridge_reconfigured(dst_bridge);
+       bridge_reconfigured(orig_bridge);
+       ao2_ref(orig_bridge, -1);
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Move a channel from one bridge to another.
+ * \since 12.0.0
+ *
+ * \param dst_bridge Destination bridge of bridge channel move.
+ * \param src_bridge Source bridge of bridge channel move.
+ * \param chan Channel to move.
+ * \param swap Channel to replace in dst_bridge.
+ * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge.
+ *
+ * \note The dst_bridge and src_bridge are assumed already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int bridge_move_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery)
+{
+       struct ast_bridge_channel *bridge_channel;
+
+       if (dst_bridge->dissolved || src_bridge->dissolved) {
+               ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, at least one bridge is dissolved.\n",
+                       ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+               || ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) {
+               ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, masquerade only.\n",
+                       ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) {
+               ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, temporarily inhibited.\n",
+                       ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+
+       bridge_channel = find_bridge_channel(src_bridge, chan);
+       if (!bridge_channel) {
+               ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel not in bridge.\n",
+                       ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+               ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel leaving bridge.\n",
+                       ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+       if (ast_test_flag(&bridge_channel->features->feature_flags,
+               AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) {
+               ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel immovable.\n",
+                       ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid);
+               return -1;
+       }
+
+       if (swap) {
+               struct ast_bridge_channel *bridge_channel_swap;
+
+               bridge_channel_swap = find_bridge_channel(dst_bridge, swap);
+               if (!bridge_channel_swap) {
+                       ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s not in bridge.\n",
+                               ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid,
+                               ast_channel_name(swap));
+                       return -1;
+               }
+               if (bridge_channel_swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                       ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s leaving bridge.\n",
+                               ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid,
+                               ast_channel_name(swap));
+                       return -1;
+               }
+       }
+
+       bridge_channel->swap = swap;
+       return bridge_move_do(dst_bridge, bridge_channel, attempt_recovery);
+}
+
+int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery)
+{
+       int res;
+
+       ast_bridge_lock_both(dst_bridge, src_bridge);
+       res = bridge_move_locked(dst_bridge, src_bridge, chan, swap, attempt_recovery);
+       ast_bridge_unlock(src_bridge);
+       ast_bridge_unlock(dst_bridge);
+       return res;
+}
+
+struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_bridge *bridge = bridge_channel->bridge;
+       struct ast_bridge_channel *other = NULL;
+
+       if (bridge_channel->in_bridge && bridge->num_channels == 2) {
+               AST_LIST_TRAVERSE(&bridge->channels, other, entry) {
+                       if (other != bridge_channel) {
+                               break;
+                       }
+               }
+       }
+
+       return other;
+}
+
+/*!
+ * \internal
+ * \brief Lock the unreal channel stack for chan and prequalify it.
+ * \since 12.0.0
+ *
+ * \param chan Unreal channel writing a frame into the channel driver.
+ *
+ * \note It is assumed that chan is already locked.
+ *
+ * \retval bridge on success with bridge and bridge_channel locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_chan_stack(struct ast_channel *chan)
+{
+       struct ast_bridge *bridge;
+       struct ast_bridge_channel *bridge_channel;
+
+       if (!AST_LIST_EMPTY(ast_channel_readq(chan))) {
+               return NULL;
+       }
+       bridge_channel = ast_channel_internal_bridge_channel(chan);
+       if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+               return NULL;
+       }
+       bridge = bridge_channel->bridge;
+       if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_SIMPLE
+               || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+               || ast_bridge_trylock(bridge)) {
+               ast_bridge_channel_unlock(bridge_channel);
+               return NULL;
+       }
+       if (bridge->inhibit_merge
+               || bridge->dissolved
+               || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+               || !bridge_channel->in_bridge
+               || !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+               ast_bridge_unlock(bridge);
+               ast_bridge_channel_unlock(bridge_channel);
+               return NULL;
+       }
+       return bridge;
+}
+
+/*!
+ * \internal
+ * \brief Lock the unreal channel stack for peer and prequalify it.
+ * \since 12.0.0
+ *
+ * \param peer Other unreal channel in the pair.
+ *
+ * \retval bridge on success with bridge, bridge_channel, and peer locked.
+ * \retval NULL if cannot do optimization now.
+ */
+static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer)
+{
+       struct ast_bridge *bridge;
+       struct ast_bridge_channel *bridge_channel;
+
+       if (ast_channel_trylock(peer)) {
+               return NULL;
+       }
+       if (!AST_LIST_EMPTY(ast_channel_readq(peer))) {
+               ast_channel_unlock(peer);
+               return NULL;
+       }
+       bridge_channel = ast_channel_internal_bridge_channel(peer);
+       if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) {
+               ast_channel_unlock(peer);
+               return NULL;
+       }
+       bridge = bridge_channel->bridge;
+       if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_IDLE
+               || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT
+               || ast_bridge_trylock(bridge)) {
+               ast_bridge_channel_unlock(bridge_channel);
+               ast_channel_unlock(peer);
+               return NULL;
+       }
+       if (bridge->inhibit_merge
+               || bridge->dissolved
+               || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)
+               || !bridge_channel->in_bridge
+               || !AST_LIST_EMPTY(&bridge_channel->wr_queue)) {
+               ast_bridge_unlock(bridge);
+               ast_bridge_channel_unlock(bridge_channel);
+               ast_channel_unlock(peer);
+               return NULL;
+       }
+       return bridge;
+}
+
+/*!
+ * \internal
+ * \brief Check and attempt to swap optimize out the unreal channels.
+ * \since 12.0.0
+ *
+ * \param chan_bridge
+ * \param chan_bridge_channel
+ * \param peer_bridge
+ * \param peer_bridge_channel
+ *
+ * \retval 1 if unreal channels failed to optimize out.
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval -1 if unreal channels were optimized out.
+ */
+static int check_swap_optimize_out(struct ast_bridge *chan_bridge,
+       struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
+       struct ast_bridge_channel *peer_bridge_channel)
+{
+       struct ast_bridge *dst_bridge = NULL;
+       struct ast_bridge_channel *dst_bridge_channel = NULL;
+       struct ast_bridge_channel *src_bridge_channel = NULL;
+       int peer_priority;
+       int chan_priority;
+       int res = 0;
+
+       if (!ast_test_flag(&chan_bridge->feature_flags,
+                       AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+               && !ast_test_flag(&peer_bridge->feature_flags,
+                       AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)) {
+               /*
+                * Can swap either way.  Swap to the higher priority merge
+                * bridge.
+                */
+               chan_priority = chan_bridge->v_table->get_merge_priority(chan_bridge);
+               peer_priority = peer_bridge->v_table->get_merge_priority(peer_bridge);
+               if (chan_bridge->num_channels == 2
+                       && chan_priority <= peer_priority) {
+                       dst_bridge = peer_bridge;
+                       dst_bridge_channel = peer_bridge_channel;
+                       src_bridge_channel = chan_bridge_channel;
+               } else if (peer_bridge->num_channels == 2
+                       && peer_priority <= chan_priority) {
+                       dst_bridge = chan_bridge;
+                       dst_bridge_channel = chan_bridge_channel;
+                       src_bridge_channel = peer_bridge_channel;
+               }
+       } else if (chan_bridge->num_channels == 2
+               && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+               && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) {
+               /* Can swap optimize only one way. */
+               dst_bridge = peer_bridge;
+               dst_bridge_channel = peer_bridge_channel;
+               src_bridge_channel = chan_bridge_channel;
+       } else if (peer_bridge->num_channels == 2
+               && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)
+               && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) {
+               /* Can swap optimize only one way. */
+               dst_bridge = chan_bridge;
+               dst_bridge_channel = chan_bridge_channel;
+               src_bridge_channel = peer_bridge_channel;
+       }
+       if (dst_bridge) {
+               struct ast_bridge_channel *other;
+
+               res = 1;
+               other = ast_bridge_channel_peer(src_bridge_channel);
+               if (other && other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
+                       ast_debug(1, "Move-swap optimizing %s <-- %s.\n",
+                               ast_channel_name(dst_bridge_channel->chan),
+                               ast_channel_name(other->chan));
+
+                       other->swap = dst_bridge_channel->chan;
+                       if (!bridge_move_do(dst_bridge, other, 1)) {
+                               ast_bridge_change_state(src_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+                               res = -1;
+                       }
+               }
+       }
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Check and attempt to merge optimize out the unreal channels.
+ * \since 12.0.0
+ *
+ * \param chan_bridge
+ * \param chan_bridge_channel
+ * \param peer_bridge
+ * \param peer_bridge_channel
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval -1 if unreal channels were optimized out.
+ */
+static int check_merge_optimize_out(struct ast_bridge *chan_bridge,
+       struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
+       struct ast_bridge_channel *peer_bridge_channel)
+{
+       struct merge_direction merge;
+       int res = 0;
+
+       merge = bridge_merge_determine_direction(chan_bridge, peer_bridge);
+       if (!merge.dest) {
+               return res;
+       }
+       if (merge.src->num_channels < 2) {
+               ast_debug(4, "Can't optimize %s -- %s out, not enough channels in bridge %s.\n",
+                       ast_channel_name(chan_bridge_channel->chan),
+                       ast_channel_name(peer_bridge_channel->chan),
+                       merge.src->uniqueid);
+       } else if ((2 + 2) < merge.dest->num_channels + merge.src->num_channels
+               && !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
+               && (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART)
+                       || !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) {
+               ast_debug(4, "Can't optimize %s -- %s out, multimix is needed and it cannot be acquired.\n",
+                       ast_channel_name(chan_bridge_channel->chan),
+                       ast_channel_name(peer_bridge_channel->chan));
+       } else {
+               struct ast_bridge_channel *kick_me[] = {
+                       chan_bridge_channel,
+                       peer_bridge_channel,
+                       };
+
+               ast_debug(1, "Merge optimizing %s -- %s out.\n",
+                       ast_channel_name(chan_bridge_channel->chan),
+                       ast_channel_name(peer_bridge_channel->chan));
+
+               bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me));
+               res = -1;
+       }
+
+       return res;
+}
+
+int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer)
+{
+       struct ast_bridge *chan_bridge;
+       struct ast_bridge *peer_bridge;
+       struct ast_bridge_channel *chan_bridge_channel;
+       struct ast_bridge_channel *peer_bridge_channel;
+       int res = 0;
+
+       chan_bridge = optimize_lock_chan_stack(chan);
+       if (!chan_bridge) {
+               return res;
+       }
+       chan_bridge_channel = ast_channel_internal_bridge_channel(chan);
+
+       peer_bridge = optimize_lock_peer_stack(peer);
+       if (peer_bridge) {
+               peer_bridge_channel = ast_channel_internal_bridge_channel(peer);
+
+               res = check_swap_optimize_out(chan_bridge, chan_bridge_channel,
+                       peer_bridge, peer_bridge_channel);
+               if (!res) {
+                       res = check_merge_optimize_out(chan_bridge, chan_bridge_channel,
+                               peer_bridge, peer_bridge_channel);
+               } else if (0 < res) {
+                       res = 0;
+               }
+
+               /* Release peer locks. */
+               ast_bridge_unlock(peer_bridge);
+               ast_bridge_channel_unlock(peer_bridge_channel);
+               ast_channel_unlock(peer);
+       }
+
+       /* Release chan locks. */
+       ast_bridge_unlock(chan_bridge);
+       ast_bridge_channel_unlock(chan_bridge_channel);
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Adjust the bridge merge inhibit request count.
+ * \since 12.0.0
+ *
+ * \param bridge What to operate on.
+ * \param request Inhibit request increment.
+ *     (Positive to add requests.  Negative to remove requests.)
+ *
+ * \note This function assumes bridge is locked.
+ *
+ * \return Nothing
+ */
+static void bridge_merge_inhibit_nolock(struct ast_bridge *bridge, int request)
+{
+       int new_request;
+
+       new_request = bridge->inhibit_merge + request;
+       ast_assert(0 <= new_request);
+       bridge->inhibit_merge = new_request;
+}
+
+void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request)
+{
+       ast_bridge_lock(bridge);
+       bridge_merge_inhibit_nolock(bridge, request);
+       ast_bridge_unlock(bridge);
+}
+
+struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request)
+{
+       struct ast_bridge *bridge;
+
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       bridge = bridge_channel->bridge;
+       ao2_ref(bridge, +1);
+       bridge_merge_inhibit_nolock(bridge, request);
+       ast_bridge_unlock(bridge);
+       return bridge;
+}
+
+int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       struct ast_bridge_channel *bridge_channel;
+/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
+/* BUGBUG suspend/unsuspend needs to be rethought. The caller must block until it has successfully suspended the channel for temporary control. */
+/* BUGBUG external suspend/unsuspend needs to be eliminated. The channel may be playing a file at the time and stealing it then is not good. */
+
+       ast_bridge_lock(bridge);
+
+       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+               ast_bridge_unlock(bridge);
+               return -1;
+       }
+
+       bridge_channel_suspend_nolock(bridge_channel);
+
+       ast_bridge_unlock(bridge);
+
+       return 0;
+}
+
+int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       struct ast_bridge_channel *bridge_channel;
+/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
+
+       ast_bridge_lock(bridge);
+
+       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+               ast_bridge_unlock(bridge);
+               return -1;
+       }
+
+       bridge_channel_unsuspend_nolock(bridge_channel);
+
+       ast_bridge_unlock(bridge);
+
+       return 0;
+}
+
+void ast_bridge_technology_suspend(struct ast_bridge_technology *technology)
+{
+       technology->suspended = 1;
+}
+
+void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology)
+{
+/* BUGBUG unsuspending a bridge technology probably needs to prod all existing bridges to see if they should start using it. */
+       technology->suspended = 0;
+}
+
+int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_hook_callback callback, const char *dtmf)
+{
+       if (ARRAY_LEN(builtin_features_handlers) <= feature
+               || builtin_features_handlers[feature]) {
+               return -1;
+       }
+
+       if (!ast_strlen_zero(dtmf)) {
+               ast_copy_string(builtin_features_dtmf[feature], dtmf, sizeof(builtin_features_dtmf[feature]));
+       }
+
+       builtin_features_handlers[feature] = callback;
+
+       return 0;
+}
+
+int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
+{
+       if (ARRAY_LEN(builtin_features_handlers) <= feature
+               || !builtin_features_handlers[feature]) {
+               return -1;
+       }
+
+       builtin_features_handlers[feature] = NULL;
+
+       return 0;
+}
+
+int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback)
+{
+       if (ARRAY_LEN(builtin_interval_handlers) <= interval
+               || builtin_interval_handlers[interval]) {
+               return -1;
+       }
+
+       builtin_interval_handlers[interval] = callback;
+
+       return 0;
+}
+
+int ast_bridge_interval_unregister(enum ast_bridge_builtin_interval interval)
+{
+       if (ARRAY_LEN(builtin_interval_handlers) <= interval
+               || !builtin_interval_handlers[interval]) {
+               return -1;
+       }
+
+       builtin_interval_handlers[interval] = NULL;
+
+       return 0;
+
+}
+
+/*!
+ * \internal
+ * \brief Bridge hook destructor.
+ * \since 12.0.0
+ *
+ * \param vhook Object to destroy.
+ *
+ * \return Nothing
+ */
+static void bridge_hook_destroy(void *vhook)
+{
+       struct ast_bridge_hook *hook = vhook;
+
+       if (hook->destructor) {
+               hook->destructor(hook->hook_pvt);
+       }
+}
+
+/*!
+ * \internal
+ * \brief Allocate and setup a generic bridge hook.
+ * \since 12.0.0
+ *
+ * \param size How big an object to allocate.
+ * \param callback Function to execute upon activation
+ * \param hook_pvt Unique data
+ * \param destructor Optional destructor callback for hook_pvt data
+ * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge.
+ *
+ * \retval hook on success.
+ * \retval NULL on error.
+ */
+static struct ast_bridge_hook *bridge_hook_generic(size_t size,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       struct ast_bridge_hook *hook;
+
+       /* Allocate new hook and setup it's basic variables */
+       hook = ao2_alloc_options(size, bridge_hook_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (hook) {
+               hook->callback = callback;
+               hook->destructor = destructor;
+               hook->hook_pvt = hook_pvt;
+               hook->remove_on_pull = remove_on_pull;
+       }
+
+       return hook;
+}
+
+int ast_bridge_dtmf_hook(struct ast_bridge_features *features,
+       const char *dtmf,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       struct ast_bridge_hook *hook;
+       int res;
+
+       /* Allocate new hook and setup it's various variables */
+       hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+               remove_on_pull);
+       if (!hook) {
+               return -1;
+       }
+       ast_copy_string(hook->parms.dtmf.code, dtmf, sizeof(hook->parms.dtmf.code));
+
+       /* Once done we put it in the container. */
+       res = ao2_link(features->dtmf_hooks, hook) ? 0 : -1;
+       ao2_ref(hook, -1);
+
+       return res;
+}
+
+int ast_bridge_hangup_hook(struct ast_bridge_features *features,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       struct ast_bridge_hook *hook;
+       int res;
+
+       /* Allocate new hook and setup it's various variables */
+       hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+               remove_on_pull);
+       if (!hook) {
+               return -1;
+       }
+
+       /* Once done we put it in the container. */
+       res = ao2_link(features->hangup_hooks, hook) ? 0 : -1;
+       ao2_ref(hook, -1);
+
+       return res;
+}
+
+int ast_bridge_join_hook(struct ast_bridge_features *features,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       struct ast_bridge_hook *hook;
+       int res;
+
+       /* Allocate new hook and setup it's various variables */
+       hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+               remove_on_pull);
+       if (!hook) {
+               return -1;
+       }
+
+       /* Once done we put it in the container. */
+       res = ao2_link(features->join_hooks, hook) ? 0 : -1;
+       ao2_ref(hook, -1);
+
+       return res;
+}
+
+int ast_bridge_leave_hook(struct ast_bridge_features *features,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       struct ast_bridge_hook *hook;
+       int res;
+
+       /* Allocate new hook and setup it's various variables */
+       hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+               remove_on_pull);
+       if (!hook) {
+               return -1;
+       }
+
+       /* Once done we put it in the container. */
+       res = ao2_link(features->leave_hooks, hook) ? 0 : -1;
+       ao2_ref(hook, -1);
+
+       return res;
+}
+
+void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
+       ast_bridge_talking_indicate_callback talker_cb,
+       ast_bridge_talking_indicate_destructor talker_destructor,
+       void *pvt_data)
+{
+       features->talker_cb = talker_cb;
+       features->talker_destructor_cb = talker_destructor;
+       features->talker_pvt_data = pvt_data;
+}
+
+int ast_bridge_interval_hook(struct ast_bridge_features *features,
+       unsigned int interval,
+       ast_bridge_hook_callback callback,
+       void *hook_pvt,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       struct ast_bridge_hook *hook;
+       int res;
+
+       if (!features ||!interval || !callback) {
+               return -1;
+       }
+
+       if (!features->interval_timer) {
+               if (!(features->interval_timer = ast_timer_open())) {
+                       ast_log(LOG_ERROR, "Failed to open a timer when adding a timed bridging feature.\n");
+                       return -1;
+               }
+               ast_timer_set_rate(features->interval_timer, BRIDGE_FEATURES_INTERVAL_RATE);
+       }
+
+       /* Allocate new hook and setup it's various variables */
+       hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor,
+               remove_on_pull);
+       if (!hook) {
+               return -1;
+       }
+       hook->parms.timer.interval = interval;
+       hook->parms.timer.trip_time = ast_tvadd(ast_tvnow(), ast_samp2tv(hook->parms.timer.interval, 1000));
+       hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &features->interval_sequence, +1);
+
+       ast_debug(1, "Putting interval hook %p with interval %u in the heap on features %p\n",
+               hook, hook->parms.timer.interval, features);
+       ast_heap_wrlock(features->interval_hooks);
+       res = ast_heap_push(features->interval_hooks, hook);
+       if (res) {
+               /* Could not push the hook onto the heap. */
+               ao2_ref(hook, -1);
+       }
+       ast_heap_unlock(features->interval_hooks);
+
+       return res ? -1 : 0;
+}
+
+int ast_bridge_features_enable(struct ast_bridge_features *features,
+       enum ast_bridge_builtin_feature feature,
+       const char *dtmf,
+       void *config,
+       ast_bridge_hook_pvt_destructor destructor,
+       int remove_on_pull)
+{
+       if (ARRAY_LEN(builtin_features_handlers) <= feature
+               || !builtin_features_handlers[feature]) {
+               return -1;
+       }
+
+       /* If no alternate DTMF stream was provided use the default one */
+       if (ast_strlen_zero(dtmf)) {
+               dtmf = builtin_features_dtmf[feature];
+               /* If no DTMF is still available (ie: it has been disabled) then error out now */
+               if (ast_strlen_zero(dtmf)) {
+                       ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n",
+                               feature, features);
+                       return -1;
+               }
+       }
+
+       /*
+        * The rest is basically pretty easy.  We create another hook
+        * using the built in feature's DTMF callback.  Easy as pie.
+        */
+       return ast_bridge_dtmf_hook(features, dtmf, builtin_features_handlers[feature],
+               config, destructor, remove_on_pull);
+}
+
+int ast_bridge_features_limits_construct(struct ast_bridge_features_limits *limits)
+{
+       memset(limits, 0, sizeof(*limits));
+
+       if (ast_string_field_init(limits, 256)) {
+               ast_free(limits);
+               return -1;
+       }
+
+       return 0;
+}
+
+void ast_bridge_features_limits_destroy(struct ast_bridge_features_limits *limits)
+{
+       ast_string_field_free_memory(limits);
+}
+
+int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull)
+{
+       if (builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS]) {
+               ast_bridge_builtin_set_limits_fn bridge_features_set_limits_callback;
+
+               bridge_features_set_limits_callback = builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS];
+               return bridge_features_set_limits_callback(features, limits, remove_on_pull);
+       }
+
+       ast_log(LOG_ERROR, "Attempted to set limits without an AST_BRIDGE_BUILTIN_INTERVAL_LIMITS callback registered.\n");
+       return -1;
+}
+
+void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag)
+{
+       ast_set_flag(&features->feature_flags, flag);
+       features->usable = 1;
+}
+
+/*!
+ * \internal
+ * \brief ao2 object match remove_on_pull hooks.
+ * \since 12.0.0
+ *
+ * \param obj Feature hook object.
+ * \param arg Not used
+ * \param flags Not used
+ *
+ * \retval CMP_MATCH if hook has remove_on_pull set.
+ * \retval 0 if not match.
+ */
+static int hook_remove_on_pull_match(void *obj, void *arg, int flags)
+{
+       struct ast_bridge_hook *hook = obj;
+
+       if (hook->remove_on_pull) {
+               return CMP_MATCH;
+       } else {
+               return 0;
+       }
+}
+
+/*!
+ * \internal
+ * \brief Remove all remove_on_pull hooks in the container.
+ * \since 12.0.0
+ *
+ * \param hooks Hooks container to work on.
+ *
+ * \return Nothing
+ */
+static void hooks_remove_on_pull_container(struct ao2_container *hooks)
+{
+       ao2_callback(hooks, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+               hook_remove_on_pull_match, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Remove all remove_on_pull hooks in the heap.
+ * \since 12.0.0
+ *
+ * \param hooks Hooks heap to work on.
+ *
+ * \return Nothing
+ */
+static void hooks_remove_on_pull_heap(struct ast_heap *hooks)
+{
+       struct ast_bridge_hook *hook;
+       int changed;
+
+       ast_heap_wrlock(hooks);
+       do {
+               int idx;
+
+               changed = 0;
+               for (idx = ast_heap_size(hooks); idx; --idx) {
+                       hook = ast_heap_peek(hooks, idx);
+                       if (hook->remove_on_pull) {
+                               ast_heap_remove(hooks, hook);
+                               ao2_ref(hook, -1);
+                               changed = 1;
+                       }
+               }
+       } while (changed);
+       ast_heap_unlock(hooks);
+}
+
+/*!
+ * \internal
+ * \brief Remove marked bridge channel feature hooks.
+ * \since 12.0.0
+ *
+ * \param features Bridge featues structure
+ *
+ * \return Nothing
+ */
+static void bridge_features_remove_on_pull(struct ast_bridge_features *features)
+{
+       hooks_remove_on_pull_container(features->dtmf_hooks);
+       hooks_remove_on_pull_container(features->hangup_hooks);
+       hooks_remove_on_pull_container(features->join_hooks);
+       hooks_remove_on_pull_container(features->leave_hooks);
+       hooks_remove_on_pull_heap(features->interval_hooks);
+}
+
+static int interval_hook_time_cmp(void *a, void *b)
+{
+       struct ast_bridge_hook *hook_a = a;
+       struct ast_bridge_hook *hook_b = b;
+       int cmp;
+
+       cmp = ast_tvcmp(hook_b->parms.timer.trip_time, hook_a->parms.timer.trip_time);
+       if (cmp) {
+               return cmp;
+       }
+
+       cmp = hook_b->parms.timer.seqno - hook_a->parms.timer.seqno;
+       return cmp;
+}
+
+/*!
+ * \internal
+ * \brief DTMF hook container sort comparison function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_dtmf_hook_sort(const void *obj_left, const void *obj_right, int flags)
+{
+       const struct ast_bridge_hook *hook_left = obj_left;
+       const struct ast_bridge_hook *hook_right = obj_right;
+       const char *right_key = obj_right;
+       int cmp;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               right_key = hook_right->parms.dtmf.code;
+               /* Fall through */
+       case OBJ_KEY:
+               cmp = strcasecmp(hook_left->parms.dtmf.code, right_key);
+               break;
+       case OBJ_PARTIAL_KEY:
+               cmp = strncasecmp(hook_left->parms.dtmf.code, right_key, strlen(right_key));
+               break;
+       }
+       return cmp;
+}
+
+/* BUGBUG make ast_bridge_features_init() static when make ast_bridge_join() requires features to be allocated. */
+int ast_bridge_features_init(struct ast_bridge_features *features)
+{
+       /* Zero out the structure */
+       memset(features, 0, sizeof(*features));
+
+       /* Initialize the DTMF hooks container */
+       features->dtmf_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX,
+               AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_dtmf_hook_sort, NULL);
+       if (!features->dtmf_hooks) {
+               return -1;
+       }
+
+       /* Initialize the hangup hooks container */
+       features->hangup_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+               NULL);
+       if (!features->hangup_hooks) {
+               return -1;
+       }
+
+       /* Initialize the join hooks container */
+       features->join_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+               NULL);
+       if (!features->join_hooks) {
+               return -1;
+       }
+
+       /* Initialize the leave hooks container */
+       features->leave_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL,
+               NULL);
+       if (!features->leave_hooks) {
+               return -1;
+       }
+
+       /* Initialize the interval hooks heap */
+       features->interval_hooks = ast_heap_create(8, interval_hook_time_cmp,
+               offsetof(struct ast_bridge_hook, parms.timer.heap_index));
+       if (!features->interval_hooks) {
+               return -1;
+       }
+
+       return 0;
+}
+
+/* BUGBUG make ast_bridge_features_cleanup() static when make ast_bridge_join() requires features to be allocated. */
+void ast_bridge_features_cleanup(struct ast_bridge_features *features)
+{
+       struct ast_bridge_hook *hook;
+
+       /* Destroy the interval hooks heap. */
+       if (features->interval_hooks) {
+               while ((hook = ast_heap_pop(features->interval_hooks))) {
+                       ao2_ref(hook, -1);
+               }
+               features->interval_hooks = ast_heap_destroy(features->interval_hooks);
+       }
+
+       if (features->interval_timer) {
+               ast_timer_close(features->interval_timer);
+               features->interval_timer = NULL;
+       }
+
+       /* If the features contains a limits pvt, destroy that as well. */
+       if (features->limits) {
+               ast_bridge_features_limits_destroy(features->limits);
+               ast_free(features->limits);
+               features->limits = NULL;
+       }
+
+       if (features->talker_destructor_cb && features->talker_pvt_data) {
+               features->talker_destructor_cb(features->talker_pvt_data);
+               features->talker_pvt_data = NULL;
+       }
+
+       /* Destroy the leave hooks container. */
+       ao2_cleanup(features->leave_hooks);
+       features->leave_hooks = NULL;
+
+       /* Destroy the join hooks container. */
+       ao2_cleanup(features->join_hooks);
+       features->join_hooks = NULL;
+
+       /* Destroy the hangup hooks container. */
+       ao2_cleanup(features->hangup_hooks);
+       features->hangup_hooks = NULL;
+
+       /* Destroy the DTMF hooks container. */
+       ao2_cleanup(features->dtmf_hooks);
+       features->dtmf_hooks = NULL;
+}
+
+void ast_bridge_features_destroy(struct ast_bridge_features *features)
+{
+       if (!features) {
+               return;
+       }
+       ast_bridge_features_cleanup(features);
+       ast_free(features);
+}
+
+struct ast_bridge_features *ast_bridge_features_new(void)
+{
+       struct ast_bridge_features *features;
+
+       features = ast_malloc(sizeof(*features));
+       if (features) {
+               if (ast_bridge_features_init(features)) {
+                       ast_bridge_features_destroy(features);
+                       features = NULL;
+               }
+       }
+
+       return features;
+}
+
+void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
+{
+       ast_bridge_lock(bridge);
+       bridge->internal_mixing_interval = mixing_interval;
+       ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
+{
+       ast_bridge_lock(bridge);
+       bridge->internal_sample_rate = sample_rate;
+       ast_bridge_unlock(bridge);
+}
+
+static void cleanup_video_mode(struct ast_bridge *bridge)
+{
+       switch (bridge->video_mode.mode) {
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+               break;
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
+                       ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
+               }
+               break;
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
+                       ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
+               }
+               if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
+                       ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
+               }
+       }
+       memset(&bridge->video_mode, 0, sizeof(bridge->video_mode));
+}
+
+void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
+{
+       ast_bridge_lock(bridge);
+       cleanup_video_mode(bridge);
+       bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
+       bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
+       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan));
+       ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
+       ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
+{
+       ast_bridge_lock(bridge);
+       cleanup_video_mode(bridge);
+       bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
+       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode);
+       ast_bridge_unlock(bridge);
+}
+
+void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
+{
+       struct ast_bridge_video_talker_src_data *data;
+       /* If the channel doesn't support video, we don't care about it */
+       if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) {
+               return;
+       }
+
+       ast_bridge_lock(bridge);
+       data = &bridge->video_mode.mode_data.talker_src_data;
+
+       if (data->chan_vsrc == chan) {
+               data->average_talking_energy = talker_energy;
+       } else if ((data->average_talking_energy < talker_energy) && is_keyframe) {
+               if (data->chan_old_vsrc) {
+                       ast_channel_unref(data->chan_old_vsrc);
+               }
+               if (data->chan_vsrc) {
+                       data->chan_old_vsrc = data->chan_vsrc;
+                       ast_indicate(data->chan_old_vsrc, AST_CONTROL_VIDUPDATE);
+               }
+               data->chan_vsrc = ast_channel_ref(chan);
+               data->average_talking_energy = talker_energy;
+               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+               ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
+       } else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
+               ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+       } else if (!data->chan_vsrc && is_keyframe) {
+               data->chan_vsrc = ast_channel_ref(chan);
+               data->average_talking_energy = talker_energy;
+               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+               ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+       } else if (!data->chan_old_vsrc && is_keyframe) {
+               data->chan_old_vsrc = ast_channel_ref(chan);
+               ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+       }
+       ast_bridge_unlock(bridge);
+}
+
+int ast_bridge_number_video_src(struct ast_bridge *bridge)
+{
+       int res = 0;
+
+       ast_bridge_lock(bridge);
+       switch (bridge->video_mode.mode) {
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+               break;
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
+                       res = 1;
+               }
+               break;
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
+                       res++;
+               }
+               if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
+                       res++;
+               }
+       }
+       ast_bridge_unlock(bridge);
+       return res;
+}
+
+int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       int res = 0;
+
+       ast_bridge_lock(bridge);
+       switch (bridge->video_mode.mode) {
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+               break;
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
+                       res = 1;
+               }
+               break;
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
+                       res = 1;
+               } else if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
+                       res = 2;
+               }
+
+       }
+       ast_bridge_unlock(bridge);
+       return res;
+}
+
+void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       ast_bridge_lock(bridge);
+       switch (bridge->video_mode.mode) {
+       case AST_BRIDGE_VIDEO_MODE_NONE:
+               break;
+       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
+                       if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
+                               ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
+                       }
+                       bridge->video_mode.mode_data.single_src_data.chan_vsrc = NULL;
+               }
+               break;
+       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
+                       if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
+                               ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
+                       }
+                       bridge->video_mode.mode_data.talker_src_data.chan_vsrc = NULL;
+                       bridge->video_mode.mode_data.talker_src_data.average_talking_energy = 0;
+               }
+               if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
+                       if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
+                               ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
+                       }
+                       bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
+               }
        }
+       ast_bridge_unlock(bridge);
+}
 
-       if (bridge_channel->bridge) {
-               ao2_ref(bridge_channel->bridge, -1);
-               bridge_channel->bridge = NULL;
+static int channel_hash(const void *obj, int flags)
+{
+       const struct ast_channel *chan = obj;
+       const char *name = obj;
+       int hash;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               name = ast_channel_name(chan);
+               /* Fall through */
+       case OBJ_KEY:
+               hash = ast_str_hash(name);
+               break;
+       case OBJ_PARTIAL_KEY:
+               /* Should never happen in hash callback. */
+               ast_assert(0);
+               hash = 0;
+               break;
        }
-       /* Destroy elements of the bridge channel structure and the bridge channel structure itself */
-       ast_cond_destroy(&bridge_channel->cond);
+       return hash;
 }
 
-static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
+static int channel_cmp(void *obj, void *arg, int flags)
 {
-       struct ast_bridge_channel *bridge_channel;
+       const struct ast_channel *left = obj;
+       const struct ast_channel *right = arg;
+       const char *right_name = arg;
+       int cmp;
 
-       bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy);
-       if (!bridge_channel) {
-               return NULL;
-       }
-       ast_cond_init(&bridge_channel->cond, NULL);
-       if (bridge) {
-               bridge_channel->bridge = bridge;
-               ao2_ref(bridge_channel->bridge, +1);
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               right_name = ast_channel_name(right);
+               /* Fall through */
+       case OBJ_KEY:
+               cmp = strcmp(ast_channel_name(left), right_name);
+               break;
+       case OBJ_PARTIAL_KEY:
+               cmp = strncmp(ast_channel_name(left), right_name, strlen(right_name));
+               break;
        }
-       return bridge_channel;
+       return cmp ? 0 : CMP_MATCH;
 }
 
-enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
-       struct ast_channel *chan,
-       struct ast_channel *swap,
-       struct ast_bridge_features *features,
-       struct ast_bridge_tech_optimizations *tech_args)
+struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge)
 {
-       struct ast_bridge_channel *bridge_channel;
-       enum ast_bridge_channel_state state;
+       struct ao2_container *channels;
+       struct ast_bridge_channel *iter;
 
-       bridge_channel = bridge_channel_alloc(bridge);
-       if (!bridge_channel) {
-               return AST_BRIDGE_CHANNEL_STATE_HANGUP;
+       channels = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK,
+               13, channel_hash, channel_cmp);
+       if (!channels) {
+               return NULL;
        }
-       if (tech_args) {
-               bridge_channel->tech_args = *tech_args;
+
+       AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+               ao2_link(channels, iter->chan);
        }
 
-       /* Initialize various other elements of the bridge channel structure that we can't do above */
-       bridge_channel->chan = chan;
-       bridge_channel->swap = swap;
-       bridge_channel->features = features;
+       return channels;
+}
 
-       state = bridge_channel_join(bridge_channel);
+struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge)
+{
+       struct ao2_container *channels;
 
-       /* Cleanup all the data in the bridge channel after it leaves the bridge. */
-       bridge_channel->chan = NULL;
-       bridge_channel->swap = NULL;
-       bridge_channel->features = NULL;
+       ast_bridge_lock(bridge);
+       channels = ast_bridge_peers_nolock(bridge);
+       ast_bridge_unlock(bridge);
 
-       ao2_ref(bridge_channel, -1);
-       return state;
+       return channels;
 }
 
-/*! \brief Thread responsible for imparted bridged channels */
-static void *bridge_channel_thread(void *data)
+struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan)
 {
-       struct ast_bridge_channel *bridge_channel = data;
-       enum ast_bridge_channel_state state;
-
-       if (bridge_channel->callid) {
-               ast_callid_threadassoc_add(bridge_channel->callid);
+       struct ast_channel *peer = NULL;
+       struct ast_bridge_channel *iter;
+
+       /* Asking for the peer channel only makes sense on a two-party bridge. */
+       if (bridge->num_channels == 2
+               && bridge->technology->capabilities
+                       & (AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX)) {
+               int in_bridge = 0;
+
+               AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+                       if (iter->chan != chan) {
+                               peer = iter->chan;
+                       } else {
+                               in_bridge = 1;
+                       }
+               }
+               if (in_bridge && peer) {
+                       ast_channel_ref(peer);
+               } else {
+                       peer = NULL;
+               }
        }
 
-       state = bridge_channel_join(bridge_channel);
-
-       /* If no other thread is going to take the channel then hang it up, or else we would have to service it until something else came along */
-       if (bridge_channel->allow_impart_hangup && (state == AST_BRIDGE_CHANNEL_STATE_END || state == AST_BRIDGE_CHANNEL_STATE_HANGUP)) {
-               ast_hangup(bridge_channel->chan);
-       }
+       return peer;
+}
 
-       /* cleanup */
-       ao2_lock(bridge_channel);
-       bridge_channel->chan = NULL;
-       bridge_channel->swap = NULL;
-       bridge_channel->features = NULL;
-       ao2_unlock(bridge_channel);
+struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       struct ast_channel *peer;
 
-       ao2_ref(bridge_channel, -1);
+       ast_bridge_lock(bridge);
+       peer = ast_bridge_peer_nolock(bridge, chan);
+       ast_bridge_unlock(bridge);
 
-       return NULL;
+       return peer;
 }
 
-int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup)
+/*!
+ * \internal
+ * \brief Transfer an entire bridge to a specific destination.
+ *
+ * This creates a local channel to dial out and swaps the called local channel
+ * with the transferer channel. By doing so, all participants in the bridge are
+ * connected to the specified destination.
+ *
+ * While this means of transferring would work for both two-party and multi-party
+ * bridges, this method is only used for multi-party bridges since this method would
+ * be less efficient for two-party bridges.
+ *
+ * \param transferer The channel performing a transfer
+ * \param bridge The bridge where the transfer is being performed
+ * \param exten The destination extension for the blind transfer
+ * \param context The destination context for the blind transfer
+ * \param hook Framehook to attach to local channel
+ * \return The success or failure of the operation
+ */
+static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transferer,
+               struct ast_bridge *bridge, const char *exten, const char *context,
+               transfer_channel_cb new_channel_cb, void *user_data)
 {
-       struct ast_bridge_channel *bridge_channel;
+       struct ast_channel *local;
+       char chan_name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
+       int cause;
+
+       snprintf(chan_name, sizeof(chan_name), "%s@%s", exten, context);
+       local = ast_request("Local", ast_channel_nativeformats(transferer), transferer,
+                       chan_name, &cause);
+       if (!local) {
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
 
-       /* Try to allocate a structure for the bridge channel */
-       bridge_channel = bridge_channel_alloc(bridge);
-       if (!bridge_channel) {
-               return -1;
+       if (new_channel_cb) {
+               new_channel_cb(local, user_data);
        }
 
-       /* Setup various parameters */
-       bridge_channel->chan = chan;
-       bridge_channel->swap = swap;
-       bridge_channel->features = features;
-       bridge_channel->allow_impart_hangup = allow_hangup;
-       bridge_channel->callid = ast_read_threadstorage_callid();
+       if (ast_call(local, chan_name, 0)) {
+               ast_hangup(local);
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
+       if (ast_bridge_impart(bridge, local, transferer, NULL, 1)) {
+               ast_hangup(local);
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
+       return AST_BRIDGE_TRANSFER_SUCCESS;
+}
 
-       /* Actually create the thread that will handle the channel */
-       if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_channel)) {
-               ao2_ref(bridge_channel, -1);
-               return -1;
+/*!
+ * \internal
+ * \brief Get the transferee channel
+ *
+ * This is only applicable to cases where a transfer is occurring on a
+ * two-party bridge. The channels container passed in is expected to only
+ * contain two channels, the transferer and the transferee. The transferer
+ * channel is passed in as a parameter to ensure we don't return it as
+ * the transferee channel.
+ *
+ * \param channels A two-channel container containing the transferer and transferee
+ * \param transferer The party that is transfering the call
+ * \return The party that is being transferred
+ */
+static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
+{
+       struct ao2_iterator channel_iter;
+       struct ast_channel *transferee;
+
+       for (channel_iter = ao2_iterator_init(channels, 0);
+                       (transferee = ao2_iterator_next(&channel_iter));
+                       ao2_cleanup(transferee)) {
+               if (transferee != transferer) {
+                       break;
+               }
        }
 
-       return 0;
+       ao2_iterator_destroy(&channel_iter);
+       return transferee;
 }
 
-int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan)
+/*!
+ * \internal
+ * \brief Queue a blind transfer action on a transferee bridge channel
+ *
+ * This is only relevant for when a blind transfer is performed on a two-party
+ * bridge. The transferee's bridge channel will have a blind transfer bridge
+ * action queued onto it, resulting in the party being redirected to a new
+ * destination
+ *
+ * \param transferee The channel to have the action queued on
+ * \param exten The destination extension for the transferee
+ * \param context The destination context for the transferee
+ * \param hook Frame hook to attach to transferee
+ * \retval 0 Successfully queued the action
+ * \retval non-zero Failed to queue the action
+ */
+static int bridge_channel_queue_blind_transfer(struct ast_channel *transferee,
+               const char *exten, const char *context,
+               transfer_channel_cb new_channel_cb, void *user_data)
 {
-       struct ast_bridge_channel *bridge_channel;
-       pthread_t thread;
+       RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup);
+       struct blind_transfer_data blind_data;
 
-       ao2_lock(bridge);
+       ast_channel_lock(transferee);
+       transferee_bridge_channel = ast_channel_get_bridge_channel(transferee);
+       ast_channel_unlock(transferee);
 
-       /* Try to find the channel that we want to depart */
-       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-               ao2_unlock(bridge);
+       if (!transferee_bridge_channel) {
                return -1;
        }
 
-       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
-       thread = bridge_channel->thread;
+       if (new_channel_cb) {
+               new_channel_cb(transferee, user_data);
+       }
 
-       ao2_unlock(bridge);
+       ast_copy_string(blind_data.exten, exten, sizeof(blind_data.exten));
+       ast_copy_string(blind_data.context, context, sizeof(blind_data.context));
 
-       pthread_join(thread, NULL);
+/* BUGBUG Why doesn't this function return success/failure? */
+       ast_bridge_channel_queue_action_data(transferee_bridge_channel,
+                       AST_BRIDGE_ACTION_BLIND_TRANSFER, &blind_data, sizeof(blind_data));
 
        return 0;
 }
 
-int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan)
+enum try_parking_result {
+       PARKING_SUCCESS,
+       PARKING_FAILURE,
+       PARKING_NOT_APPLICABLE,
+};
+
+static enum try_parking_result try_parking(struct ast_bridge *bridge, struct ast_channel *transferer,
+               const char *exten, const char *context)
 {
-       struct ast_bridge_channel *bridge_channel;
+       /* BUGBUG The following is all commented out because the functionality is not
+        * present yet. The functions referenced here are available at team/jrose/bridge_projects.
+        * Once the code there has been merged into team/group/bridge_construction,
+        * this can be uncommented and tested
+        */
 
-       ao2_lock(bridge);
+#if 0
+       RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup);
+       struct ast_exten *parking_exten;
 
-       /* Try to find the channel that we want to remove */
-       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-               ao2_unlock(bridge);
-               return -1;
-       }
+       ast_channel_lock(transferer);
+       transfer_bridge_channel = ast_channel_get_bridge_channel(transferer);
+       ast_channel_unlock(transferer);
 
-       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+       if (!transfer_bridge_channel) {
+               return PARKING_FAILURE;
+       }
 
-       ao2_unlock(bridge);
+       parking_exten = ast_get_parking_exten(exten, NULL, context);
+       if (parking_exten) {
+               return ast_park_blind_xfer(bridge, transferer, parking_exten) == 0 ?
+                       PARKING_SUCCESS : PARKING_FAILURE;
+       }
+#endif
 
-       return 0;
+       return PARKING_NOT_APPLICABLE;
 }
 
-int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1)
+/*!
+ * \internal
+ * \brief Set the BLINDTRANSFER variable as appropriate on channels involved in the transfer
+ *
+ * The transferer channel will have its BLINDTRANSFER variable set the same as its BRIDGEPEER
+ * variable. This will account for all channels that it is bridged to. The other channels
+ * involved in the transfer will have their BLINDTRANSFER variable set to the transferer
+ * channel's name.
+ *
+ * \param transferer The channel performing the blind transfer
+ * \param channels The channels belonging to the bridge
+ */
+static void set_blind_transfer_variables(struct ast_channel *transferer, struct ao2_container *channels)
 {
-       struct ast_bridge_channel *bridge_channel;
+       struct ao2_iterator iter;
+       struct ast_channel *chan;
+       const char *transferer_name;
+       const char *transferer_bridgepeer;
+
+       ast_channel_lock(transferer);
+       transferer_name = ast_strdupa(ast_channel_name(transferer));
+       transferer_bridgepeer = ast_strdupa(S_OR(pbx_builtin_getvar_helper(transferer, "BRIDGEPEER"), ""));
+       ast_channel_unlock(transferer);
+
+       for (iter = ao2_iterator_init(channels, 0);
+                       (chan = ao2_iterator_next(&iter));
+                       ao2_cleanup(chan)) {
+               if (chan == transferer) {
+                       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_bridgepeer);
+               } else {
+                       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_name);
+               }
+       }
 
-       ao2_lock(bridge0);
-       ao2_lock(bridge1);
+       ao2_iterator_destroy(&iter);
+}
 
-       /* If the first bridge currently has 2 channels and is not capable of becoming a multimixing bridge we can not merge */
-       if (bridge0->num + bridge1->num > 2
-               && !(bridge0->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
-               && !ast_test_flag(&bridge0->feature_flags, AST_BRIDGE_FLAG_SMART)) {
-               ao2_unlock(bridge1);
-               ao2_unlock(bridge0);
-               ast_debug(1, "Can't merge bridge %p into bridge %p, multimix is needed and it could not be acquired.\n", bridge1, bridge0);
-               return -1;
-       }
+enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
+               const char *exten, const char *context,
+               transfer_channel_cb new_channel_cb, void *user_data)
+{
+       RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup);
+       int do_bridge_transfer;
+       int transfer_prohibited;
+       enum try_parking_result parking_result;
 
-       ast_debug(1, "Merging channels from bridge %p into bridge %p\n", bridge1, bridge0);
+       ast_channel_lock(transferer);
+       bridge = ast_channel_get_bridge(transferer);
+       ast_channel_unlock(transferer);
 
-       /* Perform smart bridge operation on bridge we are merging into so it can change bridge technology if needed */
-       if (smart_bridge_operation(bridge0, NULL, bridge0->num + bridge1->num)) {
-               ao2_unlock(bridge1);
-               ao2_unlock(bridge0);
-               ast_debug(1, "Can't merge bridge %p into bridge %p, tried to perform smart bridge operation and failed.\n", bridge1, bridge0);
-               return -1;
+       if (!bridge) {
+               return AST_BRIDGE_TRANSFER_INVALID;
        }
 
-       /* If a thread is currently executing on bridge1 tell it to stop */
-       if (bridge1->thread) {
-               ast_debug(1, "Telling bridge thread on bridge %p to stop as it is being merged into %p\n", bridge1, bridge0);
-               bridge1->thread = AST_PTHREADT_STOP;
+       parking_result = try_parking(bridge, transferer, exten, context);
+       switch (parking_result) {
+       case PARKING_SUCCESS:
+               return AST_BRIDGE_TRANSFER_SUCCESS;
+       case PARKING_FAILURE:
+               return AST_BRIDGE_TRANSFER_FAIL;
+       case PARKING_NOT_APPLICABLE:
+       default:
+               break;
        }
 
-       /* Move channels from bridge1 over to bridge0 */
-       while ((bridge_channel = AST_LIST_REMOVE_HEAD(&bridge1->channels, entry))) {
-               /* Tell the technology handling bridge1 that the bridge channel is leaving */
-               if (bridge1->technology->leave) {
-                       ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge1->technology->name, bridge_channel, bridge1);
-                       if (bridge1->technology->leave(bridge1, bridge_channel)) {
-                               ast_debug(1, "Bridge technology %s failed to allow %p to leave bridge %p\n", bridge1->technology->name, bridge_channel, bridge1);
-                       }
+       {
+               SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+               channels = ast_bridge_peers_nolock(bridge);
+               if (!channels) {
+                       return AST_BRIDGE_TRANSFER_FAIL;
                }
+               if (ao2_container_count(channels) <= 1) {
+                       return AST_BRIDGE_TRANSFER_INVALID;
+               }
+               transfer_prohibited = ast_test_flag(&bridge->feature_flags,
+                               AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+               do_bridge_transfer = ast_test_flag(&bridge->feature_flags,
+                               AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) ||
+                               ao2_container_count(channels) > 2;
+       }
 
-               /* Drop channel count and reference count on the bridge they are leaving */
-               bridge1->num--;
-               ao2_ref(bridge1, -1);
-
-               bridge_array_remove(bridge1, bridge_channel->chan);
+       if (transfer_prohibited) {
+               return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+       }
 
-               /* Now add them into the bridge they are joining, increase channel count, and bump up reference count */
-               bridge_channel->bridge = bridge0;
-               AST_LIST_INSERT_TAIL(&bridge0->channels, bridge_channel, entry);
-               bridge0->num++;
-               ao2_ref(bridge0, +1);
+       set_blind_transfer_variables(transferer, channels);
 
-               bridge_array_add(bridge0, bridge_channel->chan);
+       if (do_bridge_transfer) {
+               return blind_transfer_bridge(transferer, bridge, exten, context,
+                               new_channel_cb, user_data);
+       }
 
-               /* Make the channel compatible with the new bridge it is joining or else formats would go amuck */
-               bridge_make_compatible(bridge0, bridge_channel);
+       /* Reaching this portion means that we're dealing with a two-party bridge */
 
-               /* Tell the technology handling bridge0 that the bridge channel is joining */
-               if (bridge0->technology->join) {
-                       ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge0->technology->name, bridge_channel, bridge0);
-                       if (bridge0->technology->join(bridge0, bridge_channel)) {
-                               ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge0->technology->name, bridge_channel, bridge0);
-                       }
-               }
+       transferee = get_transferee(channels, transferer);
+       if (!transferee) {
+               return AST_BRIDGE_TRANSFER_FAIL;
+       }
 
-               /* Poke the bridge channel, this will cause it to wake up and execute the proper threading model for the new bridge it is in */
-               bridge_channel_poke(bridge_channel);
+       if (bridge_channel_queue_blind_transfer(transferee, exten, context,
+                               new_channel_cb, user_data)) {
+               return AST_BRIDGE_TRANSFER_FAIL;
        }
 
-       ast_debug(1, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0);
+       ast_bridge_remove(bridge, transferer);
+       return AST_BRIDGE_TRANSFER_SUCCESS;
+}
 
-       ao2_unlock(bridge1);
-       ao2_unlock(bridge0);
+enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
+               struct ast_channel *to_transfer_target, struct ast_framehook *hook)
+{
+       /* First, check the validity of scenario. If invalid, return AST_BRIDGE_TRANSFER_INVALID. The following are invalid:
+        *     1) atxfer of an unbridged call to an unbridged call
+        *     2) atxfer of an unbridged call to a multi-party (n > 2) bridge
+        *     3) atxfer of a multi-party (n > 2) bridge to an unbridged call
+        * Second, check that the bridge(s) involved allows transfers. If not, return AST_BRIDGE_TRANSFER_NOT_PERMITTED.
+        * Third, break into different scenarios for different bridge situations:
+        * If both channels are bridged, perform a bridge merge. Direction of the merge is TBD.
+        * If channel A is bridged, and channel B is not (e.g. transferring to IVR or blond transfer)
+        *     Some manner of masquerading is necessary. Presumably, you'd want to move channel A's bridge peer
+        *     into where channel B is. However, it may be possible to do something a bit different, where a
+        *     local channel is created and put into channel A's bridge. The local channel's ;2 channel
+        *     is then masqueraded with channel B in some way.
+        * If channel A is not bridged and channel B is, then:
+        *     This is similar to what is done in the previous scenario. Create a local channel and place it
+        *     into B's bridge. Then masquerade the ;2 leg of the local channel.
+        */
 
-       return 0;
+       /* XXX STUB */
+       return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
-int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
+/*!
+ * \internal
+ * \brief Service the bridge manager request.
+ * \since 12.0.0
+ *
+ * \param bridge requesting service.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_service(struct ast_bridge *bridge)
 {
-       struct ast_bridge_channel *bridge_channel;
+       ast_bridge_lock(bridge);
+       if (bridge->callid) {
+               ast_callid_threadassoc_change(bridge->callid);
+       }
 
-       ao2_lock(bridge);
+       /* Do any pending bridge actions. */
+       bridge_handle_actions(bridge);
+       ast_bridge_unlock(bridge);
+}
 
-       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-               ao2_unlock(bridge);
-               return -1;
-       }
+/*!
+ * \internal
+ * \brief Bridge manager service thread.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void *bridge_manager_thread(void *data)
+{
+       struct bridge_manager_controller *manager = data;
+       struct bridge_manager_request *request;
+
+       ao2_lock(manager);
+       while (!manager->stop) {
+               request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node);
+               if (!request) {
+                       ast_cond_wait(&manager->cond, ao2_object_get_lockaddr(manager));
+                       continue;
+               }
+               ao2_unlock(manager);
 
-       bridge_channel_suspend(bridge, bridge_channel);
+               /* Service the bridge. */
+               bridge_manager_service(request->bridge);
+               ao2_ref(request->bridge, -1);
+               ast_free(request);
 
-       ao2_unlock(bridge);
+               ao2_lock(manager);
+       }
+       ao2_unlock(manager);
 
-       return 0;
+       return NULL;
 }
 
-int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan)
+/*!
+ * \internal
+ * \brief Destroy the bridge manager controller.
+ * \since 12.0.0
+ *
+ * \param obj Bridge manager to destroy.
+ *
+ * \return Nothing
+ */
+static void bridge_manager_destroy(void *obj)
 {
-       struct ast_bridge_channel *bridge_channel;
-
-       ao2_lock(bridge);
+       struct bridge_manager_controller *manager = obj;
+       struct bridge_manager_request *request;
+
+       if (manager->thread != AST_PTHREADT_NULL) {
+               /* Stop the manager thread. */
+               ao2_lock(manager);
+               manager->stop = 1;
+               ast_cond_signal(&manager->cond);
+               ao2_unlock(manager);
+               ast_debug(1, "Waiting for bridge manager thread to die.\n");
+               pthread_join(manager->thread, NULL);
+       }
 
-       if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-               ao2_unlock(bridge);
-               return -1;
+       /* Destroy the service request queue. */
+       while ((request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node))) {
+               ao2_ref(request->bridge, -1);
+               ast_free(request);
        }
 
-       bridge_channel_unsuspend(bridge, bridge_channel);
+       ast_cond_destroy(&manager->cond);
+}
+
+/*!
+ * \internal
+ * \brief Create the bridge manager controller.
+ * \since 12.0.0
+ *
+ * \retval manager on success.
+ * \retval NULL on error.
+ */
+static struct bridge_manager_controller *bridge_manager_create(void)
+{
+       struct bridge_manager_controller *manager;
 
-       ao2_unlock(bridge);
+       manager = ao2_alloc(sizeof(*manager), bridge_manager_destroy);
+       if (!manager) {
+               /* Well. This isn't good. */
+               return NULL;
+       }
+       ast_cond_init(&manager->cond, NULL);
+       AST_LIST_HEAD_INIT_NOLOCK(&manager->service_requests);
+
+       /* Create the bridge manager thread. */
+       if (ast_pthread_create(&manager->thread, NULL, bridge_manager_thread, manager)) {
+               /* Well. This isn't good either. */
+               manager->thread = AST_PTHREADT_NULL;
+               ao2_ref(manager, -1);
+               manager = NULL;
+       }
 
-       return 0;
+       return manager;
 }
 
-void ast_bridge_technology_suspend(struct ast_bridge_technology *technology)
+/*!
+ * \internal
+ * \brief Bridge ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flags)
 {
-       technology->suspended = 1;
+       const struct ast_bridge *bridge_left = obj_left;
+       const struct ast_bridge *bridge_right = obj_right;
+       const char *right_key = obj_right;
+       int cmp;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               right_key = bridge_right->uniqueid;
+               /* Fall through */
+       case OBJ_KEY:
+               cmp = strcmp(bridge_left->uniqueid, right_key);
+               break;
+       case OBJ_PARTIAL_KEY:
+               cmp = strncmp(bridge_left->uniqueid, right_key, strlen(right_key));
+               break;
+       }
+       return cmp;
 }
 
-void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology)
+struct bridge_complete {
+       /*! Nth match to return. */
+       int state;
+       /*! Which match currently on. */
+       int which;
+};
+
+static int complete_bridge_search(void *obj, void *arg, void *data, int flags)
 {
-       technology->suspended = 0;
+       struct bridge_complete *search = data;
+
+       if (++search->which > search->state) {
+               return CMP_MATCH;
+       }
+       return 0;
 }
 
-int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_features_hook_callback callback, const char *dtmf)
+static char *complete_bridge(const char *word, int state)
 {
-       if (ARRAY_LEN(builtin_features_handlers) <= feature
-               || builtin_features_handlers[feature]) {
-               return -1;
+       char *ret;
+       struct ast_bridge *bridge;
+       struct bridge_complete search = {
+               .state = state,
+               };
+
+       bridge = ao2_callback_data(bridges, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+               complete_bridge_search, (char *) word, &search);
+       if (!bridge) {
+               return NULL;
        }
+       ret = ast_strdup(bridge->uniqueid);
+       ao2_ref(bridge, -1);
+       return ret;
+}
 
-       if (!ast_strlen_zero(dtmf)) {
-               ast_copy_string(builtin_features_dtmf[feature], dtmf, sizeof(builtin_features_dtmf[feature]));
+static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-36s %5s %-15s %s\n"
+#define FORMAT_ROW "%-36s %5u %-15s %s\n"
+
+       struct ao2_iterator iter;
+       struct ast_bridge *bridge;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "bridge show all";
+               e->usage =
+                       "Usage: bridge show all\n"
+                       "       List all bridges\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
        }
 
-       builtin_features_handlers[feature] = callback;
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+       ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology");
+       iter = ao2_iterator_init(bridges, 0);
+       for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) {
+               ast_bridge_lock(bridge);
+               ast_cli(a->fd, FORMAT_ROW,
+                       bridge->uniqueid,
+                       bridge->num_channels,
+                       bridge->v_table ? bridge->v_table->name : "<unknown>",
+                       bridge->technology ? bridge->technology->name : "<unknown>");
+               ast_bridge_unlock(bridge);
+       }
+       ao2_iterator_destroy(&iter);
+       return CLI_SUCCESS;
 
-       return 0;
+#undef FORMAT_HDR
+#undef FORMAT_ROW
 }
 
-int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
+static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       if (ARRAY_LEN(builtin_features_handlers) <= feature
-               || !builtin_features_handlers[feature]) {
-               return -1;
+       struct ast_bridge *bridge;
+       struct ast_bridge_channel *bridge_channel;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "bridge show";
+               e->usage =
+                       "Usage: bridge show <bridge-id>\n"
+                       "       Show information about the <bridge-id> bridge\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 2) {
+                       return complete_bridge(a->word, a->n);
+               }
+               return NULL;
+       }
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
        }
 
-       builtin_features_handlers[feature] = NULL;
+       bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+       if (!bridge) {
+               ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+               return CLI_SUCCESS;
+       }
 
-       return 0;
+       ast_bridge_lock(bridge);
+       ast_cli(a->fd, "Id: %s\n", bridge->uniqueid);
+       ast_cli(a->fd, "Type: %s\n", bridge->v_table ? bridge->v_table->name : "<unknown>");
+       ast_cli(a->fd, "Technology: %s\n",
+               bridge->technology ? bridge->technology->name : "<unknown>");
+       ast_cli(a->fd, "Num-Channels: %u\n", bridge->num_channels);
+       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+               ast_cli(a->fd, "Channel: %s\n", ast_channel_name(bridge_channel->chan));
+       }
+       ast_bridge_unlock(bridge);
+       ao2_ref(bridge, -1);
+
+       return CLI_SUCCESS;
 }
 
-int ast_bridge_features_hook(struct ast_bridge_features *features,
-       const char *dtmf,
-       ast_bridge_features_hook_callback callback,
-       void *hook_pvt,
-       ast_bridge_features_hook_pvt_destructor destructor)
+static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       struct ast_bridge_features_hook *hook;
+       struct ast_bridge *bridge;
 
-       /* Allocate new memory and setup it's various variables */
-       hook = ast_calloc(1, sizeof(*hook));
-       if (!hook) {
-               return -1;
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "bridge destroy";
+               e->usage =
+                       "Usage: bridge destroy <bridge-id>\n"
+                       "       Destroy the <bridge-id> bridge\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 2) {
+                       return complete_bridge(a->word, a->n);
+               }
+               return NULL;
        }
-       ast_copy_string(hook->dtmf, dtmf, sizeof(hook->dtmf));
-       hook->callback = callback;
-       hook->destructor = destructor;
-       hook->hook_pvt = hook_pvt;
 
-       /* Once done we add it onto the list. Now it will be picked up when DTMF is used */
-       AST_LIST_INSERT_TAIL(&features->hooks, hook, entry);
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
+       }
 
-       features->usable = 1;
+       bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+       if (!bridge) {
+               ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+               return CLI_SUCCESS;
+       }
 
-       return 0;
-}
+       ast_cli(a->fd, "Destroying bridge '%s'\n", a->argv[2]);
+       ast_bridge_destroy(bridge);
 
-void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
-       ast_bridge_talking_indicate_callback talker_cb,
-       ast_bridge_talking_indicate_destructor talker_destructor,
-       void *pvt_data)
-{
-       features->talker_cb = talker_cb;
-       features->talker_destructor_cb = talker_destructor;
-       features->talker_pvt_data = pvt_data;
+       return CLI_SUCCESS;
 }
 
-int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config)
+static char *complete_bridge_participant(const char *bridge_name, const char *line, const char *word, int pos, int state)
 {
-       if (ARRAY_LEN(builtin_features_handlers) <= feature
-               || !builtin_features_handlers[feature]) {
-               return -1;
+       RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+       struct ast_bridge_channel *bridge_channel;
+       int which;
+       int wordlen;
+
+       bridge = ao2_find(bridges, bridge_name, OBJ_KEY);
+       if (!bridge) {
+               return NULL;
        }
 
-       /* If no alternate DTMF stream was provided use the default one */
-       if (ast_strlen_zero(dtmf)) {
-               dtmf = builtin_features_dtmf[feature];
-               /* If no DTMF is still available (ie: it has been disabled) then error out now */
-               if (ast_strlen_zero(dtmf)) {
-                       ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n", feature, features);
-                       return -1;
+       {
+               SCOPED_LOCK(bridge_lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+
+               which = 0;
+               wordlen = strlen(word);
+               AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+                       if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen)
+                               && ++which > state) {
+                               return ast_strdup(ast_channel_name(bridge_channel->chan));
+                       }
                }
        }
 
-       /* The rest is basically pretty easy. We create another hook using the built in feature's callback and DTMF, easy as pie. */
-       return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL);
+       return NULL;
 }
 
-void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag)
+static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       ast_set_flag(&features->feature_flags, flag);
-       features->usable = 1;
-}
+       RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "bridge kick";
+               e->usage =
+                       "Usage: bridge kick <bridge-id> <channel-name>\n"
+                       "       Kick the <channel-name> channel out of the <bridge-id> bridge\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 2) {
+                       return complete_bridge(a->word, a->n);
+               }
+               if (a->pos == 3) {
+                       return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
+               }
+               return NULL;
+       }
 
-int ast_bridge_features_init(struct ast_bridge_features *features)
-{
-       /* Zero out the structure */
-       memset(features, 0, sizeof(*features));
+       if (a->argc != 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+       if (!bridge) {
+               ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+               return CLI_SUCCESS;
+       }
 
-       /* Initialize the hooks list, just in case */
-       AST_LIST_HEAD_INIT_NOLOCK(&features->hooks);
+       chan = ast_channel_get_by_name_prefix(a->argv[3], strlen(a->argv[3]));
+       if (!chan) {
+               ast_cli(a->fd, "Channel '%s' not found\n", a->argv[3]);
+               return CLI_SUCCESS;
+       }
 
-       return 0;
+/*
+ * BUGBUG the CLI kick needs to get the bridge to decide if it should dissolve.
+ *
+ * Likely the best way to do this is to add a kick method.  The
+ * basic bridge class can then decide to dissolve the bridge if
+ * one of two channels is kicked.
+ *
+ * SIP/foo -- Local;1==Local;2 -- .... -- Local;1==Local;2 -- SIP/bar
+ * Kick a ;1 channel and the chain toward SIP/foo goes away.
+ * Kick a ;2 channel and the chain toward SIP/bar goes away.
+ *
+ * This can leave a local channel chain between the kicked ;1
+ * and ;2 channels that are orphaned until you manually request
+ * one of those channels to hangup or request the bridge to
+ * dissolve.
+ */
+       ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
+               ast_channel_name(chan), a->argv[2]);
+       ast_bridge_remove(bridge, chan);
+
+       return CLI_SUCCESS;
 }
 
-void ast_bridge_features_cleanup(struct ast_bridge_features *features)
+/*! Bridge technology capabilities to string. */
+static const char *tech_capability2str(uint32_t capabilities)
 {
-       struct ast_bridge_features_hook *hook;
-
-       /* This is relatively simple, hooks are kept as a list on the features structure so we just pop them off and free them */
-       while ((hook = AST_LIST_REMOVE_HEAD(&features->hooks, entry))) {
-               if (hook->destructor) {
-                       hook->destructor(hook->hook_pvt);
-               }
-               ast_free(hook);
-       }
-       if (features->talker_destructor_cb && features->talker_pvt_data) {
-               features->talker_destructor_cb(features->talker_pvt_data);
-               features->talker_pvt_data = NULL;
+       const char *type;
+
+       if (capabilities & AST_BRIDGE_CAPABILITY_HOLDING) {
+               type = "Holding";
+       } else if (capabilities & AST_BRIDGE_CAPABILITY_EARLY) {
+               type = "Early";
+       } else if (capabilities & AST_BRIDGE_CAPABILITY_NATIVE) {
+               type = "Native";
+       } else if (capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
+               type = "1to1Mix";
+       } else if (capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+               type = "MultiMix";
+       } else {
+               type = "<Unknown>";
        }
+       return type;
 }
 
-int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct ast_channel *chan)
+static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       struct ast_bridge_channel *bridge_channel;
+#define FORMAT_HDR "%-20s %-20s %8s %s\n"
+#define FORMAT_ROW "%-20s %-20s %8d %s\n"
 
-       ao2_lock(bridge);
+       struct ast_bridge_technology *cur;
 
-       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
-               if (bridge_channel->chan == chan) {
-                       continue;
-               }
-               ast_copy_string(bridge_channel->dtmf_stream_q, dtmf, sizeof(bridge_channel->dtmf_stream_q));
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DTMF);
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "bridge technology show";
+               e->usage =
+                       "Usage: bridge technology show\n"
+                       "       List registered bridge technologies\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
        }
 
-       ao2_unlock(bridge);
+       ast_cli(a->fd, FORMAT_HDR, "Name", "Type", "Priority", "Suspended");
+       AST_RWLIST_RDLOCK(&bridge_technologies);
+       AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+               const char *type;
 
-       return 0;
-}
+               /* Decode type for display */
+               type = tech_capability2str(cur->capabilities);
 
-void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
-{
-       ao2_lock(bridge);
-       bridge->internal_mixing_interval = mixing_interval;
-       ao2_unlock(bridge);
+               ast_cli(a->fd, FORMAT_ROW, cur->name, type, cur->preference,
+                       AST_CLI_YESNO(cur->suspended));
+       }
+       AST_RWLIST_UNLOCK(&bridge_technologies);
+       return CLI_SUCCESS;
+
+#undef FORMAT
 }
 
-void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
+static char *complete_bridge_technology(const char *word, int state)
 {
+       struct ast_bridge_technology *cur;
+       char *res;
+       int which;
+       int wordlen;
 
-       ao2_lock(bridge);
-       bridge->internal_sample_rate = sample_rate;
-       ao2_unlock(bridge);
+       which = 0;
+       wordlen = strlen(word);
+       AST_RWLIST_RDLOCK(&bridge_technologies);
+       AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+               if (!strncasecmp(cur->name, word, wordlen) && ++which > state) {
+                       res = ast_strdup(cur->name);
+                       AST_RWLIST_UNLOCK(&bridge_technologies);
+                       return res;
+               }
+       }
+       AST_RWLIST_UNLOCK(&bridge_technologies);
+       return NULL;
 }
 
-static void cleanup_video_mode(struct ast_bridge *bridge)
+static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       switch (bridge->video_mode.mode) {
-       case AST_BRIDGE_VIDEO_MODE_NONE:
-               break;
-       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
-                       ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
+       struct ast_bridge_technology *cur;
+       int suspend;
+       int successful;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "bridge technology {suspend|unsuspend}";
+               e->usage =
+                       "Usage: bridge technology {suspend|unsuspend} <technology-name>\n"
+                       "       Suspend or unsuspend a bridge technology.\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 3) {
+                       return complete_bridge_technology(a->word, a->n);
                }
-               break;
-       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
-                       ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
+               return NULL;
+       }
+
+       if (a->argc != 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       suspend = !strcasecmp(a->argv[2], "suspend");
+       successful = 0;
+       AST_RWLIST_WRLOCK(&bridge_technologies);
+       AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+               if (!strcasecmp(cur->name, a->argv[3])) {
+                       successful = 1;
+                       if (suspend) {
+                               ast_bridge_technology_suspend(cur);
+                       } else {
+                               ast_bridge_technology_unsuspend(cur);
+                       }
+                       break;
                }
-               if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
-                       ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
+       }
+       AST_RWLIST_UNLOCK(&bridge_technologies);
+
+       if (successful) {
+               if (suspend) {
+                       ast_cli(a->fd, "Suspended bridge technology '%s'\n", a->argv[3]);
+               } else {
+                       ast_cli(a->fd, "Unsuspended bridge technology '%s'\n", a->argv[3]);
                }
+       } else {
+               ast_cli(a->fd, "Bridge technology '%s' not found\n", a->argv[3]);
        }
-       memset(&bridge->video_mode, 0, sizeof(bridge->video_mode));
-}
 
-void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
-{
-       ao2_lock(bridge);
-       cleanup_video_mode(bridge);
-       bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
-       bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
-       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan));
-       ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
-       ao2_unlock(bridge);
+       return CLI_SUCCESS;
 }
 
-void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
+static struct ast_cli_entry bridge_cli[] = {
+       AST_CLI_DEFINE(handle_bridge_show_all, "List all bridges"),
+       AST_CLI_DEFINE(handle_bridge_show_specific, "Show information about a bridge"),
+       AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"),
+       AST_CLI_DEFINE(handle_bridge_kick_channel, "Kick a channel from a bridge"),
+       AST_CLI_DEFINE(handle_bridge_technology_show, "List registered bridge technologies"),
+       AST_CLI_DEFINE(handle_bridge_technology_suspend, "Suspend/unsuspend a bridge technology"),
+};
+
+/*!
+ * \internal
+ * \brief Shutdown the bridging system.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void bridge_shutdown(void)
 {
-       ao2_lock(bridge);
-       cleanup_video_mode(bridge);
-       bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
-       ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode);
-       ao2_unlock(bridge);
+       ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+       ao2_cleanup(bridges);
+       bridges = NULL;
+       ao2_cleanup(bridge_manager);
+       bridge_manager = NULL;
+       ast_stasis_bridging_shutdown();
 }
 
-void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
+int ast_bridging_init(void)
 {
-       struct ast_bridge_video_talker_src_data *data;
-       /* If the channel doesn't support video, we don't care about it */
-       if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) {
-               return;
+       if (ast_stasis_bridging_init()) {
+               bridge_shutdown();
+               return -1;
        }
 
-       ao2_lock(bridge);
-       data = &bridge->video_mode.mode_data.talker_src_data;
-
-       if (data->chan_vsrc == chan) {
-               data->average_talking_energy = talker_energy;
-       } else if ((data->average_talking_energy < talker_energy) && is_keyframe) {
-               if (data->chan_old_vsrc) {
-                       ast_channel_unref(data->chan_old_vsrc);
-               }
-               if (data->chan_vsrc) {
-                       data->chan_old_vsrc = data->chan_vsrc;
-                       ast_indicate(data->chan_old_vsrc, AST_CONTROL_VIDUPDATE);
-               }
-               data->chan_vsrc = ast_channel_ref(chan);
-               data->average_talking_energy = talker_energy;
-               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
-               ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
-       } else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
-               ast_indicate(chan, AST_CONTROL_VIDUPDATE);
-       } else if (!data->chan_vsrc && is_keyframe) {
-               data->chan_vsrc = ast_channel_ref(chan);
-               data->average_talking_energy = talker_energy;
-               ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
-               ast_indicate(chan, AST_CONTROL_VIDUPDATE);
-       } else if (!data->chan_old_vsrc && is_keyframe) {
-               data->chan_old_vsrc = ast_channel_ref(chan);
-               ast_indicate(chan, AST_CONTROL_VIDUPDATE);
+       bridge_manager = bridge_manager_create();
+       if (!bridge_manager) {
+               bridge_shutdown();
+               return -1;
        }
-       ao2_unlock(bridge);
-}
 
-int ast_bridge_number_video_src(struct ast_bridge *bridge)
-{
-       int res = 0;
-
-       ao2_lock(bridge);
-       switch (bridge->video_mode.mode) {
-       case AST_BRIDGE_VIDEO_MODE_NONE:
-               break;
-       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
-                       res = 1;
-               }
-               break;
-       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
-                       res++;
-               }
-               if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
-                       res++;
-               }
+       bridges = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
+               AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_sort_cmp, NULL);
+       if (!bridges) {
+               bridge_shutdown();
+               return -1;
        }
-       ao2_unlock(bridge);
-       return res;
-}
-
-int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
-{
-       int res = 0;
 
-       ao2_lock(bridge);
-       switch (bridge->video_mode.mode) {
-       case AST_BRIDGE_VIDEO_MODE_NONE:
-               break;
-       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
-                       res = 1;
-               }
-               break;
-       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
-                       res = 1;
-               } else if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
-                       res = 2;
-               }
+       ast_bridging_init_basic();
 
-       }
-       ao2_unlock(bridge);
-       return res;
-}
+/* BUGBUG need AMI action equivalents to the CLI commands. */
+       ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
 
-void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
-{
-       ao2_lock(bridge);
-       switch (bridge->video_mode.mode) {
-       case AST_BRIDGE_VIDEO_MODE_NONE:
-               break;
-       case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
-               if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) {
-                       if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) {
-                               ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc);
-                       }
-                       bridge->video_mode.mode_data.single_src_data.chan_vsrc = NULL;
-               }
-               break;
-       case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
-               if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) {
-                       if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) {
-                               ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc);
-                       }
-                       bridge->video_mode.mode_data.talker_src_data.chan_vsrc = NULL;
-                       bridge->video_mode.mode_data.talker_src_data.average_talking_energy = 0;
-               }
-               if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) {
-                       if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) {
-                               ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc);
-                       }
-                       bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
-               }
-       }
-       ao2_unlock(bridge);
+       ast_register_atexit(bridge_shutdown);
+       return 0;
 }
diff --git a/main/bridging_basic.c b/main/bridging_basic.c
new file mode 100644 (file)
index 0000000..00daf77
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Basic bridge class.  It is a subclass of struct ast_bridge.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/channel.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/astobj2.h"
+
+/* ------------------------------------------------------------------- */
+
+static const struct ast_datastore_info dtmf_features_info = {
+       .type = "bridge-dtmf-features",
+       .destroy = ast_free_ptr,
+};
+
+int ast_bridge_features_ds_set(struct ast_channel *chan, struct ast_flags *flags)
+{
+       struct ast_datastore *datastore;
+       struct ast_flags *ds_flags;
+
+       datastore = ast_channel_datastore_find(chan, &dtmf_features_info, NULL);
+       if (datastore) {
+               ds_flags = datastore->data;
+               *ds_flags = *flags;
+               return 0;
+       }
+
+       datastore = ast_datastore_alloc(&dtmf_features_info, NULL);
+       if (!datastore) {
+               return -1;
+       }
+
+       ds_flags = ast_malloc(sizeof(*ds_flags));
+       if (!ds_flags) {
+               ast_datastore_free(datastore);
+               return -1;
+       }
+
+       *ds_flags = *flags;
+       datastore->data = ds_flags;
+       ast_channel_datastore_add(chan, datastore);
+       return 0;
+}
+
+struct ast_flags *ast_bridge_features_ds_get(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       datastore = ast_channel_datastore_find(chan, &dtmf_features_info, NULL);
+       if (!datastore) {
+               return NULL;
+       }
+       return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Determine if we should dissolve the bridge from a hangup.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int basic_hangup_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+/* BUGBUG Race condition.  If all parties but one hangup at the same time, the bridge may not be dissolved on the remaining party. */
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       if (2 < bridge_channel->bridge->num_channels) {
+               /* Just allow this channel to leave the multi-party bridge. */
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+       }
+       ast_bridge_unlock(bridge_channel->bridge);
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge basic push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ * \note Stub because of nothing to do.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_basic_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+       if (ast_bridge_hangup_hook(bridge_channel->features, basic_hangup_hook, NULL, NULL, 1)
+               || ast_bridge_channel_setup_features(bridge_channel)) {
+               return -1;
+       }
+
+       return ast_bridge_base_v_table.push(self, bridge_channel, swap);
+}
+
+struct ast_bridge_methods ast_bridge_basic_v_table;
+
+struct ast_bridge *ast_bridge_basic_new(void)
+{
+       void *bridge;
+
+       bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_basic_v_table);
+       bridge = ast_bridge_base_init(bridge,
+               AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX
+                       | AST_BRIDGE_CAPABILITY_MULTIMIX,
+               AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_DISSOLVE_EMPTY
+                       | AST_BRIDGE_FLAG_SMART);
+       bridge = ast_bridge_register(bridge);
+       return bridge;
+}
+
+void ast_bridging_init_basic(void)
+{
+       /* Setup bridge basic subclass v_table. */
+       ast_bridge_basic_v_table = ast_bridge_base_v_table;
+       ast_bridge_basic_v_table.name = "basic";
+       ast_bridge_basic_v_table.push = bridge_basic_push;
+}
diff --git a/main/bridging_roles.c b/main/bridging_roles.c
new file mode 100644 (file)
index 0000000..079cbdb
--- /dev/null
@@ -0,0 +1,462 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Channel Bridging Roles API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ *
+ * \ingroup bridges
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <signal.h>
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/datastore.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_roles.h"
+#include "asterisk/stringfields.h"
+
+struct bridge_role_option {
+       AST_LIST_ENTRY(bridge_role_option) list;
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(option);
+               AST_STRING_FIELD(value);
+       );
+};
+
+struct bridge_role {
+       AST_LIST_ENTRY(bridge_role) list;
+       AST_LIST_HEAD(, bridge_role_option) options;
+       char role[AST_ROLE_LEN];
+};
+
+struct bridge_roles_datastore {
+       AST_LIST_HEAD(, bridge_role) role_list;
+};
+
+/*!
+ * \internal
+ * \brief Destructor function for a bridge role
+ * \since 12.0.0
+ *
+ * \param role bridge_role being destroyed
+ *
+ * \return Nothing
+ */
+static void bridge_role_destroy(struct bridge_role *role)
+{
+       struct bridge_role_option *role_option;
+       while ((role_option = AST_LIST_REMOVE_HEAD(&role->options, list))) {
+               ast_string_field_free_memory(role_option);
+               ast_free(role_option);
+       }
+       ast_free(role);
+}
+
+/*!
+ * \internal
+ * \brief Destructor function for bridge role datastores
+ * \since 12.0.0
+ *
+ * \param data Pointer to the datastore being destroyed
+ *
+ * \return Nothing
+ */
+static void bridge_role_datastore_destroy(void *data)
+{
+       struct bridge_roles_datastore *roles_datastore = data;
+       struct bridge_role *role;
+
+       while ((role = AST_LIST_REMOVE_HEAD(&roles_datastore->role_list, list))) {
+               bridge_role_destroy(role);
+       }
+
+       ast_free(roles_datastore);
+}
+
+static const struct ast_datastore_info bridge_role_info = {
+       .type = "bridge roles",
+       .destroy = bridge_role_datastore_destroy,
+};
+
+/*!
+ * \internal
+ * \brief Setup a bridge role datastore on a channel
+ * \since 12.0.0
+ *
+ * \param chan Chan the datastore is being setup on
+ *
+ * \retval NULL if failed
+ * \retval pointer to the newly created datastore
+ */
+static struct bridge_roles_datastore *setup_bridge_roles_datastore(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore = NULL;
+       struct bridge_roles_datastore *roles_datastore = NULL;
+
+       if (!(datastore = ast_datastore_alloc(&bridge_role_info, NULL))) {
+               return NULL;
+       }
+
+       if (!(roles_datastore = ast_calloc(1, sizeof(*roles_datastore)))) {
+               ast_datastore_free(datastore);
+               return NULL;
+       }
+
+       datastore->data = roles_datastore;
+       ast_channel_datastore_add(chan, datastore);
+       return roles_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Get the bridge_roles_datastore from a channel if it exists. Don't create one if it doesn't.
+ * \since 12.0.0
+ *
+ * \param chan Channel we want the bridge_roles_datastore from
+ *
+ * \retval NULL if we can't find the datastore
+ * \retval pointer to the bridge_roles_datastore
+ */
+static struct bridge_roles_datastore *fetch_bridge_roles_datastore(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore = NULL;
+
+       ast_channel_lock(chan);
+       if (!(datastore = ast_channel_datastore_find(chan, &bridge_role_info, NULL))) {
+               ast_channel_unlock(chan);
+               return NULL;
+       }
+       ast_channel_unlock(chan);
+
+       return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Get the bridge_roles_datastore from a channel if it exists. If not, create one.
+ * \since 12.0.0
+ *
+ * \param chan Channel we want the bridge_roles_datastore from
+ *
+ * \retval NULL If we can't find and can't create the datastore
+ * \retval pointer to the bridge_roles_datastore
+ */
+static struct bridge_roles_datastore *fetch_or_create_bridge_roles_datastore(struct ast_channel *chan)
+{
+       struct bridge_roles_datastore *roles_datastore;
+
+       ast_channel_lock(chan);
+       roles_datastore = fetch_bridge_roles_datastore(chan);
+       if (!roles_datastore) {
+               roles_datastore = setup_bridge_roles_datastore(chan);
+       }
+       ast_channel_unlock(chan);
+
+       return roles_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role from a bridge_roles_datastore if the datastore has it
+ * \since 12.0.0
+ *
+ * \param roles_datastore The bridge_roles_datastore we are looking for the role of
+ * \param role_name Name of the role being sought
+ *
+ * \retval NULL if the datastore does not have the requested role
+ * \retval pointer to the requested role
+ */
+static struct bridge_role *get_role_from_datastore(struct bridge_roles_datastore *roles_datastore, const char *role_name)
+{
+       struct bridge_role *role;
+
+       AST_LIST_TRAVERSE(&roles_datastore->role_list, role, list) {
+               if (!strcmp(role->role, role_name)) {
+                       return role;
+               }
+       }
+
+       return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role from a channel structure if the channel's datastore has it
+ * \since 12.0.0
+ *
+ * \param channel The channel we are checking the role of
+ * \param role_name Name of the role sought
+ *
+ * \retval NULL if the channel's datastore does not have the requested role
+ * \retval pointer to the requested role
+ */
+static struct bridge_role *get_role_from_channel(struct ast_channel *channel, const char *role_name)
+{
+       struct bridge_roles_datastore *roles_datastore = fetch_bridge_roles_datastore(channel);
+       return roles_datastore ? get_role_from_datastore(roles_datastore, role_name) : NULL;
+}
+
+/*!
+ * \internal
+ * \brief Obtain a role option from a bridge role if it exists in the bridge role's option list
+ * \since 12.0.0
+ *
+ * \param role a pointer to the bridge role wea re searching for the option of
+ * \param option Name of the option sought
+ *
+ * \retval NULL if the bridge role doesn't have the requested option
+ * \retval pointer to the requested option
+ */
+static struct bridge_role_option *get_role_option(struct bridge_role *role, const char *option)
+{
+       struct bridge_role_option *role_option = NULL;
+       AST_LIST_TRAVERSE(&role->options, role_option, list) {
+               if (!strcmp(role_option->option, option)) {
+                       return role_option;
+               }
+       }
+       return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Setup a bridge role on an existing bridge role datastore
+ * \since 12.0.0
+ *
+ * \param roles_datastore bridge_roles_datastore receiving the new role
+ * \param role_name Name of the role being received
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int setup_bridge_role(struct bridge_roles_datastore *roles_datastore, const char *role_name)
+{
+       struct bridge_role *role;
+       role = ast_calloc(1, sizeof(*role));
+
+       if (!role) {
+               return -1;
+       }
+
+       ast_copy_string(role->role, role_name, sizeof(role->role));
+
+       AST_LIST_INSERT_TAIL(&roles_datastore->role_list, role, list);
+       ast_debug(3, "Set role '%s'\n", role_name);
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Setup a bridge role option on an existing bridge role
+ * \since 12.0.0
+ *
+ * \param role The role receiving the option
+ * \param option Name of the option
+ * \param value the option's value
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int setup_bridge_role_option(struct bridge_role *role, const char *option, const char *value)
+{
+       struct bridge_role_option *role_option;
+
+       if (!value) {
+               value = "";
+       }
+
+       role_option = ast_calloc(1, sizeof(*role_option));
+       if (!role_option) {
+               return -1;
+       }
+
+       if (ast_string_field_init(role_option, 32)) {
+               ast_free(role_option);
+               return -1;
+       }
+
+       ast_string_field_set(role_option, option, option);
+       ast_string_field_set(role_option, value, value);
+
+       AST_LIST_INSERT_TAIL(&role->options, role_option, list);
+
+       return 0;
+}
+
+int ast_channel_add_bridge_role(struct ast_channel *chan, const char *role_name)
+{
+       struct bridge_roles_datastore *roles_datastore = fetch_or_create_bridge_roles_datastore(chan);
+
+       if (!roles_datastore) {
+               ast_log(LOG_WARNING, "Unable to set up bridge role datastore on channel %s\n", ast_channel_name(chan));
+               return -1;
+       }
+
+       /* Check to make sure we aren't adding a redundant role */
+       if (get_role_from_datastore(roles_datastore, role_name)) {
+               ast_debug(2, "Bridge role %s is already applied to the channel %s\n", role_name, ast_channel_name(chan));
+               return 0;
+       }
+
+       /* It wasn't already there, so we can just finish setting it up now. */
+       return setup_bridge_role(roles_datastore, role_name);
+}
+
+void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_name)
+{
+       struct bridge_roles_datastore *roles_datastore = fetch_bridge_roles_datastore(chan);
+       struct bridge_role *role;
+
+       if (!roles_datastore) {
+               /* The roles datastore didn't already exist, so there is no need to remove a role */
+               ast_debug(2, "Role %s did not exist on channel %s\n", role_name, ast_channel_name(chan));
+               return;
+       }
+
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&roles_datastore->role_list, role, list) {
+               if (!strcmp(role->role, role_name)) {
+                       ast_debug(2, "Removing bridge role %s from channel %s\n", role_name, ast_channel_name(chan));
+                       AST_LIST_REMOVE_CURRENT(list);
+                       bridge_role_destroy(role);
+                       return;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+
+       ast_debug(2, "Role %s did not exist on channel %s\n", role_name, ast_channel_name(chan));
+}
+
+int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value)
+{
+       struct bridge_role *role = get_role_from_channel(channel, role_name);
+       struct bridge_role_option *role_option;
+
+       if (!role) {
+               return -1;
+       }
+
+       role_option = get_role_option(role, option);
+
+       if (role_option) {
+               ast_string_field_set(role_option, value, value);
+               return 0;
+       }
+
+       setup_bridge_role_option(role, option, value);
+
+       return 0;
+}
+
+int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name)
+{
+       if (!bridge_channel->bridge_roles) {
+               return 0;
+       }
+
+       return get_role_from_datastore(bridge_channel->bridge_roles, role_name) ? 1 : 0;
+}
+
+const char *ast_bridge_channel_get_role_option(struct ast_bridge_channel *bridge_channel, const char *role_name, const char *option)
+{
+       struct bridge_role *role;
+       struct bridge_role_option *role_option = NULL;
+
+       if (!bridge_channel->bridge_roles) {
+               return NULL;
+       }
+
+       role = get_role_from_datastore(bridge_channel->bridge_roles, role_name);
+
+       if (!role) {
+               return NULL;
+       }
+
+       role_option = get_role_option(role, option);
+
+       return role_option ? role_option->value : NULL;
+}
+
+int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel)
+{
+       struct bridge_roles_datastore *roles_datastore;
+       struct bridge_role *role = NULL;
+       struct bridge_role_option *role_option;
+
+       if (!bridge_channel->chan) {
+               ast_debug(2, "Attempted to set roles on a bridge channel that has no associated channel. That's a bad idea.\n");
+               return -1;
+       }
+
+       if (bridge_channel->bridge_roles) {
+               ast_debug(2, "Attempted to reset roles while roles were already established. Purge existing roles first.\n");
+               return -1;
+       }
+
+       roles_datastore = fetch_bridge_roles_datastore(bridge_channel->chan);
+       if (!roles_datastore) {
+               /* No roles to establish. */
+               return 0;
+       }
+
+       if (!(bridge_channel->bridge_roles = ast_calloc(1, sizeof(*bridge_channel->bridge_roles)))) {
+               return -1;
+       }
+
+       AST_LIST_TRAVERSE(&roles_datastore->role_list, role, list) {
+               struct bridge_role *this_role_copy;
+
+               if (setup_bridge_role(bridge_channel->bridge_roles, role->role)) {
+                       /* We need to abandon the copy because we couldn't setup a role */
+                       ast_bridge_channel_clear_roles(bridge_channel);
+                       return -1;
+               }
+               this_role_copy = AST_LIST_LAST(&bridge_channel->bridge_roles->role_list);
+
+               AST_LIST_TRAVERSE(&role->options, role_option, list) {
+                       if (setup_bridge_role_option(this_role_copy, role_option->option, role_option->value)) {
+                               /* We need to abandon the copy because we couldn't setup a role option */
+                               ast_bridge_channel_clear_roles(bridge_channel);
+                               return -1;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+void ast_bridge_channel_clear_roles(struct ast_bridge_channel *bridge_channel)
+{
+       if (bridge_channel->bridge_roles) {
+               bridge_role_datastore_destroy(bridge_channel->bridge_roles);
+               bridge_channel->bridge_roles = NULL;
+       }
+}
index 0b2dedd637a33768f81084804bd7e1f447ab195a..aec43edf74aec8a3e39bbcc99644cf5dafc6400e 100644 (file)
@@ -73,6 +73,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/data.h"
 #include "asterisk/channel_internal.h"
 #include "asterisk/features.h"
+#include "asterisk/bridging.h"
 #include "asterisk/test.h"
 #include "asterisk/stasis_channels.h"
 
@@ -1634,6 +1635,7 @@ int ast_is_deferrable_frame(const struct ast_frame *frame)
         * be queued up or not.
         */
        switch (frame->frametype) {
+       case AST_FRAME_BRIDGE_ACTION:
        case AST_FRAME_CONTROL:
        case AST_FRAME_TEXT:
        case AST_FRAME_IMAGE:
@@ -3013,6 +3015,7 @@ int __ast_answer(struct ast_channel *chan, unsigned int delay, int cdr_answer)
                                        break;
                                case AST_FRAME_CONTROL:
                                case AST_FRAME_IAX:
+                               case AST_FRAME_BRIDGE_ACTION:
                                case AST_FRAME_NULL:
                                case AST_FRAME_CNG:
                                        break;
@@ -6237,87 +6240,21 @@ int ast_channel_make_compatible(struct ast_channel *chan, struct ast_channel *pe
 static int __ast_channel_masquerade(struct ast_channel *original, struct ast_channel *clonechan, struct ast_datastore *xfer_ds)
 {
        int res = -1;
-       struct ast_channel *final_orig, *final_clone, *base;
 
-       for (;;) {
-               final_orig = original;
-               final_clone = clonechan;
-
-               ast_channel_lock_both(original, clonechan);
-
-               if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
-                       || ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
-                       /* Zombies! Run! */
-                       ast_log(LOG_WARNING,
-                               "Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
-                               ast_channel_name(original), ast_channel_name(clonechan));
-                       ast_channel_unlock(clonechan);
-                       ast_channel_unlock(original);
-                       return -1;
-               }
-
-               /*
-                * Each of these channels may be sitting behind a channel proxy
-                * (i.e. chan_agent) and if so, we don't really want to
-                * masquerade it, but its proxy
-                */
-               if (ast_channel_internal_bridged_channel(original)
-                       && (ast_channel_internal_bridged_channel(original) != ast_bridged_channel(original))
-                       && (ast_channel_internal_bridged_channel(ast_channel_internal_bridged_channel(original)) != original)) {
-                       final_orig = ast_channel_internal_bridged_channel(original);
-               }
-               if (ast_channel_internal_bridged_channel(clonechan)
-                       && (ast_channel_internal_bridged_channel(clonechan) != ast_bridged_channel(clonechan))
-                       && (ast_channel_internal_bridged_channel(ast_channel_internal_bridged_channel(clonechan)) != clonechan)) {
-                       final_clone = ast_channel_internal_bridged_channel(clonechan);
-               }
-               if (ast_channel_tech(final_clone)->get_base_channel
-                       && (base = ast_channel_tech(final_clone)->get_base_channel(final_clone))) {
-                       final_clone = base;
-               }
-
-               if ((final_orig != original) || (final_clone != clonechan)) {
-                       /*
-                        * Lots and lots of deadlock avoidance.  The main one we're
-                        * competing with is ast_write(), which locks channels
-                        * recursively, when working with a proxy channel.
-                        */
-                       if (ast_channel_trylock(final_orig)) {
-                               ast_channel_unlock(clonechan);
-                               ast_channel_unlock(original);
-
-                               /* Try again */
-                               continue;
-                       }
-                       if (ast_channel_trylock(final_clone)) {
-                               ast_channel_unlock(final_orig);
-                               ast_channel_unlock(clonechan);
-                               ast_channel_unlock(original);
-
-                               /* Try again */
-                               continue;
-                       }
-                       ast_channel_unlock(clonechan);
-                       ast_channel_unlock(original);
-                       original = final_orig;
-                       clonechan = final_clone;
-
-                       if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
-                               || ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
-                               /* Zombies! Run! */
-                               ast_log(LOG_WARNING,
-                                       "Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
-                                       ast_channel_name(original), ast_channel_name(clonechan));
-                               ast_channel_unlock(clonechan);
-                               ast_channel_unlock(original);
-                               return -1;
-                       }
-               }
-               break;
+       if (original == clonechan) {
+               ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n",
+                       ast_channel_name(original));
+               return -1;
        }
 
-       if (original == clonechan) {
-               ast_log(LOG_WARNING, "Can't masquerade channel '%s' into itself!\n", ast_channel_name(original));
+       ast_channel_lock_both(original, clonechan);
+
+       if (ast_test_flag(ast_channel_flags(original), AST_FLAG_ZOMBIE)
+               || ast_test_flag(ast_channel_flags(clonechan), AST_FLAG_ZOMBIE)) {
+               /* Zombies! Run! */
+               ast_log(LOG_WARNING,
+                       "Can't setup masquerade. One or both channels is dead. (%s <-- %s)\n",
+                       ast_channel_name(original), ast_channel_name(clonechan));
                ast_channel_unlock(clonechan);
                ast_channel_unlock(original);
                return -1;
@@ -6638,15 +6575,33 @@ static void ast_channel_change_linkedid(struct ast_channel *chan, const char *li
        ast_cel_linkedid_ref(linkedid);
 }
 
-/*!
-  \brief Propagate the oldest linkedid between associated channels
-
-*/
+/*! \brief Propagate the oldest linkedid between associated channels */
 void ast_channel_set_linkgroup(struct ast_channel *chan, struct ast_channel *peer)
 {
        const char* linkedid=NULL;
        struct ast_channel *bridged;
 
+/*
+ * BUGBUG this needs to be updated to not use ast_channel_internal_bridged_channel().
+ * BUGBUG this needs to be updated to not use ast_bridged_channel().
+ *
+ * We may be better off making this a function of the bridging
+ * framework.  Essentially, as each channel joins a bridge, the
+ * oldest linkedid should be propagated between all pairs of
+ * channels.  This should be handled by bridging (unless you're
+ * in an infinite wait bridge...) just like the BRIDGEPEER
+ * channel variable.
+ *
+ * This is currently called in two places:
+ *
+ * (1) In channel masquerade. To some extent this shouldn't
+ * really be done any longer - we don't really want a channel to
+ * have its linkedid change, even if it replaces a channel that
+ * had an older linkedid.  The two channels aren't really
+ * 'related', they're simply swapping with each other.
+ *
+ * (2) In features.c as two channels are bridged.
+ */
        linkedid = oldest_linkedid(ast_channel_linkedid(chan), ast_channel_linkedid(peer));
        linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(chan));
        linkedid = oldest_linkedid(linkedid, ast_channel_uniqueid(peer));
@@ -6829,7 +6784,7 @@ void ast_do_masquerade(struct ast_channel *original)
         * and new masquerade attempts, the channels container must be
         * locked for the entire masquerade.  The original and clonechan
         * need to be unlocked earlier to avoid potential deadlocks with
-        * the chan_local deadlock avoidance method.
+        * the unreal/local channel deadlock avoidance method.
         *
         * The container lock blocks competing masquerade attempts from
         * starting as well as being necessary for proper locking order
@@ -7146,11 +7101,6 @@ void ast_do_masquerade(struct ast_channel *original)
 
        /* copy over accuntcode and set peeraccount across the bridge */
        ast_channel_accountcode_set(original, S_OR(ast_channel_accountcode(clonechan), ""));
-       if (ast_channel_internal_bridged_channel(original)) {
-               /* XXX - should we try to lock original's bridged channel here? */
-               ast_channel_peeraccount_set(ast_channel_internal_bridged_channel(original), S_OR(ast_channel_accountcode(clonechan), ""));
-               ast_cel_report_event(original, AST_CEL_BRIDGE_UPDATE, NULL, NULL, NULL);
-       }
 
        ast_debug(1, "Putting channel %s in %s/%s formats\n", ast_channel_name(original),
                ast_getformatname(&wformat), ast_getformatname(&rformat));
@@ -7196,6 +7146,8 @@ void ast_do_masquerade(struct ast_channel *original)
        ast_channel_unlock(original);
        ast_channel_unlock(clonechan);
 
+       ast_bridge_notify_masquerade(original);
+
        if (clone_sending_dtmf_digit) {
                /*
                 * The clonechan was sending a DTMF digit that was not completed
@@ -7348,14 +7300,10 @@ int ast_setstate(struct ast_channel *chan, enum ast_channel_state state)
        return 0;
 }
 
-/*! \brief Find bridged channel */
+/*! BUGBUG ast_bridged_channel() is to be removed. */
 struct ast_channel *ast_bridged_channel(struct ast_channel *chan)
 {
-       struct ast_channel *bridged;
-       bridged = ast_channel_internal_bridged_channel(chan);
-       if (bridged && ast_channel_tech(bridged)->bridged_channel)
-               bridged = ast_channel_tech(bridged)->bridged_channel(chan, bridged);
-       return bridged;
+       return NULL;
 }
 
 static void bridge_playfile(struct ast_channel *chan, struct ast_channel *peer, const char *sound, int remain)
@@ -7723,6 +7671,7 @@ static void bridge_play_sounds(struct ast_channel *c0, struct ast_channel *c1)
        }
 }
 
+/* BUGBUG ast_channel_bridge() and anything that only it calls will be removed. */
 /*! \brief Bridge two channels together */
 enum ast_bridge_result ast_channel_bridge(struct ast_channel *c0, struct ast_channel *c1,
                                          struct ast_bridge_config *config, struct ast_frame **fo, struct ast_channel **rc)
@@ -11232,3 +11181,48 @@ void ast_channel_unlink(struct ast_channel *chan)
 {
        ao2_unlink(channels, chan);
 }
+
+struct ast_bridge *ast_channel_get_bridge(const struct ast_channel *chan)
+{
+       struct ast_bridge *bridge;
+
+       bridge = ast_channel_internal_bridge(chan);
+       if (bridge) {
+               ao2_ref(bridge, +1);
+       }
+       return bridge;
+}
+
+int ast_channel_is_bridged(const struct ast_channel *chan)
+{
+       return ast_channel_internal_bridge(chan) != NULL;
+}
+
+struct ast_channel *ast_channel_bridge_peer(struct ast_channel *chan)
+{
+       struct ast_channel *peer;
+       struct ast_bridge *bridge;
+
+       /* Get the bridge the channel is in. */
+       ast_channel_lock(chan);
+       bridge = ast_channel_get_bridge(chan);
+       ast_channel_unlock(chan);
+       if (!bridge) {
+               return NULL;
+       }
+
+       peer = ast_bridge_peer(bridge, chan);
+       ao2_ref(bridge, -1);
+       return peer;
+}
+
+struct ast_bridge_channel *ast_channel_get_bridge_channel(struct ast_channel *chan)
+{
+       struct ast_bridge_channel *bridge_channel;
+
+       bridge_channel = ast_channel_internal_bridge_channel(chan);
+       if (bridge_channel) {
+               ao2_ref(bridge_channel, +1);
+       }
+       return bridge_channel;
+}
index f3293d59b8038038d14c41acf7cbeac477b58ebd..42aaada6d3eade73a7987896e27d1e128c476adb 100644 (file)
@@ -65,6 +65,7 @@ struct ast_channel {
        void *music_state;                              /*!< Music State*/
        void *generatordata;                            /*!< Current generator data if there is any */
        struct ast_generator *generator;                /*!< Current active data generator */
+/* BUGBUG bridged_channel must be eliminated from ast_channel */
        struct ast_channel * bridged_channel;                   /*!< Who are we bridged to, if we're bridged.
                                                         *   Who is proxying for us, if we are proxied (i.e. chan_agent).
                                                         *   Do not access directly, use ast_bridged_channel(chan) */
@@ -188,7 +189,9 @@ struct ast_channel {
 
        unsigned short transfercapability;              /*!< ISDN Transfer Capability - AST_FLAG_DIGITAL is not enough */
 
+/* BUGBUG the bridge pointer must change to an ast_channel_bridge pointer because it will never change while the channel is in the bridging system whereas the bridge could change. */
        struct ast_bridge *bridge;                      /*!< Bridge this channel is participating in */
+       struct ast_bridge_channel *bridge_channel;/*!< The bridge_channel this channel is linked with. */
        struct ast_timer *timer;                        /*!< timer object that provided timingfd */
 
        char context[AST_MAX_CONTEXT];                  /*!< Dialplan: Current extension context */
@@ -267,7 +270,6 @@ static void channel_data_add_flags(struct ast_data *tree,
        ast_data_add_bool(tree, "END_DTMF_ONLY", ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY));
        ast_data_add_bool(tree, "MASQ_NOSTREAM", ast_test_flag(ast_channel_flags(chan), AST_FLAG_MASQ_NOSTREAM));
        ast_data_add_bool(tree, "BRIDGE_HANGUP_RUN", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN));
-       ast_data_add_bool(tree, "BRIDGE_HANGUP_DONT", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT));
        ast_data_add_bool(tree, "DISABLE_WORKAROUNDS", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS));
        ast_data_add_bool(tree, "DISABLE_DEVSTATE_CACHE", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE));
 }
@@ -1257,6 +1259,15 @@ void ast_channel_internal_bridge_set(struct ast_channel *chan, struct ast_bridge
        chan->bridge = value;
 }
 
+struct ast_bridge_channel *ast_channel_internal_bridge_channel(const struct ast_channel *chan)
+{
+       return chan->bridge_channel;
+}
+void ast_channel_internal_bridge_channel_set(struct ast_channel *chan, struct ast_bridge_channel *value)
+{
+       chan->bridge_channel = value;
+}
+
 struct ast_channel *ast_channel_internal_bridged_channel(const struct ast_channel *chan)
 {
        return chan->bridged_channel;
index 7782b2279b096eda6a1bc18070ccb175eac56b24..22232acbc6c82918ac4e7b59df99509b753f476c 100644 (file)
@@ -60,6 +60,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/lock.h"
 #include "asterisk/threadstorage.h"
 #include "asterisk/translate.h"
+#include "asterisk/bridging.h"
 
 /*!
  * \brief List of restrictions per user.
@@ -899,7 +900,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                        ast_cli(a->fd, FORMAT_STRING2, "Channel", "Location", "State", "Application(Data)");
                else if (verbose)
                        ast_cli(a->fd, VERBOSE_FORMAT_STRING2, "Channel", "Context", "Extension", "Priority", "State", "Application", "Data",
-                               "CallerID", "Duration", "Accountcode", "PeerAccount", "BridgedTo");
+                               "CallerID", "Duration", "Accountcode", "PeerAccount", "BridgeID");
        }
 
        if (!count && !(iter = ast_channel_iterator_all_new())) {
@@ -907,12 +908,12 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
        }
 
        for (; iter && (c = ast_channel_iterator_next(iter)); ast_channel_unref(c)) {
-               struct ast_channel *bc;
+               struct ast_bridge *bridge;
                char durbuf[10] = "-";
 
                ast_channel_lock(c);
 
-               bc = ast_bridged_channel(c);
+               bridge = ast_channel_get_bridge(c);
 
                if (!count) {
                        if ((concise || verbose)  && ast_channel_cdr(c) && !ast_tvzero(ast_channel_cdr(c)->start)) {
@@ -935,7 +936,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                                        S_OR(ast_channel_peeraccount(c), ""),
                                        ast_channel_amaflags(c),
                                        durbuf,
-                                       bc ? ast_channel_name(bc) : "(None)",
+                                       bridge ? bridge->uniqueid : "(Not bridged)",
                                        ast_channel_uniqueid(c));
                        } else if (verbose) {
                                ast_cli(a->fd, VERBOSE_FORMAT_STRING, ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_state2str(ast_channel_state(c)),
@@ -945,7 +946,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                                        durbuf,
                                        S_OR(ast_channel_accountcode(c), ""),
                                        S_OR(ast_channel_peeraccount(c), ""),
-                                       bc ? ast_channel_name(bc) : "(None)");
+                                       bridge ? bridge->uniqueid : "(Not bridged)");
                        } else {
                                char locbuf[40] = "(None)";
                                char appdata[40] = "(None)";
@@ -958,6 +959,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                        }
                }
                ast_channel_unlock(c);
+               ao2_cleanup(bridge);
        }
 
        if (iter) {
@@ -1412,6 +1414,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 #ifdef CHANNEL_TRACE
        int trace_enabled;
 #endif
+       struct ast_bridge *bridge;
 
        switch (cmd) {
        case CLI_INIT:
@@ -1463,6 +1466,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
        }
 
        effective_connected_id = ast_channel_connected_effective_id(c);
+       bridge = ast_channel_get_bridge(c);
 
        ast_str_append(&output, 0,
                " -- General --\n"
@@ -1490,8 +1494,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                "     Frames out: %d%s\n"
                " Time to Hangup: %ld\n"
                "   Elapsed Time: %s\n"
-               "  Direct Bridge: %s\n"
-               "Indirect Bridge: %s\n"
+               "      Bridge ID: %s\n"
                " --   PBX   --\n"
                "        Context: %s\n"
                "      Extension: %s\n"
@@ -1502,7 +1505,10 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                "           Data: %s\n"
                "    Blocking in: %s\n"
                " Call Identifer: %s\n",
-               ast_channel_name(c), ast_channel_tech(c)->type, ast_channel_uniqueid(c), ast_channel_linkedid(c),
+               ast_channel_name(c),
+               ast_channel_tech(c)->type,
+               ast_channel_uniqueid(c),
+               ast_channel_linkedid(c),
                S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "(N/A)"),
                S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "(N/A)"),
                S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "(N/A)"),
@@ -1511,7 +1517,9 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                S_COR(effective_connected_id.name.valid, effective_connected_id.name.str, "(N/A)"),
                S_OR(ast_channel_dialed(c)->number.str, "(N/A)"),
                ast_channel_language(c),
-               ast_state2str(ast_channel_state(c)), ast_channel_state(c), ast_channel_rings(c),
+               ast_state2str(ast_channel_state(c)),
+               ast_channel_state(c),
+               ast_channel_rings(c),
                ast_getformatname_multiple(nf, sizeof(nf), ast_channel_nativeformats(c)),
                ast_getformatname(ast_channel_writeformat(c)),
                ast_getformatname(ast_channel_readformat(c)),
@@ -1520,11 +1528,19 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
                ast_channel_readtrans(c) ? "Yes" : "No",
                ast_translate_path_to_str(ast_channel_readtrans(c), &read_transpath),
                ast_channel_fd(c, 0),
-               ast_channel_fin(c) & ~DEBUGCHAN_FLAG, (ast_channel_fin(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
-               ast_channel_fout(c) & ~DEBUGCHAN_FLAG, (ast_channel_fout(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
-               (long)ast_channel_whentohangup(c)->tv_sec,
-               cdrtime, ast_channel_internal_bridged_channel(c) ? ast_channel_name(ast_channel_internal_bridged_channel(c)) : "<none>", ast_bridged_channel(c) ? ast_channel_name(ast_bridged_channel(c)) : "<none>",
-               ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_callgroup(c), ast_channel_pickupgroup(c), (ast_channel_appl(c) ? ast_channel_appl(c) : "(N/A)" ),
+               ast_channel_fin(c) & ~DEBUGCHAN_FLAG,
+               (ast_channel_fin(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+               ast_channel_fout(c) & ~DEBUGCHAN_FLAG,
+               (ast_channel_fout(c) & DEBUGCHAN_FLAG) ? " (DEBUGGED)" : "",
+               (long) ast_channel_whentohangup(c)->tv_sec,
+               cdrtime,
+               bridge ? bridge->uniqueid : "(Not bridged)",
+               ast_channel_context(c),
+               ast_channel_exten(c),
+               ast_channel_priority(c),
+               ast_channel_callgroup(c),
+               ast_channel_pickupgroup(c),
+               (ast_channel_appl(c) ? ast_channel_appl(c) : "(N/A)" ),
                (ast_channel_data(c) ? S_OR(ast_channel_data(c), "(Empty)") : "(None)"),
                (ast_test_flag(ast_channel_flags(c), AST_FLAG_BLOCKING) ? ast_channel_blockproc(c) : "(Not Blocking)"),
                S_OR(call_identifier_str, "(None)"));
@@ -1548,6 +1564,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 
        ast_channel_unlock(c);
        c = ast_channel_unref(c);
+       ao2_cleanup(bridge);
 
        ast_cli(a->fd, "%s", ast_str_buffer(output));
        ast_free(output);
index 06b45213194b2e05d8df667822cbd3a3fe77dea5..39a3fbe61c96ddcb57c00d28875ea63aa5a542ff 100644 (file)
@@ -222,6 +222,11 @@ int aco_option_register_deprecated(struct aco_info *info, const char *name, stru
        return 0;
 }
 
+unsigned int aco_option_get_flags(const struct aco_option *option)
+{
+       return option->flags;
+}
+
 #ifdef AST_XML_DOCS
 /*! \internal
  * \brief Find a particular ast_xml_doc_item from it's parent config_info, types, and name
diff --git a/main/core_local.c b/main/core_local.c
new file mode 100644 (file)
index 0000000..2e0bcc4
--- /dev/null
@@ -0,0 +1,775 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Local proxy channel driver.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+/* ------------------------------------------------------------------- */
+
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+#include "asterisk/core_local.h"
+#include "asterisk/_private.h"
+
+/*** DOCUMENTATION
+       <manager name="LocalOptimizeAway" language="en_US">
+               <synopsis>
+                       Optimize away a local channel when possible.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="Channel" required="true">
+                               <para>The channel name to optimize away.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>A local channel created with "/n" will not automatically optimize away.
+                       Calling this command on the local channel will clear that flag and allow
+                       it to optimize away if it's bridged or when it becomes bridged.</para>
+               </description>
+       </manager>
+ ***/
+
+static const char tdesc[] = "Local Proxy Channel Driver";
+
+static struct ao2_container *locals;
+
+static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
+static int local_call(struct ast_channel *ast, const char *dest, int timeout);
+static int local_hangup(struct ast_channel *ast);
+static int local_devicestate(const char *data);
+
+/* PBX interface structure for channel registration */
+static struct ast_channel_tech local_tech = {
+       .type = "Local",
+       .description = tdesc,
+       .requester = local_request,
+       .send_digit_begin = ast_unreal_digit_begin,
+       .send_digit_end = ast_unreal_digit_end,
+       .call = local_call,
+       .hangup = local_hangup,
+       .answer = ast_unreal_answer,
+       .read = ast_unreal_read,
+       .write = ast_unreal_write,
+       .write_video = ast_unreal_write,
+       .exception = ast_unreal_read,
+       .indicate = ast_unreal_indicate,
+       .fixup = ast_unreal_fixup,
+       .send_html = ast_unreal_sendhtml,
+       .send_text = ast_unreal_sendtext,
+       .devicestate = local_devicestate,
+       .queryoption = ast_unreal_queryoption,
+       .setoption = ast_unreal_setoption,
+};
+
+/*! What to do with the ;2 channel when ast_call() happens. */
+enum local_call_action {
+       /* The ast_call() will run dialplan on the ;2 channel. */
+       LOCAL_CALL_ACTION_DIALPLAN,
+       /* The ast_call() will impart the ;2 channel into a bridge. */
+       LOCAL_CALL_ACTION_BRIDGE,
+       /* The ast_call() will masquerade the ;2 channel into a channel. */
+       LOCAL_CALL_ACTION_MASQUERADE,
+};
+
+/*! Join a bridge on ast_call() parameters. */
+struct local_bridge {
+       /*! Bridge to join. */
+       struct ast_bridge *join;
+       /*! Channel to swap with when joining bridge. */
+       struct ast_channel *swap;
+       /*! Features that are specific to this channel when pushed into the bridge. */
+       struct ast_bridge_features *features;
+};
+
+/*!
+ * \brief the local pvt structure for all channels
+ *
+ * The local channel pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
+ *
+ * ast_chan owner -> local_pvt -> ast_chan chan
+ */
+struct local_pvt {
+       /*! Unreal channel driver base class values. */
+       struct ast_unreal_pvt base;
+       /*! Additional action arguments */
+       union {
+               /*! Make ;2 join a bridge on ast_call(). */
+               struct local_bridge bridge;
+               /*! Make ;2 masquerade into this channel on ast_call(). */
+               struct ast_channel *masq;
+       } action;
+       /*! What to do with the ;2 channel on ast_call(). */
+       enum local_call_action type;
+       /*! Context to call */
+       char context[AST_MAX_CONTEXT];
+       /*! Extension to call */
+       char exten[AST_MAX_EXTENSION];
+};
+
+struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
+{
+       struct local_pvt *p = ast_channel_tech_pvt(ast);
+       struct local_pvt *found;
+       struct ast_channel *peer;
+
+       if (!p) {
+               return NULL;
+       }
+
+       found = p ? ao2_find(locals, p, 0) : NULL;
+       if (!found) {
+               /* ast is either not a local channel or it has alredy been hungup */
+               return NULL;
+       }
+       ao2_lock(found);
+       if (ast == p->base.owner) {
+               peer = p->base.chan;
+       } else if (ast == p->base.chan) {
+               peer = p->base.owner;
+       } else {
+               peer = NULL;
+       }
+       if (peer) {
+               ast_channel_ref(peer);
+       }
+       ao2_unlock(found);
+       ao2_ref(found, -1);
+       return peer;
+}
+
+/*! \brief Adds devicestate to local channels */
+static int local_devicestate(const char *data)
+{
+       int is_inuse = 0;
+       int res = AST_DEVICE_INVALID;
+       char *exten = ast_strdupa(data);
+       char *context;
+       char *opts;
+       struct local_pvt *lp;
+       struct ao2_iterator it;
+
+       /* Strip options if they exist */
+       opts = strchr(exten, '/');
+       if (opts) {
+               *opts = '\0';
+       }
+
+       context = strchr(exten, '@');
+       if (!context) {
+               ast_log(LOG_WARNING,
+                       "Someone used Local/%s somewhere without a @context. This is bad.\n", data);
+               return AST_DEVICE_INVALID;
+       }
+       *context++ = '\0';
+
+       it = ao2_iterator_init(locals, 0);
+       for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
+               ao2_lock(lp);
+               if (!strcmp(exten, lp->exten)
+                       && !strcmp(context, lp->context)) {
+                       res = AST_DEVICE_NOT_INUSE;
+                       if (lp->base.owner
+                               && ast_test_flag(&lp->base, AST_UNREAL_CARETAKER_THREAD)) {
+                               is_inuse = 1;
+                       }
+               }
+               ao2_unlock(lp);
+               if (is_inuse) {
+                       res = AST_DEVICE_INUSE;
+                       ao2_ref(lp, -1);
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&it);
+
+       if (res == AST_DEVICE_INVALID) {
+               ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
+               if (ast_exists_extension(NULL, context, exten, 1, NULL)) {
+                       res = AST_DEVICE_NOT_INUSE;
+               }
+       }
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Post the LocalBridge AMI event.
+ * \since 12.0.0
+ *
+ * \param p local_pvt to raise the bridge event.
+ *
+ * \return Nothing
+ */
+static void local_bridge_event(struct local_pvt *p)
+{
+       ao2_lock(p);
+       /*** DOCUMENTATION
+               <managerEventInstance>
+                       <synopsis>Raised when two halves of a Local Channel form a bridge.</synopsis>
+                       <syntax>
+                               <parameter name="Channel1">
+                                       <para>The name of the Local Channel half that bridges to another channel.</para>
+                               </parameter>
+                               <parameter name="Channel2">
+                                       <para>The name of the Local Channel half that executes the dialplan.</para>
+                               </parameter>
+                               <parameter name="Context">
+                                       <para>The context in the dialplan that Channel2 starts in.</para>
+                               </parameter>
+                               <parameter name="Exten">
+                                       <para>The extension in the dialplan that Channel2 starts in.</para>
+                               </parameter>
+                               <parameter name="LocalOptimization">
+                                       <enumlist>
+                                               <enum name="Yes"/>
+                                               <enum name="No"/>
+                                       </enumlist>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       ***/
+       manager_event(EVENT_FLAG_CALL, "LocalBridge",
+               "Channel1: %s\r\n"
+               "Channel2: %s\r\n"
+               "Uniqueid1: %s\r\n"
+               "Uniqueid2: %s\r\n"
+               "Context: %s\r\n"
+               "Exten: %s\r\n"
+               "LocalOptimization: %s\r\n",
+               ast_channel_name(p->base.owner), ast_channel_name(p->base.chan),
+               ast_channel_uniqueid(p->base.owner), ast_channel_uniqueid(p->base.chan),
+               p->context, p->exten,
+               ast_test_flag(&p->base, AST_UNREAL_NO_OPTIMIZATION) ? "Yes" : "No");
+       ao2_unlock(p);
+}
+
+int ast_local_setup_bridge(struct ast_channel *ast, struct ast_bridge *bridge, struct ast_channel *swap, struct ast_bridge_features *features)
+{
+       struct local_pvt *p;
+       struct local_pvt *found;
+       int res = -1;
+
+       /* Sanity checks. */
+       if (!ast || !bridge) {
+               ast_bridge_features_destroy(features);
+               return -1;
+       }
+
+       ast_channel_lock(ast);
+       p = ast_channel_tech_pvt(ast);
+       ast_channel_unlock(ast);
+
+       found = p ? ao2_find(locals, p, 0) : NULL;
+       if (found) {
+               ao2_lock(found);
+               if (found->type == LOCAL_CALL_ACTION_DIALPLAN
+                       && found->base.owner
+                       && found->base.chan
+                       && !ast_test_flag(&found->base, AST_UNREAL_CARETAKER_THREAD)) {
+                       ao2_ref(bridge, +1);
+                       if (swap) {
+                               ast_channel_ref(swap);
+                       }
+                       found->type = LOCAL_CALL_ACTION_BRIDGE;
+                       found->action.bridge.join = bridge;
+                       found->action.bridge.swap = swap;
+                       found->action.bridge.features = features;
+                       res = 0;
+               } else {
+                       ast_bridge_features_destroy(features);
+               }
+               ao2_unlock(found);
+               ao2_ref(found, -1);
+       }
+
+       return res;
+}
+
+int ast_local_setup_masquerade(struct ast_channel *ast, struct ast_channel *masq)
+{
+       struct local_pvt *p;
+       struct local_pvt *found;
+       int res = -1;
+
+       /* Sanity checks. */
+       if (!ast || !masq) {
+               return -1;
+       }
+
+       ast_channel_lock(ast);
+       p = ast_channel_tech_pvt(ast);
+       ast_channel_unlock(ast);
+
+       found = p ? ao2_find(locals, p, 0) : NULL;
+       if (found) {
+               ao2_lock(found);
+               if (found->type == LOCAL_CALL_ACTION_DIALPLAN
+                       && found->base.owner
+                       && found->base.chan
+                       && !ast_test_flag(&found->base, AST_UNREAL_CARETAKER_THREAD)) {
+                       ast_channel_ref(masq);
+                       found->type = LOCAL_CALL_ACTION_MASQUERADE;
+                       found->action.masq = masq;
+                       res = 0;
+               }
+               ao2_unlock(found);
+               ao2_ref(found, -1);
+       }
+
+       return res;
+}
+
+/*! \brief Initiate new call, part of PBX interface
+ *         dest is the dial string */
+static int local_call(struct ast_channel *ast, const char *dest, int timeout)
+{
+       struct local_pvt *p = ast_channel_tech_pvt(ast);
+       int pvt_locked = 0;
+
+       struct ast_channel *owner = NULL;
+       struct ast_channel *chan = NULL;
+       int res;
+       char *reduced_dest = ast_strdupa(dest);
+       char *slash;
+       const char *chan_cid;
+
+       if (!p) {
+               return -1;
+       }
+
+       /* since we are letting go of channel locks that were locked coming into
+        * this function, then we need to give the tech pvt a ref */
+       ao2_ref(p, 1);
+       ast_channel_unlock(ast);
+
+       ast_unreal_lock_all(&p->base, &chan, &owner);
+       pvt_locked = 1;
+
+       if (owner != ast) {
+               res = -1;
+               goto return_cleanup;
+       }
+
+       if (!owner || !chan) {
+               res = -1;
+               goto return_cleanup;
+       }
+
+       ast_unreal_call_setup(owner, chan);
+
+       /*
+        * If the local channel has /n on the end of it, we need to lop
+        * that off for our argument to setting up the CC_INTERFACES
+        * variable.
+        */
+       if ((slash = strrchr(reduced_dest, '/'))) {
+               *slash = '\0';
+       }
+       ast_set_cc_interfaces_chanvar(chan, reduced_dest);
+
+       ao2_unlock(p);
+       pvt_locked = 0;
+
+       ast_channel_unlock(owner);
+
+       chan_cid = S_COR(ast_channel_caller(chan)->id.number.valid,
+               ast_channel_caller(chan)->id.number.str, NULL);
+       if (chan_cid) {
+               chan_cid = ast_strdupa(chan_cid);
+       }
+       ast_channel_unlock(chan);
+
+       res = -1;
+       switch (p->type) {
+       case LOCAL_CALL_ACTION_DIALPLAN:
+               if (!ast_exists_extension(NULL, p->context, p->exten, 1, chan_cid)) {
+                       ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n",
+                               p->exten, p->context);
+               } else {
+                       local_bridge_event(p);
+
+                       /* Start switch on sub channel */
+                       res = ast_pbx_start(chan);
+               }
+               break;
+       case LOCAL_CALL_ACTION_BRIDGE:
+               local_bridge_event(p);
+               ast_answer(chan);
+               res = ast_bridge_impart(p->action.bridge.join, chan, p->action.bridge.swap,
+                       p->action.bridge.features, 1);
+               ao2_ref(p->action.bridge.join, -1);
+               p->action.bridge.join = NULL;
+               ao2_cleanup(p->action.bridge.swap);
+               p->action.bridge.swap = NULL;
+               p->action.bridge.features = NULL;
+               break;
+       case LOCAL_CALL_ACTION_MASQUERADE:
+               local_bridge_event(p);
+               ast_answer(chan);
+               res = ast_channel_masquerade(p->action.masq, chan);
+               if (!res) {
+                       ast_do_masquerade(p->action.masq);
+                       /* Chan is now an orphaned zombie.  Destroy it. */
+                       ast_hangup(chan);
+               }
+               p->action.masq = ast_channel_unref(p->action.masq);
+               break;
+       }
+       if (!res) {
+               ao2_lock(p);
+               ast_set_flag(&p->base, AST_UNREAL_CARETAKER_THREAD);
+               ao2_unlock(p);
+       }
+
+       /* we already unlocked them, clear them here so the cleanup label won't touch them. */
+       owner = ast_channel_unref(owner);
+       chan = ast_channel_unref(chan);
+
+return_cleanup:
+       if (p) {
+               if (pvt_locked) {
+                       ao2_unlock(p);
+               }
+               ao2_ref(p, -1);
+       }
+       if (chan) {
+               ast_channel_unlock(chan);
+               ast_channel_unref(chan);
+       }
+
+       /*
+        * owner is supposed to be == to ast, if it is, don't unlock it
+        * because ast must exit locked
+        */
+       if (owner) {
+               if (owner != ast) {
+                       ast_channel_unlock(owner);
+                       ast_channel_lock(ast);
+               }
+               ast_channel_unref(owner);
+       } else {
+               /* we have to exit with ast locked */
+               ast_channel_lock(ast);
+       }
+
+       return res;
+}
+
+/*! \brief Hangup a call through the local proxy channel */
+static int local_hangup(struct ast_channel *ast)
+{
+       struct local_pvt *p = ast_channel_tech_pvt(ast);
+       int res;
+
+       if (!p) {
+               return -1;
+       }
+
+       /* give the pvt a ref to fulfill calling requirements. */
+       ao2_ref(p, +1);
+       res = ast_unreal_hangup(&p->base, ast);
+       if (!res) {
+               int unlink;
+
+               ao2_lock(p);
+               unlink = !p->base.owner && !p->base.chan;
+               ao2_unlock(p);
+               if (unlink) {
+                       ao2_unlink(locals, p);
+               }
+       }
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief struct local_pvt destructor.
+ *
+ * \param vdoomed Object to destroy.
+ *
+ * \return Nothing
+ */
+static void local_pvt_destructor(void *vdoomed)
+{
+       struct local_pvt *doomed = vdoomed;
+
+       switch (doomed->type) {
+       case LOCAL_CALL_ACTION_DIALPLAN:
+               break;
+       case LOCAL_CALL_ACTION_BRIDGE:
+               ao2_cleanup(doomed->action.bridge.join);
+               ao2_cleanup(doomed->action.bridge.swap);
+               ast_bridge_features_destroy(doomed->action.bridge.features);
+               break;
+       case LOCAL_CALL_ACTION_MASQUERADE:
+               ao2_cleanup(doomed->action.masq);
+               break;
+       }
+       ast_unreal_destructor(&doomed->base);
+}
+
+/*! \brief Create a call structure */
+static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
+{
+       struct local_pvt *pvt;
+       char *parse;
+       char *context;
+       char *opts;
+
+       pvt = (struct local_pvt *) ast_unreal_alloc(sizeof(*pvt), local_pvt_destructor, cap);
+       if (!pvt) {
+               return NULL;
+       }
+
+       parse = ast_strdupa(data);
+
+       /*
+        * Local channels intercept MOH by default.
+        *
+        * This is a silly default because it represents state held by
+        * the local channels.  Unless local channel optimization is
+        * disabled, the state will dissapear when the local channels
+        * optimize out.
+        */
+       ast_set_flag(&pvt->base, AST_UNREAL_MOH_INTERCEPT);
+
+       /* Look for options */
+       if ((opts = strchr(parse, '/'))) {
+               *opts++ = '\0';
+               if (strchr(opts, 'n')) {
+                       ast_set_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION);
+               }
+               if (strchr(opts, 'j')) {
+                       if (ast_test_flag(&pvt->base, AST_UNREAL_NO_OPTIMIZATION)) {
+                               ast_set_flag(&pvt->base.jb_conf, AST_JB_ENABLED);
+                       } else {
+                               ast_log(LOG_ERROR, "You must use the 'n' option with the 'j' option to enable the jitter buffer\n");
+                       }
+               }
+               if (strchr(opts, 'm')) {
+                       ast_clear_flag(&pvt->base, AST_UNREAL_MOH_INTERCEPT);
+               }
+       }
+
+       /* Look for a context */
+       if ((context = strchr(parse, '@'))) {
+               *context++ = '\0';
+       }
+
+       ast_copy_string(pvt->context, S_OR(context, "default"), sizeof(pvt->context));
+       ast_copy_string(pvt->exten, parse, sizeof(pvt->exten));
+       snprintf(pvt->base.name, sizeof(pvt->base.name), "%s@%s", pvt->exten, pvt->context);
+
+       return pvt; /* this is returned with a ref */
+}
+
+/*! \brief Part of PBX interface */
+static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
+{
+       struct local_pvt *p;
+       struct ast_channel *chan;
+       struct ast_callid *callid;
+
+       /* Allocate a new private structure and then Asterisk channels */
+       p = local_alloc(data, cap);
+       if (!p) {
+               return NULL;
+       }
+       callid = ast_read_threadstorage_callid();
+       chan = ast_unreal_new_channels(&p->base, &local_tech, AST_STATE_DOWN, AST_STATE_RING,
+               p->exten, p->context, requestor, callid);
+       if (chan) {
+               ao2_link(locals, p);
+       }
+       if (callid) {
+               ast_callid_unref(callid);
+       }
+       ao2_ref(p, -1); /* kill the ref from the alloc */
+
+       return chan;
+}
+
+/*! \brief CLI command "local show channels" */
+static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct local_pvt *p;
+       struct ao2_iterator it;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "local show channels";
+               e->usage =
+                       "Usage: local show channels\n"
+                       "       Provides summary information on active local proxy channels.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 3) {
+               return CLI_SHOWUSAGE;
+       }
+
+       if (ao2_container_count(locals) == 0) {
+               ast_cli(a->fd, "No local channels in use\n");
+               return RESULT_SUCCESS;
+       }
+
+       it = ao2_iterator_init(locals, 0);
+       while ((p = ao2_iterator_next(&it))) {
+               ao2_lock(p);
+               ast_cli(a->fd, "%s -- %s\n",
+                       p->base.owner ? ast_channel_name(p->base.owner) : "<unowned>",
+                       p->base.name);
+               ao2_unlock(p);
+               ao2_ref(p, -1);
+       }
+       ao2_iterator_destroy(&it);
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_local[] = {
+       AST_CLI_DEFINE(locals_show, "List status of local channels"),
+};
+
+static int manager_optimize_away(struct mansession *s, const struct message *m)
+{
+       const char *channel;
+       struct local_pvt *p;
+       struct local_pvt *found;
+       struct ast_channel *chan;
+
+       channel = astman_get_header(m, "Channel");
+       if (ast_strlen_zero(channel)) {
+               astman_send_error(s, m, "'Channel' not specified.");
+               return 0;
+       }
+
+       chan = ast_channel_get_by_name(channel);
+       if (!chan) {
+               astman_send_error(s, m, "Channel does not exist.");
+               return 0;
+       }
+
+       p = ast_channel_tech_pvt(chan);
+       ast_channel_unref(chan);
+
+       found = p ? ao2_find(locals, p, 0) : NULL;
+       if (found) {
+               ao2_lock(found);
+               ast_clear_flag(&found->base, AST_UNREAL_NO_OPTIMIZATION);
+               ao2_unlock(found);
+               ao2_ref(found, -1);
+               astman_send_ack(s, m, "Queued channel to be optimized away");
+       } else {
+               astman_send_error(s, m, "Unable to find channel");
+       }
+
+       return 0;
+}
+
+
+static int locals_cmp_cb(void *obj, void *arg, int flags)
+{
+       return (obj == arg) ? CMP_MATCH : 0;
+}
+
+/*!
+ * \internal
+ * \brief Shutdown the local proxy channel.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void local_shutdown(void)
+{
+       struct local_pvt *p;
+       struct ao2_iterator it;
+
+       /* First, take us out of the channel loop */
+       ast_cli_unregister_multiple(cli_local, ARRAY_LEN(cli_local));
+       ast_manager_unregister("LocalOptimizeAway");
+       ast_channel_unregister(&local_tech);
+
+       it = ao2_iterator_init(locals, 0);
+       while ((p = ao2_iterator_next(&it))) {
+               if (p->base.owner) {
+                       ast_softhangup(p->base.owner, AST_SOFTHANGUP_APPUNLOAD);
+               }
+               ao2_ref(p, -1);
+       }
+       ao2_iterator_destroy(&it);
+       ao2_ref(locals, -1);
+       locals = NULL;
+
+       ast_format_cap_destroy(local_tech.capabilities);
+}
+
+int ast_local_init(void)
+{
+       if (!(local_tech.capabilities = ast_format_cap_alloc())) {
+               return -1;
+       }
+       ast_format_cap_add_all(local_tech.capabilities);
+
+       locals = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, locals_cmp_cb);
+       if (!locals) {
+               ast_format_cap_destroy(local_tech.capabilities);
+               return -1;
+       }
+
+       /* Make sure we can register our channel type */
+       if (ast_channel_register(&local_tech)) {
+               ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
+               ao2_ref(locals, -1);
+               ast_format_cap_destroy(local_tech.capabilities);
+               return -1;
+       }
+       ast_cli_register_multiple(cli_local, ARRAY_LEN(cli_local));
+       ast_manager_register_xml_core("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
+
+       ast_register_atexit(local_shutdown);
+       return 0;
+}
diff --git a/main/core_unreal.c b/main/core_unreal.c
new file mode 100644 (file)
index 0000000..d5e5881
--- /dev/null
@@ -0,0 +1,855 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Unreal channel derivatives framework for channel drivers like local channels.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/causes.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/musiconhold.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/core_unreal.h"
+
+static unsigned int name_sequence = 0;
+
+void ast_unreal_lock_all(struct ast_unreal_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
+{
+       struct ast_channel *chan = NULL;
+       struct ast_channel *owner = NULL;
+
+       ao2_lock(p);
+       for (;;) {
+               if (p->chan) {
+                       chan = p->chan;
+                       ast_channel_ref(chan);
+               }
+               if (p->owner) {
+                       owner = p->owner;
+                       ast_channel_ref(owner);
+               }
+               ao2_unlock(p);
+
+               /* if we don't have both channels, then this is very easy */
+               if (!owner || !chan) {
+                       if (owner) {
+                               ast_channel_lock(owner);
+                       } else if(chan) {
+                               ast_channel_lock(chan);
+                       }
+               } else {
+                       /* lock both channels first, then get the pvt lock */
+                       ast_channel_lock_both(chan, owner);
+               }
+               ao2_lock(p);
+
+               /* Now that we have all the locks, validate that nothing changed */
+               if (p->owner != owner || p->chan != chan) {
+                       if (owner) {
+                               ast_channel_unlock(owner);
+                               owner = ast_channel_unref(owner);
+                       }
+                       if (chan) {
+                               ast_channel_unlock(chan);
+                               chan = ast_channel_unref(chan);
+                       }
+                       continue;
+               }
+
+               break;
+       }
+       *outowner = p->owner;
+       *outchan = p->chan;
+}
+
+/* Called with ast locked */
+int ast_unreal_setoption(struct ast_channel *ast, int option, void *data, int datalen)
+{
+       int res = 0;
+       struct ast_unreal_pvt *p;
+       struct ast_channel *otherchan = NULL;
+       ast_chan_write_info_t *write_info;
+
+       if (option != AST_OPTION_CHANNEL_WRITE) {
+               return -1;
+       }
+
+       write_info = data;
+
+       if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
+               ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
+               return -1;
+       }
+
+       if (!strcmp(write_info->function, "CHANNEL")
+               && !strncasecmp(write_info->data, "hangup_handler_", 15)) {
+               /* Block CHANNEL(hangup_handler_xxx) writes to the other unreal channel. */
+               return 0;
+       }
+
+       /* get the tech pvt */
+       if (!(p = ast_channel_tech_pvt(ast))) {
+               return -1;
+       }
+       ao2_ref(p, 1);
+       ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
+
+       /* get the channel we are supposed to write to */
+       ao2_lock(p);
+       otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
+       if (!otherchan || otherchan == write_info->chan) {
+               res = -1;
+               otherchan = NULL;
+               ao2_unlock(p);
+               goto setoption_cleanup;
+       }
+       ast_channel_ref(otherchan);
+
+       /* clear the pvt lock before grabbing the channel */
+       ao2_unlock(p);
+
+       ast_channel_lock(otherchan);
+       res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
+       ast_channel_unlock(otherchan);
+
+setoption_cleanup:
+       ao2_ref(p, -1);
+       if (otherchan) {
+               ast_channel_unref(otherchan);
+       }
+       ast_channel_lock(ast); /* Lock back before we leave */
+       return res;
+}
+
+/* Called with ast locked */
+int ast_unreal_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
+{
+       struct ast_unreal_pvt *p;
+       struct ast_channel *peer;
+       struct ast_channel *other;
+       int res = 0;
+
+       if (option != AST_OPTION_T38_STATE) {
+               /* AST_OPTION_T38_STATE is the only supported option at this time */
+               return -1;
+       }
+
+       /* for some reason the channel is not locked in channel.c when this function is called */
+       if (!(p = ast_channel_tech_pvt(ast))) {
+               return -1;
+       }
+
+       ao2_lock(p);
+       other = AST_UNREAL_IS_OUTBOUND(ast, p) ? p->owner : p->chan;
+       if (!other) {
+               ao2_unlock(p);
+               return -1;
+       }
+       ast_channel_ref(other);
+       ao2_unlock(p);
+       ast_channel_unlock(ast); /* Held when called, unlock before locking another channel */
+
+       peer = ast_channel_bridge_peer(other);
+       if (peer) {
+               res = ast_channel_queryoption(peer, option, data, datalen, 0);
+               ast_channel_unref(peer);
+       }
+       ast_channel_unref(other);
+       ast_channel_lock(ast); /* Lock back before we leave */
+
+       return res;
+}
+
+/*!
+ * \brief queue a frame onto either the p->owner or p->chan
+ *
+ * \note the ast_unreal_pvt MUST have it's ref count bumped before entering this function and
+ * decremented after this function is called.  This is a side effect of the deadlock
+ * avoidance that is necessary to lock 2 channels and a tech_pvt.  Without a ref counted
+ * ast_unreal_pvt, it is impossible to guarantee it will not be destroyed by another thread
+ * during deadlock avoidance.
+ */
+static int unreal_queue_frame(struct ast_unreal_pvt *p, int isoutbound, struct ast_frame *f,
+       struct ast_channel *us, int us_locked)
+{
+       struct ast_channel *other;
+
+       /* Recalculate outbound channel */
+       other = isoutbound ? p->owner : p->chan;
+       if (!other) {
+               return 0;
+       }
+
+       /* do not queue frame if generator is on both unreal channels */
+       if (us && ast_channel_generator(us) && ast_channel_generator(other)) {
+               return 0;
+       }
+
+       /* grab a ref on the channel before unlocking the pvt,
+        * other can not go away from us now regardless of locking */
+       ast_channel_ref(other);
+       if (us && us_locked) {
+               ast_channel_unlock(us);
+       }
+       ao2_unlock(p);
+
+       if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
+               ast_setstate(other, AST_STATE_RINGING);
+       }
+       ast_queue_frame(other, f);
+
+       other = ast_channel_unref(other);
+       if (us && us_locked) {
+               ast_channel_lock(us);
+       }
+       ao2_lock(p);
+
+       return 0;
+}
+
+int ast_unreal_answer(struct ast_channel *ast)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int isoutbound;
+       int res = -1;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_ref(p, 1);
+       ao2_lock(p);
+       isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+       if (isoutbound) {
+               /* Pass along answer since somebody answered us */
+               struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
+
+               res = unreal_queue_frame(p, isoutbound, &answer, ast, 1);
+       } else {
+               ast_log(LOG_WARNING, "Huh?  %s is being asked to answer?\n",
+                       ast_channel_name(ast));
+       }
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Check and optimize out the unreal channels between bridges.
+ * \since 12.0.0
+ *
+ * \param ast Channel writing a frame into the unreal channels.
+ * \param p Unreal channel private.
+ *
+ * \note It is assumed that ast is locked.
+ * \note It is assumed that p is locked.
+ *
+ * \retval 0 if unreal channels were not optimized out.
+ * \retval non-zero if unreal channels were optimized out.
+ */
+static int got_optimized_out(struct ast_channel *ast, struct ast_unreal_pvt *p)
+{
+       /* Do a few conditional checks early on just to see if this optimization is possible */
+       if (ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION) || !p->chan || !p->owner) {
+               return 0;
+       }
+       if (ast == p->owner) {
+               return ast_bridge_unreal_optimized_out(p->owner, p->chan);
+       }
+       if (ast == p->chan) {
+               return ast_bridge_unreal_optimized_out(p->chan, p->owner);
+       }
+       /* ast is not valid to optimize. */
+       return 0;
+}
+
+struct ast_frame  *ast_unreal_read(struct ast_channel *ast)
+{
+       return &ast_null_frame;
+}
+
+int ast_unreal_write(struct ast_channel *ast, struct ast_frame *f)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res = -1;
+
+       if (!p) {
+               return -1;
+       }
+
+       /* Just queue for delivery to the other side */
+       ao2_ref(p, 1);
+       ao2_lock(p);
+       switch (f->frametype) {
+       case AST_FRAME_VOICE:
+       case AST_FRAME_VIDEO:
+               if (got_optimized_out(ast, p)) {
+                       break;
+               }
+               /* fall through */
+       default:
+               res = unreal_queue_frame(p, AST_UNREAL_IS_OUTBOUND(ast, p), f, ast, 1);
+               break;
+       }
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+int ast_unreal_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(newchan);
+       struct ast_bridge *bridge_owner;
+       struct ast_bridge *bridge_chan;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_lock(p);
+
+       if ((p->owner != oldchan) && (p->chan != oldchan)) {
+               ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
+               ao2_unlock(p);
+               return -1;
+       }
+       if (p->owner == oldchan) {
+               p->owner = newchan;
+       } else {
+               p->chan = newchan;
+       }
+
+       if (ast_check_hangup(newchan) || !p->owner || !p->chan) {
+               ao2_unlock(p);
+               return 0;
+       }
+
+       /* Do not let a masquerade cause an unreal channel to be bridged to itself! */
+       bridge_owner = ast_channel_internal_bridge(p->owner);
+       bridge_chan = ast_channel_internal_bridge(p->chan);
+       if (bridge_owner && bridge_owner == bridge_chan) {
+               ast_log(LOG_WARNING, "You can not bridge an unreal channel (%s) to itself!\n",
+                       ast_channel_name(newchan));
+               ao2_unlock(p);
+               ast_queue_hangup(newchan);
+               return -1;
+       }
+
+       ao2_unlock(p);
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Queue up a frame representing the indication as a control frame.
+ * \since 12.0.0
+ *
+ * \param p Unreal private structure.
+ * \param ast Channel indicating the condition.
+ * \param condition What is being indicated.
+ * \param data Extra data.
+ * \param datalen Length of extra data.
+ *
+ * \retval 0 on success.
+ * \retval AST_T38_REQUEST_PARMS if successful and condition is AST_CONTROL_T38_PARAMETERS.
+ * \retval -1 on error.
+ */
+static int unreal_queue_indicate(struct ast_unreal_pvt *p, struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+       int res = 0;
+       int isoutbound;
+
+       ao2_lock(p);
+       /*
+        * Block -1 stop tones events if we are to be optimized out.  We
+        * don't need a flurry of these events on an unreal channel chain
+        * when initially connected to slow the optimization process.
+        */
+       if (0 <= condition || ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION)) {
+               struct ast_frame f = {
+                       .frametype = AST_FRAME_CONTROL,
+                       .subclass.integer = condition,
+                       .data.ptr = (void *) data,
+                       .datalen = datalen,
+               };
+
+               isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+               res = unreal_queue_frame(p, isoutbound, &f, ast, 1);
+               if (!res
+                       && condition == AST_CONTROL_T38_PARAMETERS
+                       && datalen == sizeof(struct ast_control_t38_parameters)) {
+                       const struct ast_control_t38_parameters *parameters = data;
+
+                       if (parameters->request_response == AST_T38_REQUEST_PARMS) {
+                               res = AST_T38_REQUEST_PARMS;
+                       }
+               }
+       } else {
+               ast_debug(4, "Blocked indication %d\n", condition);
+       }
+       ao2_unlock(p);
+
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Handle COLP and redirecting conditions.
+ * \since 12.0.0
+ *
+ * \param p Unreal private structure.
+ * \param ast Channel indicating the condition.
+ * \param condition What is being indicated.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int unreal_colp_redirect_indicate(struct ast_unreal_pvt *p, struct ast_channel *ast, int condition)
+{
+       struct ast_channel *this_channel;
+       struct ast_channel *the_other_channel;
+       int isoutbound;
+       int res = 0;
+
+       /*
+        * A connected line update frame may only contain a partial
+        * amount of data, such as just a source, or just a ton, and not
+        * the full amount of information.  However, the collected
+        * information is all stored in the outgoing channel's
+        * connectedline structure, so when receiving a connected line
+        * update on an outgoing unreal channel, we need to transmit the
+        * collected connected line information instead of whatever
+        * happens to be in this control frame.  The same applies for
+        * redirecting information, which is why it is handled here as
+        * well.
+        */
+       ao2_lock(p);
+       isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+       if (isoutbound) {
+               this_channel = p->chan;
+               the_other_channel = p->owner;
+       } else {
+               this_channel = p->owner;
+               the_other_channel = p->chan;
+       }
+       if (the_other_channel) {
+               unsigned char frame_data[1024];
+               struct ast_frame f = {
+                       .frametype = AST_FRAME_CONTROL,
+                       .subclass.integer = condition,
+                       .data.ptr = frame_data,
+               };
+
+               if (condition == AST_CONTROL_CONNECTED_LINE) {
+                       ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel),
+                               ast_channel_connected(this_channel));
+                       f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data),
+                               ast_channel_connected(this_channel), NULL);
+               } else {
+                       f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data),
+                               ast_channel_redirecting(this_channel), NULL);
+               }
+               res = unreal_queue_frame(p, isoutbound, &f, ast, 1);
+       }
+       ao2_unlock(p);
+
+       return res;
+}
+
+int ast_unreal_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res = 0;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_ref(p, 1); /* ref for unreal_queue_frame */
+
+       switch (condition) {
+       case AST_CONTROL_CONNECTED_LINE:
+       case AST_CONTROL_REDIRECTING:
+               res = unreal_colp_redirect_indicate(p, ast, condition);
+               break;
+       case AST_CONTROL_HOLD:
+               if (ast_test_flag(p, AST_UNREAL_MOH_INTERCEPT)) {
+                       ast_moh_start(ast, data, NULL);
+                       break;
+               }
+               res = unreal_queue_indicate(p, ast, condition, data, datalen);
+               break;
+       case AST_CONTROL_UNHOLD:
+               if (ast_test_flag(p, AST_UNREAL_MOH_INTERCEPT)) {
+                       ast_moh_stop(ast);
+                       break;
+               }
+               res = unreal_queue_indicate(p, ast, condition, data, datalen);
+               break;
+       default:
+               res = unreal_queue_indicate(p, ast, condition, data, datalen);
+               break;
+       }
+
+       ao2_ref(p, -1);
+       return res;
+}
+
+int ast_unreal_digit_begin(struct ast_channel *ast, char digit)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res = -1;
+       struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
+       int isoutbound;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_ref(p, 1); /* ref for unreal_queue_frame */
+       ao2_lock(p);
+       isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+       f.subclass.integer = digit;
+       res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+int ast_unreal_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res = -1;
+       struct ast_frame f = { AST_FRAME_DTMF_END, };
+       int isoutbound;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_ref(p, 1); /* ref for unreal_queue_frame */
+       ao2_lock(p);
+       isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+       f.subclass.integer = digit;
+       f.len = duration;
+       res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+int ast_unreal_sendtext(struct ast_channel *ast, const char *text)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res = -1;
+       struct ast_frame f = { AST_FRAME_TEXT, };
+       int isoutbound;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_ref(p, 1); /* ref for unreal_queue_frame */
+       ao2_lock(p);
+       isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+       f.data.ptr = (char *) text;
+       f.datalen = strlen(text) + 1;
+       res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+       return res;
+}
+
+int ast_unreal_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
+{
+       struct ast_unreal_pvt *p = ast_channel_tech_pvt(ast);
+       int res = -1;
+       struct ast_frame f = { AST_FRAME_HTML, };
+       int isoutbound;
+
+       if (!p) {
+               return -1;
+       }
+
+       ao2_ref(p, 1); /* ref for unreal_queue_frame */
+       ao2_lock(p);
+       isoutbound = AST_UNREAL_IS_OUTBOUND(ast, p);
+       f.subclass.integer = subclass;
+       f.data.ptr = (char *)data;
+       f.datalen = datalen;
+       res = unreal_queue_frame(p, isoutbound, &f, ast, 0);
+       ao2_unlock(p);
+       ao2_ref(p, -1);
+
+       return res;
+}
+
+void ast_unreal_call_setup(struct ast_channel *semi1, struct ast_channel *semi2)
+{
+       struct ast_var_t *varptr;
+       struct ast_var_t *clone_var;
+
+       /*
+        * Note that cid_num and cid_name aren't passed in the
+        * ast_channel_alloc calls in ast_unreal_new_channels().  It's
+        * done here instead.
+        */
+       ast_party_redirecting_copy(ast_channel_redirecting(semi2), ast_channel_redirecting(semi1));
+
+       ast_party_dialed_copy(ast_channel_dialed(semi2), ast_channel_dialed(semi1));
+
+       ast_connected_line_copy_to_caller(ast_channel_caller(semi2), ast_channel_connected(semi1));
+       ast_connected_line_copy_from_caller(ast_channel_connected(semi2), ast_channel_caller(semi1));
+
+       ast_channel_language_set(semi2, ast_channel_language(semi1));
+       ast_channel_accountcode_set(semi2, ast_channel_accountcode(semi1));
+       ast_channel_musicclass_set(semi2, ast_channel_musicclass(semi1));
+
+       ast_channel_cc_params_init(semi2, ast_channel_get_cc_config_params(semi1));
+
+       /*
+        * Make sure we inherit the AST_CAUSE_ANSWERED_ELSEWHERE if it's
+        * set on the queue/dial call request in the dialplan.
+        */
+       if (ast_channel_hangupcause(semi1) == AST_CAUSE_ANSWERED_ELSEWHERE) {
+               ast_channel_hangupcause_set(semi2, AST_CAUSE_ANSWERED_ELSEWHERE);
+       }
+
+       /*
+        * Copy the channel variables from the semi1 channel to the
+        * outgoing channel.
+        *
+        * Note that due to certain assumptions, they MUST be in the
+        * same order.
+        */
+       AST_LIST_TRAVERSE(ast_channel_varshead(semi1), varptr, entries) {
+               clone_var = ast_var_assign(varptr->name, varptr->value);
+               if (clone_var) {
+                       AST_LIST_INSERT_TAIL(ast_channel_varshead(semi2), clone_var, entries);
+               }
+       }
+       ast_channel_datastore_inherit(semi1, semi2);
+}
+
+int ast_unreal_hangup(struct ast_unreal_pvt *p, struct ast_channel *ast)
+{
+       int hangup_chan = 0;
+       int res = 0;
+       int cause;
+       struct ast_channel *owner = NULL;
+       struct ast_channel *chan = NULL;
+
+       /* the pvt isn't going anywhere, it has a ref */
+       ast_channel_unlock(ast);
+
+       /* lock everything */
+       ast_unreal_lock_all(p, &chan, &owner);
+
+       if (ast != chan && ast != owner) {
+               res = -1;
+               goto unreal_hangup_cleanup;
+       }
+
+       cause = ast_channel_hangupcause(ast);
+
+       if (ast == p->chan) {
+               /* Outgoing side is hanging up. */
+               ast_clear_flag(p, AST_UNREAL_CARETAKER_THREAD);
+               p->chan = NULL;
+               if (p->owner) {
+                       const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
+
+                       if (status) {
+                               ast_channel_hangupcause_set(p->owner, cause);
+                               pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
+                       }
+                       ast_queue_hangup_with_cause(p->owner, cause);
+               }
+       } else {
+               /* Owner side is hanging up. */
+               p->owner = NULL;
+               if (p->chan) {
+                       if (cause == AST_CAUSE_ANSWERED_ELSEWHERE) {
+                               ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
+                               ast_debug(2, "%s has AST_CAUSE_ANSWERED_ELSEWHERE set.\n",
+                                       ast_channel_name(p->chan));
+                       }
+                       if (!ast_test_flag(p, AST_UNREAL_CARETAKER_THREAD)) {
+                               /*
+                                * Need to actually hangup p->chan since nothing else is taking
+                                * care of it.
+                                */
+                               hangup_chan = 1;
+                       } else {
+                               ast_queue_hangup_with_cause(p->chan, cause);
+                       }
+               }
+       }
+
+       /* this is one of our locked channels, doesn't matter which */
+       ast_channel_tech_pvt_set(ast, NULL);
+       ao2_ref(p, -1);
+
+unreal_hangup_cleanup:
+       ao2_unlock(p);
+       if (owner) {
+               ast_channel_unlock(owner);
+               ast_channel_unref(owner);
+       }
+       if (chan) {
+               ast_channel_unlock(chan);
+               if (hangup_chan) {
+                       ast_hangup(chan);
+               }
+               ast_channel_unref(chan);
+       }
+
+       /* leave with the channel locked that came in */
+       ast_channel_lock(ast);
+
+       return res;
+}
+
+void ast_unreal_destructor(void *vdoomed)
+{
+       struct ast_unreal_pvt *doomed = vdoomed;
+
+       doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
+}
+
+struct ast_unreal_pvt *ast_unreal_alloc(size_t size, ao2_destructor_fn destructor, struct ast_format_cap *cap)
+{
+       struct ast_unreal_pvt *unreal;
+
+       static const struct ast_jb_conf jb_conf = {
+               .flags = 0,
+               .max_size = -1,
+               .resync_threshold = -1,
+               .impl = "",
+               .target_extra = -1,
+       };
+
+       unreal = ao2_alloc(size, destructor);
+       if (!unreal) {
+               return NULL;
+       }
+       unreal->reqcap = ast_format_cap_dup(cap);
+       if (!unreal->reqcap) {
+               ao2_ref(unreal, -1);
+               return NULL;
+       }
+
+       memcpy(&unreal->jb_conf, &jb_conf, sizeof(unreal->jb_conf));
+
+       return unreal;
+}
+
+struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
+       const struct ast_channel_tech *tech, int semi1_state, int semi2_state,
+       const char *exten, const char *context, const struct ast_channel *requestor,
+       struct ast_callid *callid)
+{
+       struct ast_channel *owner;
+       struct ast_channel *chan;
+       const char *linkedid = requestor ? ast_channel_linkedid(requestor) : NULL;
+       struct ast_format fmt;
+       int generated_seqno = ast_atomic_fetchadd_int((int *) &name_sequence, +1);
+
+       /*
+        * Allocate two new Asterisk channels
+        *
+        * Make sure that the ;2 channel gets the same linkedid as ;1.
+        * You can't pass linkedid to both allocations since if linkedid
+        * isn't set, then each channel will generate its own linkedid.
+        */
+       if (!(owner = ast_channel_alloc(1, semi1_state, NULL, NULL, NULL,
+                       exten, context, linkedid, 0,
+                       "%s/%s-%08x;1", tech->type, p->name, generated_seqno))
+               || !(chan = ast_channel_alloc(1, semi2_state, NULL, NULL, NULL,
+                       exten, context, ast_channel_linkedid(owner), 0,
+                       "%s/%s-%08x;2", tech->type, p->name, generated_seqno))) {
+               if (owner) {
+                       owner = ast_channel_release(owner);
+               }
+               ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
+               return NULL;
+       }
+
+       if (callid) {
+               ast_channel_callid_set(owner, callid);
+               ast_channel_callid_set(chan, callid);
+       }
+
+       ast_channel_tech_set(owner, tech);
+       ast_channel_tech_set(chan, tech);
+       ast_channel_tech_pvt_set(owner, p);
+       ast_channel_tech_pvt_set(chan, p);
+
+       ast_format_cap_copy(ast_channel_nativeformats(owner), p->reqcap);
+       ast_format_cap_copy(ast_channel_nativeformats(chan), p->reqcap);
+
+       /* Determine our read/write format and set it on each channel */
+       ast_best_codec(p->reqcap, &fmt);
+       ast_format_copy(ast_channel_writeformat(owner), &fmt);
+       ast_format_copy(ast_channel_writeformat(chan), &fmt);
+       ast_format_copy(ast_channel_rawwriteformat(owner), &fmt);
+       ast_format_copy(ast_channel_rawwriteformat(chan), &fmt);
+       ast_format_copy(ast_channel_readformat(owner), &fmt);
+       ast_format_copy(ast_channel_readformat(chan), &fmt);
+       ast_format_copy(ast_channel_rawreadformat(owner), &fmt);
+       ast_format_copy(ast_channel_rawreadformat(chan), &fmt);
+
+       ast_set_flag(ast_channel_flags(owner), AST_FLAG_DISABLE_DEVSTATE_CACHE);
+       ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE);
+
+       ast_jb_configure(owner, &p->jb_conf);
+
+       if (ast_channel_cc_params_init(owner, requestor
+               ? ast_channel_get_cc_config_params((struct ast_channel *) requestor) : NULL)) {
+               ast_channel_release(owner);
+               ast_channel_release(chan);
+               return NULL;
+       }
+
+       /* Give the private a ref for each channel. */
+       ao2_ref(p, +2);
+       p->owner = owner;
+       p->chan = chan;
+
+       return owner;
+}
index b6cc1916497b41cabb4b0baa52d477a30a427511..be872a80d64ce4769f1f4f3f8ed0f8be7e598ab5 100644 (file)
@@ -72,6 +72,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/astobj2.h"
 #include "asterisk/cel.h"
 #include "asterisk/test.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
 
 /*
  * Party A - transferee
@@ -248,129 +250,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        </variablelist>
                </description>
        </application>
-       <application name="ParkedCall" language="en_US">
-               <synopsis>
-                       Retrieve a parked call.
-               </synopsis>
-               <syntax>
-                       <parameter name="exten">
-                               <para>Parking space extension to retrieve a parked call.
-                               If not provided then the first available parked call in the
-                               parking lot will be retrieved.</para>
-                       </parameter>
-                       <parameter name="parking_lot_name">
-                               <para>Specify from which parking lot to retrieve a parked call.</para>
-                               <para>The parking lot used is selected in the following order:</para>
-                               <para>1) parking_lot_name option</para>
-                               <para>2) <variable>PARKINGLOT</variable> variable</para>
-                               <para>3) <literal>CHANNEL(parkinglot)</literal> function
-                               (Possibly preset by the channel driver.)</para>
-                               <para>4) Default parking lot.</para>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>Used to retrieve a parked call from a parking lot.</para>
-                       <note>
-                               <para>Parking lots automatically create and manage dialplan extensions in
-                               the parking lot context.  You do not need to explicitly use this
-                               application in your dialplan.  Instead, all you should do is include the
-                               parking lot context in your dialplan.</para>
-                       </note>
-               </description>
-               <see-also>
-                       <ref type="application">Park</ref>
-                       <ref type="application">ParkAndAnnounce</ref>
-               </see-also>
-       </application>
-       <application name="Park" language="en_US">
-               <synopsis>
-                       Park yourself.
-               </synopsis>
-               <syntax>
-                       <parameter name="timeout">
-                               <para>A custom parking timeout for this parked call. Value in milliseconds.</para>
-                       </parameter>
-                       <parameter name="return_context">
-                               <para>The context to return the call to after it times out.</para>
-                       </parameter>
-                       <parameter name="return_exten">
-                               <para>The extension to return the call to after it times out.</para>
-                       </parameter>
-                       <parameter name="return_priority">
-                               <para>The priority to return the call to after it times out.</para>
-                       </parameter>
-                       <parameter name="options">
-                               <para>A list of options for this parked call.</para>
-                               <optionlist>
-                                       <option name="r">
-                                               <para>Send ringing instead of MOH to the parked call.</para>
-                                       </option>
-                                       <option name="R">
-                                               <para>Randomize the selection of a parking space.</para>
-                                       </option>
-                                       <option name="s">
-                                               <para>Silence announcement of the parking space number.</para>
-                                       </option>
-                               </optionlist>
-                       </parameter>
-                       <parameter name="parking_lot_name">
-                               <para>Specify in which parking lot to park a call.</para>
-                               <para>The parking lot used is selected in the following order:</para>
-                               <para>1) parking_lot_name option</para>
-                               <para>2) <variable>PARKINGLOT</variable> variable</para>
-                               <para>3) <literal>CHANNEL(parkinglot)</literal> function
-                               (Possibly preset by the channel driver.)</para>
-                               <para>4) Default parking lot.</para>
-                       </parameter>
-               </syntax>
-               <description>
-                       <para>Used to park yourself (typically in combination with a supervised
-                       transfer to know the parking space).</para>
-                       <para>If you set the <variable>PARKINGEXTEN</variable> variable to a
-                       parking space extension in the parking lot, Park() will attempt to park the call
-                       on that extension.  If the extension is already is in use then execution
-                       will continue at the next priority.</para>
-                       <para>If the <literal>parkeddynamic</literal> option is enabled in <filename>features.conf</filename>
-                       the following variables can be used to dynamically create new parking lots.</para>
-                       <para>If you set the <variable>PARKINGDYNAMIC</variable> variable and this parking lot
-                       exists then it will be used as a template for the newly created dynamic lot.  Otherwise,
-                       the default parking lot will be used.</para>
-                       <para>If you set the <variable>PARKINGDYNCONTEXT</variable> variable then the newly created dynamic
-                       parking lot will use this context.</para>
-                       <para>If you set the <variable>PARKINGDYNEXTEN</variable> variable then the newly created dynamic
-                       parking lot will use this extension to access the parking lot.</para>
-                       <para>If you set the <variable>PARKINGDYNPOS</variable> variable then the newly created dynamic parking lot
-                       will use those parking postitions.</para>
-                       <note>
-                               <para>This application must be used as the first extension priority
-                               to be recognized as a parking access extension.  DTMF transfers
-                               and some channel drivers need this distinction to operate properly.
-                               The parking access extension in this case is treated like a dialplan
-                               hint.</para>
-                       </note>
-                       <note>
-                               <para>Parking lots automatically create and manage dialplan extensions in
-                               the parking lot context.  You do not need to explicitly use this
-                               application in your dialplan.  Instead, all you should do is include the
-                               parking lot context in your dialplan.</para>
-                       </note>
-               </description>
-               <see-also>
-                       <ref type="application">ParkAndAnnounce</ref>
-                       <ref type="application">ParkedCall</ref>
-               </see-also>
-       </application>
-       <manager name="ParkedCalls" language="en_US">
-               <synopsis>
-                       List parked calls.
-               </synopsis>
-               <syntax>
-                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-               </syntax>
-               <description>
-                       <para>List parked calls.</para>
-               </description>
-       </manager>
        <manager name="Park" language="en_US">
                <synopsis>
                        Park a channel.
@@ -418,17 +297,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>Bridge together two channels already in the PBX.</para>
                </description>
        </manager>
-       <manager name="Parkinglots" language="en_US">
-               <synopsis>
-                       Get a list of parking lots
-               </synopsis>
-               <syntax>
-                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-               </syntax>
-               <description>
-                       <para>List all parking lots as a series of AMI events</para>
-               </description>
-       </manager>
        <function name="FEATURE" language="en_US">
                <synopsis>
                        Get or set a feature option on a channel.
@@ -538,6 +406,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define AST_MAX_WATCHERS 256
 #define MAX_DIAL_FEATURE_OPTIONS 30
 
+/* TODO Scrape all of the parking stuff out of features.c */
+
 struct feature_group_exten {
        AST_LIST_ENTRY(feature_group_exten) entry;
        AST_DECLARE_STRING_FIELDS(
@@ -1134,65 +1004,40 @@ static void *bridge_call_thread(void *data)
                ast_channel_data_set(tobj->peer, "(Empty)");
        }
 
-       ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
-
        if (tobj->return_to_pbx) {
-               if (!ast_check_hangup(tobj->peer)) {
-                       ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", ast_channel_name(tobj->peer));
-                       if (ast_pbx_start(tobj->peer)) {
-                               ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", ast_channel_name(tobj->peer));
-                               ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer);
-                       }
-               } else {
-                       ast_autoservice_chan_hangup_peer(tobj->chan, tobj->peer);
-               }
-               if (!ast_check_hangup(tobj->chan)) {
-                       ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", ast_channel_name(tobj->chan));
-                       if (ast_pbx_start(tobj->chan)) {
-                               ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", ast_channel_name(tobj->chan));
-                               ast_hangup(tobj->chan);
-                       }
-               } else {
-                       ast_hangup(tobj->chan);
-               }
-       } else {
-               ast_hangup(tobj->chan);
-               ast_hangup(tobj->peer);
+               ast_after_bridge_set_goto(tobj->chan, ast_channel_context(tobj->chan),
+                       ast_channel_exten(tobj->chan), ast_channel_priority(tobj->chan));
+               ast_after_bridge_set_goto(tobj->peer, ast_channel_context(tobj->peer),
+                       ast_channel_exten(tobj->peer), ast_channel_priority(tobj->peer));
        }
 
+       ast_bridge_call(tobj->chan, tobj->peer, &tobj->bconfig);
+
+       ast_after_bridge_goto_run(tobj->chan);
+
        ast_free(tobj);
 
        return NULL;
 }
 
 /*!
- * \brief create thread for the parked call
- * \param data
- *
- * Create thread and attributes, call bridge_call_thread
+ * \brief create thread for the bridging call
+ * \param tobj
  */
-static void bridge_call_thread_launch(struct ast_bridge_thread_obj *data)
+static void bridge_call_thread_launch(struct ast_bridge_thread_obj *tobj)
 {
        pthread_t thread;
-       pthread_attr_t attr;
-       struct sched_param sched;
 
        /* This needs to be unreffed once it has been associated with the new thread. */
-       data->callid = ast_read_threadstorage_callid();
-
-       pthread_attr_init(&attr);
-       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-       if (ast_pthread_create(&thread, &attr, bridge_call_thread, data)) {
-               /* Failed to create thread. Ditch the reference to callid. */
-               ast_callid_unref(data->callid);
-               ast_hangup(data->chan);
-               ast_hangup(data->peer);
+       tobj->callid = ast_read_threadstorage_callid();
+
+       if (ast_pthread_create_detached(&thread, NULL, bridge_call_thread, tobj)) {
                ast_log(LOG_ERROR, "Failed to create bridge_call_thread.\n");
-               return;
+               ast_callid_unref(tobj->callid);
+               ast_hangup(tobj->chan);
+               ast_hangup(tobj->peer);
+               ast_free(tobj);
        }
-       pthread_attr_destroy(&attr);
-       memset(&sched, 0, sizeof(sched));
-       pthread_setschedparam(thread, SCHED_RR, &sched);
 }
 
 /*!
@@ -2583,11 +2428,6 @@ static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *p
        if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) {
                ast_log(LOG_WARNING, "Async goto failed :-(\n");
                res = -1;
-       } else if (res == AST_FEATURE_RETURN_SUCCESSBREAK) {
-               /* Don't let the after-bridge code run the h-exten */
-               ast_channel_lock(transferee);
-               ast_set_flag(ast_channel_flags(transferee), AST_FLAG_BRIDGE_HANGUP_DONT);
-               ast_channel_unlock(transferee);
        }
        check_goto_on_transfer(transferer);
        return res;
@@ -2774,8 +2614,6 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
        ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate);
 
        if (!ast_check_hangup(transferer)) {
-               int hangup_dont = 0;
-
                /* Transferer (party B) is up */
                ast_debug(1, "Actually doing an attended transfer.\n");
 
@@ -2814,32 +2652,12 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
                ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
                ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
 
-               /*
-                * ast_bridge_call clears AST_FLAG_BRIDGE_HANGUP_DONT, but we
-                * don't want that to happen here because the transferer is in
-                * another bridge already.
-                */
-               if (ast_test_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT)) {
-                       hangup_dont = 1;
-               }
-
-               /*
-                * Don't let the after-bridge code run the h-exten.  It is the
-                * wrong bridge to run the h-exten after.
-                */
-               ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT);
-
                /*
                 * Let party B and C talk as long as they want while party A
                 * languishes in autoservice listening to MOH.
                 */
                ast_bridge_call(transferer, newchan, &bconfig);
 
-               if (hangup_dont) {
-                       /* Restore the AST_FLAG_BRIDGE_HANGUP_DONT flag */
-                       ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT);
-               }
-
                if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) {
                        ast_autoservice_chan_hangup_peer(transferer, newchan);
                        if (ast_stream_and_wait(transferer, xfersound, "")) {
@@ -3731,6 +3549,7 @@ static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel
        return res;
 }
 
+#if 0//BUGBUG
 /*!
  * \brief Check the dynamic features
  * \param chan,peer,config,code,sense
@@ -3777,12 +3596,14 @@ static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer,
 
        return res;
 }
+#endif
 
 
 int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) {
        return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature);
 }
 
+#if 0//BUGBUG
 /*! \brief Check if a feature exists */
 static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) {
        struct ast_str *chan_dynamic_features;
@@ -3801,11 +3622,15 @@ static int feature_check(struct ast_channel *chan, struct ast_flags *features, c
 
        return res;
 }
+#endif
 
 static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config)
 {
        int x;
 
+/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */
+/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */
        ast_clear_flag(config, AST_FLAGS_ALL);
 
        ast_rdlock_call_features();
@@ -4208,37 +4033,22 @@ void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this i
 {
        ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan);
        ast_log(LOG_NOTICE, "CHAN: name: %s;  appl: %s; data: %s; contxt: %s;  exten: %s; pri: %d;\n",
-               ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+               ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan),
+               ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
        ast_log(LOG_NOTICE, "CHAN: acctcode: %s;  dialcontext: %s; amaflags: %x; maccontxt: %s;  macexten: %s; macpri: %d;\n",
-               ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
-       ast_log(LOG_NOTICE, "CHAN: masq: %p;  masqr: %p; _bridge: %p; uniqueID: %s; linkedID:%s\n",
+               ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan),
+               ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan));
+       ast_log(LOG_NOTICE, "CHAN: masq: %p;  masqr: %p; uniqueID: %s; linkedID:%s\n",
                ast_channel_masq(chan), ast_channel_masqr(chan),
-               ast_channel_internal_bridged_channel(chan), ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
+               ast_channel_uniqueid(chan), ast_channel_linkedid(chan));
        if (ast_channel_masqr(chan)) {
                ast_log(LOG_NOTICE, "CHAN: masquerading as: %s;  cdr: %p;\n",
                        ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan)));
        }
-       if (ast_channel_internal_bridged_channel(chan)) {
-               ast_log(LOG_NOTICE, "CHAN: Bridged to %s\n", ast_channel_name(ast_channel_internal_bridged_channel(chan)));
-       }
 
        ast_log(LOG_NOTICE, "===== done ====\n");
 }
 
-/*!
- * \brief return the first unlocked cdr in a possible chain
- */
-static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr)
-{
-       struct ast_cdr *cdr_orig = cdr;
-       while (cdr) {
-               if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED))
-                       return cdr;
-               cdr = cdr->next;
-       }
-       return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */
-}
-
 static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features)
 {
        const char *feature;
@@ -4249,17 +4059,14 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons
 
        for (feature = features; *feature; feature++) {
                struct ast_flags *party;
-               char this_feature;
 
                if (isupper(*feature)) {
-                       party = &(config->features_caller);
+                       party = &config->features_caller;
                } else {
-                       party = &(config->features_callee);
+                       party = &config->features_callee;
                }
 
-               this_feature = tolower(*feature);
-
-               switch (this_feature) {
+               switch (tolower(*feature)) {
                case 't' :
                        ast_set_flag(party, AST_FEATURE_REDIRECT);
                        break;
@@ -4277,6 +4084,7 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons
                        break;
                default :
                        ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature);
+                       break;
                }
        }
 }
@@ -4332,6 +4140,279 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st
                digit, ast_channel_name(chan), why, duration);
 }
 
+/*!
+ * \internal
+ * \brief Setup bridge builtin features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan)
+{
+       struct ast_flags *flags;
+       char dtmf[FEATURE_MAX_LEN];
+       int res;
+
+       ast_channel_lock(chan);
+       flags = ast_bridge_features_ds_get(chan);
+       ast_channel_unlock(chan);
+       if (!flags) {
+               return 0;
+       }
+
+       res = 0;
+       ast_rdlock_call_features();
+       if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) {
+               /* Add atxfer and blind transfer. */
+               builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf));
+               if (!ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */
+                       res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf, NULL, NULL, 1);
+               }
+               builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf));
+               if (!ast_strlen_zero(dtmf)) {
+/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */
+                       res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf, NULL, NULL, 1);
+               }
+       }
+       if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) {
+               builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf));
+               if (ast_strlen_zero(dtmf)) {
+                       res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf, NULL, NULL, 1);
+               }
+       }
+       if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) {
+               builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf));
+               if (!ast_strlen_zero(dtmf)) {
+                       res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf, NULL, NULL, 1);
+               }
+       }
+       if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) {
+               builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf));
+               if (!ast_strlen_zero(dtmf)) {
+                       res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf, NULL, NULL, 1);
+               }
+       }
+       if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) {
+               builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf));
+               if (!ast_strlen_zero(dtmf)) {
+                       res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf, NULL, NULL, 1);
+               }
+       }
+       ast_unlock_call_features();
+
+#if 0  /* BUGBUG don't report errors untill all of the builtin features are supported. */
+       return res ? -1 : 0;
+#else
+       return 0;
+#endif
+}
+
+struct dtmf_hook_run_app {
+       /*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */
+       unsigned int flags;
+       /*! Offset into app_name[] where the MOH class name starts.  (zero if no MOH) */
+       int moh_offset;
+       /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */
+       int app_args_offset;
+       /*! Application name to run. */
+       char app_name[0];
+};
+
+/*!
+ * \internal
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct dtmf_hook_run_app *pvt = hook_pvt;
+       void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class);
+
+       if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) {
+               run_it = ast_bridge_channel_write_app;
+       } else {
+               run_it = ast_bridge_channel_run_app;
+       }
+
+/*
+ * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run.
+ *
+ * This would replace DYNAMIC_PEERNAME which is redundant with
+ * BRIDGEPEER anyway.  The value of DYNAMIC_WHO_TRIGGERED is
+ * really useful in the case of a multi-party bridge.
+ */
+       run_it(bridge_channel, pvt->app_name,
+               pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL,
+               pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL);
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Add a dynamic DTMF feature hook to the bridge features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER).
+ * \param dtmf DTMF trigger sequence.
+ * \param app_name Dialplan application name to run.
+ * \param app_args Dialplan application arguments. (Empty or NULL if no arguments)
+ * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played)
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class)
+{
+       struct dtmf_hook_run_app *app_data;
+       size_t len_name = strlen(app_name) + 1;
+       size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1;
+       size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1;
+       size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh;
+
+       /* Fill in application run hook data. */
+       app_data = ast_malloc(len_data);
+       if (!app_data) {
+               return -1;
+       }
+       app_data->flags = flags;
+       app_data->app_args_offset = len_args ? len_name : 0;
+       app_data->moh_offset = len_moh ? len_name + len_args : 0;
+       strcpy(app_data->app_name, app_name);/* Safe */
+       if (len_args) {
+               strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */
+       }
+       if (len_moh) {
+               strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
+       }
+
+       return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook,
+               app_data, ast_free_ptr, 1);
+}
+
+/*!
+ * \internal
+ * \brief Setup bridge dynamic features.
+ * \since 12.0.0
+ *
+ * \param features Bridge features to setup.
+ * \param chan Get features from this channel.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
+{
+       const char *feat;
+       char *dynamic_features = NULL;
+       char *tok;
+       int res;
+
+       ast_channel_lock(chan);
+       feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
+       if (!ast_strlen_zero(feat)) {
+               dynamic_features = ast_strdupa(feat);
+       }
+       ast_channel_unlock(chan);
+       if (!dynamic_features) {
+               return 0;
+       }
+
+/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */
+       res = 0;
+       while ((tok = strsep(&dynamic_features, "#"))) {
+               struct feature_group *fg;
+               struct ast_call_feature *feature;
+
+               AST_RWLIST_RDLOCK(&feature_groups);
+               fg = find_group(tok);
+               if (fg) {
+                       struct feature_group_exten *fge;
+
+                       AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+                               res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten,
+                                       fge->feature->app, fge->feature->app_args, fge->feature->moh_class);
+                       }
+               }
+               AST_RWLIST_UNLOCK(&feature_groups);
+
+               ast_rdlock_call_features();
+               feature = find_dynamic_feature(tok);
+               if (feature) {
+                       res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten,
+                               feature->app, feature->app_args, feature->moh_class);
+               }
+               ast_unlock_call_features();
+       }
+       return res;
+}
+
+/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */
+/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */
+int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel)
+{
+       int res = 0;
+
+       /* Always pass through any DTMF digits. */
+       bridge_channel->features->dtmf_passthrough = 1;
+
+       res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan);
+       res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan);
+
+       return res;
+}
+
+static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
+{
+       if (config->end_sound) {
+               ast_string_field_set(limits, duration_sound, config->end_sound);
+       }
+
+       if (config->warning_sound) {
+               ast_string_field_set(limits, warning_sound, config->warning_sound);
+       }
+
+       if (config->start_sound) {
+               ast_string_field_set(limits, connect_sound, config->start_sound);
+       }
+
+       limits->frequency = config->warning_freq;
+       limits->warning = config->play_warning;
+}
+
+/*!
+ * \internal brief Setup limit hook structures on calls that need limits
+ *
+ * \param config ast_bridge_config which provides the limit data
+ * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits
+ * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits
+ */
+static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits)
+{
+       if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) {
+               bridge_config_set_limits_warning_values(config, caller_limits);
+       }
+
+       if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) {
+               bridge_config_set_limits_warning_values(config, callee_limits);
+       }
+
+       caller_limits->duration = config->timelimit;
+       callee_limits->duration = config->timelimit;
+}
+
 /*!
  * \internal
  * \brief Check if Monitor needs to be started on a channel.
@@ -4374,6 +4455,24 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
        }
 }
 
+/*!
+ * \internal
+ * \brief Send the peer channel on its way on bridge start failure.
+ * \since 12.0.0
+ *
+ * \param chan Chan to put into autoservice.
+ * \param peer Chan to send to after bridge goto or run hangup handlers and hangup.
+ *
+ * \return Nothing
+ */
+static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer)
+{
+       if (ast_after_bridge_goto_setup(peer)
+               || ast_pbx_start(peer)) {
+               ast_autoservice_chan_hangup_peer(chan, peer);
+       }
+}
+
 /*!
  * \brief bridge the call and set CDR
  *
@@ -4388,33 +4487,16 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
  */
 int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
 {
-       /* Copy voice back and forth between the two channels.  Give the peer
-          the ability to transfer calls with '#<extension' syntax. */
-       struct ast_frame *f;
-       struct ast_channel *who;
-       char chan_featurecode[FEATURE_MAX_LEN + 1]="";
-       char peer_featurecode[FEATURE_MAX_LEN + 1]="";
-       char orig_channame[AST_CHANNEL_NAME];
-       char orig_peername[AST_CHANNEL_NAME];
        int res;
-       int diff;
-       int hasfeatures=0;
-       int hadfeatures=0;
-       int sendingdtmfdigit = 0;
-       int we_disabled_peer_cdr = 0;
-       struct ast_option_header *aoh;
-       struct ast_cdr *bridge_cdr = NULL;
-       struct ast_cdr *chan_cdr = ast_channel_cdr(chan); /* the proper chan cdr, if there are forked cdrs */
-       struct ast_cdr *peer_cdr = ast_channel_cdr(peer); /* the proper chan cdr, if there are forked cdrs */
-       struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
-       struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */
-       struct ast_silence_generator *silgen = NULL;
-       /*! TRUE if h-exten or hangup handlers run. */
-       int hangup_run = 0;
+       struct ast_bridge *bridge;
+       struct ast_bridge_features chan_features;
+       struct ast_bridge_features *peer_features;
 
+/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */
        pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer));
        pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan));
 
+/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */
        /* Clear any BLINDTRANSFER since the transfer has completed. */
        pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
        pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL);
@@ -4422,10 +4504,15 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
        set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES"));
        add_features_datastores(chan, peer, config);
 
-       /* This is an interesting case.  One example is if a ringing channel gets redirected to
-        * an extension that picks up a parked call.  This will make sure that the call taken
-        * out of parking gets told that the channel it just got bridged to is still ringing. */
-       if (ast_channel_state(chan) == AST_STATE_RINGING && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
+       /*
+        * This is an interesting case.  One example is if a ringing
+        * channel gets redirected to an extension that picks up a
+        * parked call.  This will make sure that the call taken out of
+        * parking gets told that the channel it just got bridged to is
+        * still ringing.
+        */
+       if (ast_channel_state(chan) == AST_STATE_RINGING
+               && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) {
                ast_indicate(peer, AST_CONTROL_RINGING);
        }
 
@@ -4436,6 +4523,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
        /* Answer if need be */
        if (ast_channel_state(chan) != AST_STATE_UP) {
                if (ast_raw_answer(chan, 1)) {
+                       bridge_failed_peer_goto(chan, peer);
                        return -1;
                }
        }
@@ -4446,571 +4534,123 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
        ast_channel_log("Pre-bridge PEER Channel info", peer);
 #endif
        /* two channels are being marked as linked here */
-       ast_channel_set_linkgroup(chan,peer);
+       ast_channel_set_linkgroup(chan, peer);
 
-       /* copy the userfield from the B-leg to A-leg if applicable */
-       if (ast_channel_cdr(chan) && ast_channel_cdr(peer) && !ast_strlen_zero(ast_channel_cdr(peer)->userfield)) {
-               char tmp[256];
-
-               ast_channel_lock(chan);
-               if (!ast_strlen_zero(ast_channel_cdr(chan)->userfield)) {
-                       snprintf(tmp, sizeof(tmp), "%s;%s", ast_channel_cdr(chan)->userfield, ast_channel_cdr(peer)->userfield);
-                       ast_cdr_appenduserfield(chan, tmp);
-               } else {
-                       ast_cdr_setuserfield(chan, ast_channel_cdr(peer)->userfield);
-               }
-               ast_channel_unlock(chan);
-               /* Don't delete the CDR; just disable it. */
-               ast_set_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED);
-               we_disabled_peer_cdr = 1;
-       }
-       ast_copy_string(orig_channame,ast_channel_name(chan),sizeof(orig_channame));
-       ast_copy_string(orig_peername,ast_channel_name(peer),sizeof(orig_peername));
-
-       if (!chan_cdr || (chan_cdr && !ast_test_flag(chan_cdr, AST_CDR_FLAG_POST_DISABLED))) {
-               ast_channel_lock_both(chan, peer);
-               if (chan_cdr) {
-                       ast_set_flag(chan_cdr, AST_CDR_FLAG_MAIN);
-                       ast_cdr_update(chan);
-                       bridge_cdr = ast_cdr_dup_unique_swap(chan_cdr);
-                       /* rip any forked CDR's off of the chan_cdr and attach
-                        * them to the bridge_cdr instead */
-                       bridge_cdr->next = chan_cdr->next;
-                       chan_cdr->next = NULL;
-                       ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp));
-                       ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata));
-                       if (peer_cdr && !ast_strlen_zero(peer_cdr->userfield)) {
-                               ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
-                       }
-                       ast_cdr_setaccount(peer, ast_channel_accountcode(chan));
-               } else {
-                       /* better yet, in a xfer situation, find out why the chan cdr got zapped (pun unintentional) */
-                       bridge_cdr = ast_cdr_alloc(); /* this should be really, really rare/impossible? */
-                       ast_copy_string(bridge_cdr->channel, ast_channel_name(chan), sizeof(bridge_cdr->channel));
-                       ast_copy_string(bridge_cdr->dstchannel, ast_channel_name(peer), sizeof(bridge_cdr->dstchannel));
-                       ast_copy_string(bridge_cdr->uniqueid, ast_channel_uniqueid(chan), sizeof(bridge_cdr->uniqueid));
-                       ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp));
-                       ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata));
-                       ast_cdr_setcid(bridge_cdr, chan);
-                       bridge_cdr->disposition = (ast_channel_state(chan) == AST_STATE_UP) ?  AST_CDR_ANSWERED : AST_CDR_NULL;
-                       bridge_cdr->amaflags = ast_channel_amaflags(chan) ? ast_channel_amaflags(chan) :  ast_default_amaflags;
-                       ast_copy_string(bridge_cdr->accountcode, ast_channel_accountcode(chan), sizeof(bridge_cdr->accountcode));
-                       /* Destination information */
-                       ast_copy_string(bridge_cdr->dst, ast_channel_exten(chan), sizeof(bridge_cdr->dst));
-                       ast_copy_string(bridge_cdr->dcontext, ast_channel_context(chan), sizeof(bridge_cdr->dcontext));
-                       if (peer_cdr) {
-                               bridge_cdr->start = peer_cdr->start;
-                               ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield));
-                       } else {
-                               ast_cdr_start(bridge_cdr);
-                       }
-               }
-               ast_channel_unlock(chan);
-               ast_channel_unlock(peer);
+       /*
+        * If we are bridging a call, stop worrying about forwarding
+        * loops.  We presume that if a call is being bridged, that the
+        * humans in charge know what they're doing.  If they don't,
+        * well, what can we do about that?
+        */
+       clear_dialed_interfaces(chan);
+       clear_dialed_interfaces(peer);
 
-               ast_debug(4, "bridge answer set, chan answer set\n");
-               /* peer_cdr->answer will be set when a macro runs on the peer;
-                  in that case, the bridge answer will be delayed while the
-                  macro plays on the peer channel. The peer answered the call
-                  before the macro started playing. To the phone system,
-                  this is billable time for the call, even tho the caller
-                  hears nothing but ringing while the macro does its thing. */
-
-               /* Another case where the peer cdr's time will be set, is when
-                  A self-parks by pickup up phone and dialing 700, then B
-                  picks up A by dialing its parking slot; there may be more
-                  practical paths that get the same result, tho... in which
-                  case you get the previous answer time from the Park... which
-                  is before the bridge's start time, so I added in the
-                  tvcmp check to the if below */
-
-               if (peer_cdr && !ast_tvzero(peer_cdr->answer) && ast_tvcmp(peer_cdr->answer, bridge_cdr->start) >= 0) {
-                       ast_cdr_setanswer(bridge_cdr, peer_cdr->answer);
-                       ast_cdr_setdisposition(bridge_cdr, peer_cdr->disposition);
-                       if (chan_cdr) {
-                               ast_cdr_setanswer(chan_cdr, peer_cdr->answer);
-                               ast_cdr_setdisposition(chan_cdr, peer_cdr->disposition);
-                       }
-               } else {
-                       ast_cdr_answer(bridge_cdr);
-                       if (chan_cdr) {
-                               ast_cdr_answer(chan_cdr); /* for the sake of cli status checks */
-                       }
-               }
-               if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT) && (chan_cdr || peer_cdr)) {
-                       if (chan_cdr) {
-                               ast_set_flag(chan_cdr, AST_CDR_FLAG_BRIDGED);
-                       }
-                       if (peer_cdr) {
-                               ast_set_flag(peer_cdr, AST_CDR_FLAG_BRIDGED);
-                       }
-               }
-               /* the DIALED flag may be set if a dialed channel is transfered
-                * and then bridged to another channel.  In order for the
-                * bridge CDR to be written, the DIALED flag must not be
-                * present. */
-               ast_clear_flag(bridge_cdr, AST_CDR_FLAG_DIALED);
+       res = 0;
+       ast_channel_lock(chan);
+       res |= ast_bridge_features_ds_set(chan, &config->features_caller);
+       ast_channel_unlock(chan);
+       ast_channel_lock(peer);
+       res |= ast_bridge_features_ds_set(peer, &config->features_callee);
+       ast_channel_unlock(peer);
+       if (res) {
+               bridge_failed_peer_goto(chan, peer);
+               return -1;
        }
-       ast_cel_report_event(chan, AST_CEL_BRIDGE_START, NULL, NULL, peer);
-
-       /* If we are bridging a call, stop worrying about forwarding loops. We presume that if
-        * a call is being bridged, that the humans in charge know what they're doing. If they
-        * don't, well, what can we do about that? */
-       clear_dialed_interfaces(chan);
-       clear_dialed_interfaces(peer);
 
-       for (;;) {
-               struct ast_channel *other;      /* used later */
+       /* Setup features. */
+       res = ast_bridge_features_init(&chan_features);
+       peer_features = ast_bridge_features_new();
+       if (res || !peer_features) {
+               ast_bridge_features_destroy(peer_features);
+               ast_bridge_features_cleanup(&chan_features);
+               bridge_failed_peer_goto(chan, peer);
+               return -1;
+       }
 
-               res = ast_channel_bridge(chan, peer, config, &f, &who);
+       if (config->timelimit) {
+               struct ast_bridge_features_limits call_duration_limits_chan;
+               struct ast_bridge_features_limits call_duration_limits_peer;
+               int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */
 
-               if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
-                       || ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE)) {
-                       /* Zombies are present time to leave! */
-                       res = -1;
-                       if (f) {
-                               ast_frfree(f);
-                       }
-                       goto before_you_go;
-               }
-
-               /* When frame is not set, we are probably involved in a situation
-                  where we've timed out.
-                  When frame is set, we'll come this code twice; once for DTMF_BEGIN
-                  and also for DTMF_END. If we flow into the following 'if' for both, then
-                  our wait times are cut in half, as both will subtract from the
-                  feature_timer. Not good!
-               */
-               if (config->feature_timer && (!f || f->frametype == AST_FRAME_DTMF_END)) {
-                       /* Update feature timer for next pass */
-                       diff = ast_tvdiff_ms(ast_tvnow(), config->feature_start_time);
-                       if (res == AST_BRIDGE_RETRY) {
-                               /* The feature fully timed out but has not been updated. Skip
-                                * the potential round error from the diff calculation and
-                                * explicitly set to expired. */
-                               config->feature_timer = -1;
-                       } else {
-                               config->feature_timer -= diff;
-                       }
+               if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
+                       ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
 
-                       if (hasfeatures) {
-                               if (config->feature_timer <= 0) {
-                                       /* Not *really* out of time, just out of time for
-                                          digits to come in for features. */
-                                       ast_debug(1, "Timed out for feature!\n");
-                                       if (!ast_strlen_zero(peer_featurecode)) {
-                                               ast_dtmf_stream(chan, peer, peer_featurecode, 0, f ? f->len : 0);
-                                               memset(peer_featurecode, 0, sizeof(peer_featurecode));
-                                       }
-                                       if (!ast_strlen_zero(chan_featurecode)) {
-                                               ast_dtmf_stream(peer, chan, chan_featurecode, 0, f ? f->len : 0);
-                                               memset(chan_featurecode, 0, sizeof(chan_featurecode));
-                                       }
-                                       if (f)
-                                               ast_frfree(f);
-                                       hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
-                                       if (!hasfeatures) {
-                                               /* No more digits expected - reset the timer */
-                                               config->feature_timer = 0;
-                                       }
-                                       hadfeatures = hasfeatures;
-                                       /* Continue as we were */
-                                       continue;
-                               } else if (!f) {
-                                       /* The bridge returned without a frame and there is a feature in progress.
-                                        * However, we don't think the feature has quite yet timed out, so just
-                                        * go back into the bridge. */
-                                       continue;
-                               }
-                       } else {
-                               if (config->feature_timer <=0) {
-                                       /* We ran out of time */
-                                       config->feature_timer = 0;
-                                       who = chan;
-                                       if (f)
-                                               ast_frfree(f);
-                                       f = NULL;
-                                       res = 0;
-                               }
-                       }
-               }
-               if (res < 0) {
-                       if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer)) {
-                               ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", ast_channel_name(chan), ast_channel_name(peer));
-                       }
-                       goto before_you_go;
+                       ast_bridge_features_destroy(peer_features);
+                       ast_bridge_features_cleanup(&chan_features);
+                       bridge_failed_peer_goto(chan, peer);
+                       return -1;
                }
 
-               if (!f || (f->frametype == AST_FRAME_CONTROL &&
-                               (f->subclass.integer == AST_CONTROL_HANGUP || f->subclass.integer == AST_CONTROL_BUSY ||
-                                       f->subclass.integer == AST_CONTROL_CONGESTION))) {
-                       res = -1;
-                       break;
-               }
-               /* many things should be sent to the 'other' channel */
-               other = (who == chan) ? peer : chan;
-               if (f->frametype == AST_FRAME_CONTROL) {
-                       switch (f->subclass.integer) {
-                       case AST_CONTROL_RINGING:
-                       case AST_CONTROL_FLASH:
-                       case AST_CONTROL_MCID:
-                       case -1:
-                               ast_indicate(other, f->subclass.integer);
-                               break;
-                       case AST_CONTROL_CONNECTED_LINE:
-                               if (ast_channel_connected_line_sub(who, other, f, 1) &&
-                                       ast_channel_connected_line_macro(who, other, f, who != chan, 1)) {
-                                       ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
-                               }
-                               break;
-                       case AST_CONTROL_REDIRECTING:
-                               if (ast_channel_redirecting_sub(who, other, f, 1) &&
-                                       ast_channel_redirecting_macro(who, other, f, who != chan, 1)) {
-                                       ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
-                               }
-                               break;
-                       case AST_CONTROL_PVT_CAUSE_CODE:
-                       case AST_CONTROL_AOC:
-                       case AST_CONTROL_HOLD:
-                       case AST_CONTROL_UNHOLD:
-                               ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen);
-                               break;
-                       case AST_CONTROL_OPTION:
-                               aoh = f->data.ptr;
-                               /* Forward option Requests, but only ones we know are safe
-                                * These are ONLY sent by chan_iax2 and I'm not convinced that
-                                * they are useful. I haven't deleted them entirely because I
-                                * just am not sure of the ramifications of removing them. */
-                               if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
-                                       switch (ntohs(aoh->option)) {
-                                       case AST_OPTION_TONE_VERIFY:
-                                       case AST_OPTION_TDD:
-                                       case AST_OPTION_RELAXDTMF:
-                                       case AST_OPTION_AUDIO_MODE:
-                                       case AST_OPTION_DIGIT_DETECT:
-                                       case AST_OPTION_FAX_DETECT:
-                                               ast_channel_setoption(other, ntohs(aoh->option), aoh->data,
-                                                       f->datalen - sizeof(struct ast_option_header), 0);
-                                       }
-                               }
-                               break;
-                       }
-               } else if (f->frametype == AST_FRAME_DTMF_BEGIN) {
-                       struct ast_flags *cfg;
-                       char dtmfcode[2] = { f->subclass.integer, };
-                       size_t featurelen;
-
-                       if (who == chan) {
-                               featurelen = strlen(chan_featurecode);
-                               cfg = &(config->features_caller);
-                       } else {
-                               featurelen = strlen(peer_featurecode);
-                               cfg = &(config->features_callee);
-                       }
-                       /* Take a peek if this (possibly) matches a feature. If not, just pass this
-                        * DTMF along untouched. If this is not the first digit of a multi-digit code
-                        * then we need to fall through and stream the characters if it matches */
-                       if (featurelen == 0
-                               && feature_check(chan, cfg, &dtmfcode[0]) == AST_FEATURE_RETURN_PASSDIGITS) {
-                               if (option_debug > 3) {
-                                       ast_log(LOG_DEBUG, "Passing DTMF through, since it is not a feature code\n");
-                               }
-                               ast_write(other, f);
-                               sendingdtmfdigit = 1;
-                       } else {
-                               /* If ast_opt_transmit_silence is set, then we need to make sure we are
-                                * transmitting something while we hold on to the DTMF waiting for a
-                                * feature. */
-                               if (!silgen && ast_opt_transmit_silence) {
-                                       silgen = ast_channel_start_silence_generator(other);
-                               }
-                               if (option_debug > 3) {
-                                       ast_log(LOG_DEBUG, "Not passing DTMF through, since it may be a feature code\n");
-                               }
-                       }
-               } else if (f->frametype == AST_FRAME_DTMF_END) {
-                       char *featurecode;
-                       int sense;
-                       unsigned int dtmfduration = f->len;
-
-                       hadfeatures = hasfeatures;
-                       /* This cannot overrun because the longest feature is one shorter than our buffer */
-                       if (who == chan) {
-                               sense = FEATURE_SENSE_CHAN;
-                               featurecode = chan_featurecode;
-                       } else  {
-                               sense = FEATURE_SENSE_PEER;
-                               featurecode = peer_featurecode;
-                       }
+               if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) {
+                       ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
+                       ast_bridge_features_limits_destroy(&call_duration_limits_chan);
 
-                       if (sendingdtmfdigit == 1) {
-                               /* We let the BEGIN go through happily, so let's not bother with the END,
-                                * since we already know it's not something we bother with */
-                               ast_write(other, f);
-                               sendingdtmfdigit = 0;
-                       } else {
-                               /*! append the event to featurecode. we rely on the string being zero-filled, and
-                                * not overflowing it.
-                                * \todo XXX how do we guarantee the latter ?
-                                */
-                               featurecode[strlen(featurecode)] = f->subclass.integer;
-                               /* Get rid of the frame before we start doing "stuff" with the channels */
-                               ast_frfree(f);
-                               f = NULL;
-                               if (silgen) {
-                                       ast_channel_stop_silence_generator(other, silgen);
-                                       silgen = NULL;
-                               }
-                               config->feature_timer = 0;
-                               res = feature_interpret(chan, peer, config, featurecode, sense);
-                               switch(res) {
-                               case AST_FEATURE_RETURN_PASSDIGITS:
-                                       ast_dtmf_stream(other, who, featurecode, 0, dtmfduration);
-                                       /* Fall through */
-                               case AST_FEATURE_RETURN_SUCCESS:
-                                       memset(featurecode, 0, sizeof(chan_featurecode));
-                                       break;
-                               }
-                               if (res >= AST_FEATURE_RETURN_PASSDIGITS) {
-                                       res = 0;
-                               } else {
-                                       break;
-                               }
-                               hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
-                               if (hadfeatures && !hasfeatures) {
-                                       /* Feature completed or timed out */
-                                       config->feature_timer = 0;
-                               } else if (hasfeatures) {
-                                       if (config->timelimit) {
-                                               /* No warning next time - we are waiting for feature code */
-                                               ast_set_flag(config, AST_FEATURE_WARNING_ACTIVE);
-                                       }
-                                       config->feature_start_time = ast_tvnow();
-                                       config->feature_timer = featuredigittimeout;
-                                       ast_debug(1, "Set feature timer to %ld ms\n", config->feature_timer);
-                               }
-                       }
+                       ast_bridge_features_destroy(peer_features);
+                       ast_bridge_features_cleanup(&chan_features);
+                       bridge_failed_peer_goto(chan, peer);
+                       return -1;
                }
-               if (f)
-                       ast_frfree(f);
-       }
-       ast_cel_report_event(chan, AST_CEL_BRIDGE_END, NULL, NULL, peer);
 
-before_you_go:
-       if (ast_channel_sending_dtmf_digit(chan)) {
-               ast_bridge_end_dtmf(chan, ast_channel_sending_dtmf_digit(chan),
-                       ast_channel_sending_dtmf_tv(chan), "bridge end");
-       }
-       if (ast_channel_sending_dtmf_digit(peer)) {
-               ast_bridge_end_dtmf(peer, ast_channel_sending_dtmf_digit(peer),
-                       ast_channel_sending_dtmf_tv(peer), "bridge end");
-       }
+               bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
 
-       /* Just in case something weird happened and we didn't clean up the silence generator... */
-       if (silgen) {
-               ast_channel_stop_silence_generator(who == chan ? peer : chan, silgen);
-               silgen = NULL;
-       }
+               if (ast_bridge_features_set_limits(&chan_features, &call_duration_limits_chan, 0)) {
+                       abandon_call = 1;
+               }
+               if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) {
+                       abandon_call = 1;
+               }
 
-       /* Wait for any dual redirect to complete. */
-       while (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
-               sched_yield();
-       }
+               /* At this point we are done with the limits structs since they have been copied to the individual feature sets. */
+               ast_bridge_features_limits_destroy(&call_duration_limits_chan);
+               ast_bridge_features_limits_destroy(&call_duration_limits_peer);
 
-       if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT)) {
-               ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); /* its job is done */
-               if (bridge_cdr) {
-                       ast_cdr_discard(bridge_cdr);
-                       /* QUESTION: should we copy bridge_cdr fields to the peer before we throw it away? */
+               if (abandon_call) {
+                       ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n");
+                       ast_bridge_features_destroy(peer_features);
+                       ast_bridge_features_cleanup(&chan_features);
+                       bridge_failed_peer_goto(chan, peer);
+                       return -1;
                }
-               return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */
        }
 
-       if (config->end_bridge_callback) {
-               config->end_bridge_callback(config->end_bridge_callback_data);
+       /* Create bridge */
+       bridge = ast_bridge_basic_new();
+       if (!bridge) {
+               ast_bridge_features_destroy(peer_features);
+               ast_bridge_features_cleanup(&chan_features);
+               bridge_failed_peer_goto(chan, peer);
+               return -1;
        }
 
-       /* run the hangup exten on the chan object IFF it was NOT involved in a parking situation
-        * if it were, then chan belongs to a different thread now, and might have been hung up long
-        * ago.
-        */
-       if (!(ast_channel_softhangup_internal_flag(chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE))
-                       && !ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) {
-               struct ast_cdr *swapper = NULL;
-               char savelastapp[AST_MAX_EXTENSION];
-               char savelastdata[AST_MAX_EXTENSION];
-               char save_context[AST_MAX_CONTEXT];
-               char save_exten[AST_MAX_EXTENSION];
-               int  save_prio;
-
-               ast_channel_lock(chan);
-               if (bridge_cdr) {
-                       /*
-                        * Swap the bridge_cdr and the chan cdr for a moment, and let
-                        * the hangup dialplan code operate on it.
-                        */
-                       swapper = ast_channel_cdr(chan);
-                       ast_channel_cdr_set(chan, bridge_cdr);
-
-                       /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */
-                       ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp));
-                       ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata));
-               }
-               ast_copy_string(save_context, ast_channel_context(chan), sizeof(save_context));
-               ast_copy_string(save_exten, ast_channel_exten(chan), sizeof(save_exten));
-               save_prio = ast_channel_priority(chan);
-               ast_channel_unlock(chan);
-
-               ast_autoservice_start(peer);
-               if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1,
-                       S_COR(ast_channel_caller(chan)->id.number.valid,
-                               ast_channel_caller(chan)->id.number.str, NULL))) {
-                       ast_pbx_h_exten_run(chan, ast_channel_context(chan));
-                       hangup_run = 1;
-               } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
-                       && ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1,
-                               S_COR(ast_channel_caller(chan)->id.number.valid,
-                                       ast_channel_caller(chan)->id.number.str, NULL))) {
-                       ast_pbx_h_exten_run(chan, ast_channel_macrocontext(chan));
-                       hangup_run = 1;
-               }
-               if (ast_pbx_hangup_handler_run(chan)) {
-                       /* Indicate hangup handlers were run. */
-                       hangup_run = 1;
-               }
-               ast_autoservice_stop(peer);
-
-               ast_channel_lock(chan);
-
-               /* swap it back */
-               ast_channel_context_set(chan, save_context);
-               ast_channel_exten_set(chan, save_exten);
-               ast_channel_priority_set(chan, save_prio);
-               if (bridge_cdr) {
-                       if (ast_channel_cdr(chan) == bridge_cdr) {
-                               ast_channel_cdr_set(chan, swapper);
-
-                               /* Restore the lastapp/lastdata */
-                               ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp));
-                               ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata));
-                       } else {
-                               bridge_cdr = NULL;
-                       }
-               }
-               ast_channel_unlock(chan);
+       /* Put peer into the bridge */
+       if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) {
+               ast_bridge_destroy(bridge);
+               ast_bridge_features_cleanup(&chan_features);
+               bridge_failed_peer_goto(chan, peer);
+               return -1;
        }
 
-       /* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */
-       new_chan_cdr = pick_unlocked_cdr(ast_channel_cdr(chan)); /* the proper chan cdr, if there are forked cdrs */
+       /* Join bridge */
+       ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1);
 
        /*
-        * If the channel CDR has been modified during the call, record
-        * the changes in the bridge cdr, BUT, if hangup_run, the CDR
-        * got swapped so don't overwrite what was done in the
-        * h-extension or hangup handlers.  What a mess.  This is why
-        * you never touch CDR code.
+        * If the bridge was broken for a hangup that isn't real, then
+        * don't run the h extension, because the channel isn't really
+        * hung up.  This should really only happen with
+        * AST_SOFTHANGUP_ASYNCGOTO.
         */
-       if (new_chan_cdr && bridge_cdr && !hangup_run) {
-               ast_cdr_copy_vars(bridge_cdr, new_chan_cdr);
-               ast_copy_string(bridge_cdr->userfield, new_chan_cdr->userfield, sizeof(bridge_cdr->userfield));
-               bridge_cdr->amaflags = new_chan_cdr->amaflags;
-               ast_copy_string(bridge_cdr->accountcode, new_chan_cdr->accountcode, sizeof(bridge_cdr->accountcode));
-               if (ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED)) {
-                       ast_set_flag(bridge_cdr, AST_CDR_FLAG_POST_DISABLED);
-               }
-       }
-
-       /* we can post the bridge CDR at this point */
-       if (bridge_cdr) {
-               ast_cdr_end(bridge_cdr);
-               ast_cdr_detach(bridge_cdr);
-       }
-
-       /* do a specialized reset on the beginning channel
-          CDR's, if they still exist, so as not to mess up
-          issues in future bridges;
-
-          Here are the rules of the game:
-          1. The chan and peer channel pointers will not change
-             during the life of the bridge.
-          2. But, in transfers, the channel names will change.
-             between the time the bridge is started, and the
-             time the channel ends.
-             Usually, when a channel changes names, it will
-             also change CDR pointers.
-          3. Usually, only one of the two channels (chan or peer)
-             will change names.
-          4. Usually, if a channel changes names during a bridge,
-             it is because of a transfer. Usually, in these situations,
-             it is normal to see 2 bridges running simultaneously, and
-             it is not unusual to see the two channels that change
-             swapped between bridges.
-          5. After a bridge occurs, we have 2 or 3 channels' CDRs
-             to attend to; if the chan or peer changed names,
-             we have the before and after attached CDR's.
-       */
-
-       if (new_chan_cdr) {
-               struct ast_channel *chan_ptr = NULL;
-
-               if (strcasecmp(orig_channame, ast_channel_name(chan)) != 0) {
-                       /* old channel */
-                       if ((chan_ptr = ast_channel_get_by_name(orig_channame))) {
-                               ast_channel_lock(chan_ptr);
-                               if (!ast_bridged_channel(chan_ptr)) {
-                                       struct ast_cdr *cur;
-                                       for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) {
-                                               if (cur == chan_cdr) {
-                                                       break;
-                                               }
-                                       }
-                                       if (cur) {
-                                               ast_cdr_specialized_reset(chan_cdr, 0);
-                                       }
-                               }
-                               ast_channel_unlock(chan_ptr);
-                               chan_ptr = ast_channel_unref(chan_ptr);
-                       }
-                       /* new channel */
-                       ast_cdr_specialized_reset(new_chan_cdr, 0);
-               } else {
-                       ast_cdr_specialized_reset(ast_channel_cdr(chan), 0); /* nothing changed, reset the chan cdr  */
-               }
-       }
-
-       {
-               struct ast_channel *chan_ptr = NULL;
-               new_peer_cdr = pick_unlocked_cdr(ast_channel_cdr(peer)); /* the proper chan cdr, if there are forked cdrs */
-               if (new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED) && new_peer_cdr && !ast_test_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED))
-                       ast_set_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED); /* DISABLED is viral-- it will propagate across a bridge */
-               if (strcasecmp(orig_peername, ast_channel_name(peer)) != 0) {
-                       /* old channel */
-                       if ((chan_ptr = ast_channel_get_by_name(orig_peername))) {
-                               ast_channel_lock(chan_ptr);
-                               if (!ast_bridged_channel(chan_ptr)) {
-                                       struct ast_cdr *cur;
-                                       for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) {
-                                               if (cur == peer_cdr) {
-                                                       break;
-                                               }
-                                       }
-                                       if (cur) {
-                                               ast_cdr_specialized_reset(peer_cdr, 0);
-                                       }
-                               }
-                               ast_channel_unlock(chan_ptr);
-                               chan_ptr = ast_channel_unref(chan_ptr);
-                       }
-                       /* new channel */
-                       if (new_peer_cdr) {
-                               ast_cdr_specialized_reset(new_peer_cdr, 0);
-                       }
-               } else {
-                       if (we_disabled_peer_cdr) {
-                               ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED);
-                       }
-                       ast_cdr_specialized_reset(ast_channel_cdr(peer), 0); /* nothing changed, reset the peer cdr  */
-               }
+       res = -1;
+       ast_channel_lock(chan);
+       if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+               res = 0;
+       }
+       ast_channel_unlock(chan);
+
+       ast_bridge_features_cleanup(&chan_features);
+
+/* BUGBUG this is used by Dial and FollowMe for CDR information.  By Queue for Queue stats like CDRs. */
+       if (res && config->end_bridge_callback) {
+               config->end_bridge_callback(config->end_bridge_callback_data);
        }
 
        return res;
@@ -5456,395 +5096,6 @@ AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS
        AST_APP_OPTION('s', AST_PARK_OPT_SILENCE),
 END_OPTIONS );
 
-/*! \brief Park a call */
-static int park_call_exec(struct ast_channel *chan, const char *data)
-{
-       struct ast_park_call_args args = { 0, };
-       struct ast_flags flags = { 0 };
-       char orig_exten[AST_MAX_EXTENSION];
-       int orig_priority;
-       int res;
-       const char *pl_name;
-       char *parse;
-       struct park_app_args app_args;
-
-       /*
-        * Cache the original channel name because we are going to
-        * masquerade the channel.  Prefer the BLINDTRANSFER channel
-        * name over this channel name.  BLINDTRANSFER could be set if
-        * the parking access extension did not get detected and we are
-        * executing the Park application from the dialplan.
-        *
-        * The orig_chan_name is used to return the call to the
-        * originator on parking timeout.
-        */
-       args.orig_chan_name = ast_strdupa(S_OR(
-               pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), ast_channel_name(chan)));
-
-       /* Answer if call is not up */
-       if (ast_channel_state(chan) != AST_STATE_UP) {
-               if (ast_answer(chan)) {
-                       return -1;
-               }
-
-               /* Sleep to allow VoIP streams to settle down */
-               if (ast_safe_sleep(chan, 1000)) {
-                       return -1;
-               }
-       }
-
-       /* Process the dialplan application options. */
-       parse = ast_strdupa(data);
-       AST_STANDARD_APP_ARGS(app_args, parse);
-
-       if (!ast_strlen_zero(app_args.timeout)) {
-               if (sscanf(app_args.timeout, "%30d", &args.timeout) != 1) {
-                       ast_log(LOG_WARNING, "Invalid timeout '%s' provided\n", app_args.timeout);
-                       args.timeout = 0;
-               }
-       }
-       if (!ast_strlen_zero(app_args.return_con)) {
-               args.return_con = app_args.return_con;
-       }
-       if (!ast_strlen_zero(app_args.return_ext)) {
-               args.return_ext = app_args.return_ext;
-       }
-       if (!ast_strlen_zero(app_args.return_pri)) {
-               if (sscanf(app_args.return_pri, "%30d", &args.return_pri) != 1) {
-                       ast_log(LOG_WARNING, "Invalid priority '%s' specified\n", app_args.return_pri);
-                       args.return_pri = 0;
-               }
-       }
-
-       ast_app_parse_options(park_call_options, &flags, NULL, app_args.options);
-       args.flags = flags.flags;
-
-       /*
-        * Setup the exten/priority to be s/1 since we don't know where
-        * this call should return.
-        */
-       ast_copy_string(orig_exten, ast_channel_exten(chan), sizeof(orig_exten));
-       orig_priority = ast_channel_priority(chan);
-       ast_channel_exten_set(chan, "s");
-       ast_channel_priority_set(chan, 1);
-
-       /* Park the call */
-       if (!ast_strlen_zero(app_args.pl_name)) {
-               pl_name = app_args.pl_name;
-       } else {
-               pl_name = findparkinglotname(chan);
-       }
-       if (ast_strlen_zero(pl_name)) {
-               /* Parking lot is not specified, so use the default parking lot. */
-               args.parkinglot = parkinglot_addref(default_parkinglot);
-       } else {
-               args.parkinglot = find_parkinglot(pl_name);
-               if (!args.parkinglot && parkeddynamic) {
-                       args.parkinglot = create_dynamic_parkinglot(pl_name, chan);
-               }
-       }
-       if (args.parkinglot) {
-               res = masq_park_call(chan, chan, &args);
-               parkinglot_unref(args.parkinglot);
-       } else {
-               /* Parking failed because the parking lot does not exist. */
-               if (!ast_test_flag(&args, AST_PARK_OPT_SILENCE)) {
-                       ast_stream_and_wait(chan, "pbx-parkingfailed", "");
-               }
-               res = -1;
-       }
-       if (res) {
-               /* Park failed, try to continue in the dialplan. */
-               ast_channel_exten_set(chan, orig_exten);
-               ast_channel_priority_set(chan, orig_priority);
-               res = 0;
-       } else {
-               /* Park succeeded. */
-               res = -1;
-       }
-
-       return res;
-}
-
-/*! \brief Pickup parked call */
-static int parked_call_exec(struct ast_channel *chan, const char *data)
-{
-       int res;
-       struct ast_channel *peer = NULL;
-       struct parkeduser *pu;
-       struct ast_context *con;
-       char *parse;
-       const char *pl_name;
-       int park = 0;
-       struct ast_bridge_config config;
-       struct ast_parkinglot *parkinglot;
-       AST_DECLARE_APP_ARGS(app_args,
-               AST_APP_ARG(pl_space);  /*!< Parking lot space to retrieve if present. */
-               AST_APP_ARG(pl_name);   /*!< Parking lot name to use if present. */
-               AST_APP_ARG(dummy);             /*!< Place to put any remaining args string. */
-       );
-
-       parse = ast_strdupa(data);
-       AST_STANDARD_APP_ARGS(app_args, parse);
-
-       if (!ast_strlen_zero(app_args.pl_space)) {
-               if (sscanf(app_args.pl_space, "%30u", &park) != 1) {
-                       ast_log(LOG_WARNING, "Specified parking extension not a number: %s\n",
-                               app_args.pl_space);
-                       park = -1;
-               }
-       }
-
-       if (!ast_strlen_zero(app_args.pl_name)) {
-               pl_name = app_args.pl_name;
-       } else {
-               pl_name = findparkinglotname(chan);
-       }
-       if (ast_strlen_zero(pl_name)) {
-               /* Parking lot is not specified, so use the default parking lot. */
-               parkinglot = parkinglot_addref(default_parkinglot);
-       } else {
-               parkinglot = find_parkinglot(pl_name);
-               if (!parkinglot) {
-                       /* It helps to answer the channel if not already up. :) */
-                       if (ast_channel_state(chan) != AST_STATE_UP) {
-                               ast_answer(chan);
-                       }
-                       if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) {
-                               ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n",
-                                       "pbx-invalidpark", ast_channel_name(chan));
-                       }
-                       ast_log(LOG_WARNING,
-                               "Channel %s tried to retrieve parked call from unknown parking lot '%s'\n",
-                               ast_channel_name(chan), pl_name);
-                       return -1;
-               }
-       }
-
-       AST_LIST_LOCK(&parkinglot->parkings);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot->parkings, pu, list) {
-               if ((ast_strlen_zero(app_args.pl_space) || pu->parkingnum == park)
-                       && !pu->notquiteyet && !ast_channel_pbx(pu->chan)) {
-                       /* The parking space has a call and can be picked up now. */
-                       AST_LIST_REMOVE_CURRENT(list);
-                       break;
-               }
-       }
-       AST_LIST_TRAVERSE_SAFE_END;
-       if (pu) {
-               struct ast_callid *callid = ast_read_threadstorage_callid();
-
-               /* Found a parked call to pickup. */
-               peer = pu->chan;
-
-               /* We need to map the call id we have from this thread to the channel we found. */
-               if (callid) {
-                       ast_channel_callid_set(peer, callid);
-                       callid = ast_callid_unref(callid);
-               }
-
-               con = ast_context_find(parkinglot->cfg.parking_con);
-               if (con) {
-                       if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) {
-                               ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
-                       } else {
-                               notify_metermaids(pu->parkingexten, parkinglot->cfg.parking_con, AST_DEVICE_NOT_INUSE);
-                       }
-               } else {
-                       ast_log(LOG_WARNING, "Whoa, no parking context?\n");
-               }
-
-               ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "UnParkedCall", chan);
-               /*** DOCUMENTATION
-                       <managerEventInstance>
-                               <synopsis>Raised when a call has been unparked.</synopsis>
-                               <syntax>
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Exten'])" />
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Parkinglot'])" />
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='From'])" />
-                               </syntax>
-                               <see-also>
-                                       <ref type="application">ParkedCall</ref>
-                                       <ref type="managerEvent">ParkedCall</ref>
-                               </see-also>
-                       </managerEventInstance>
-               ***/
-               ast_manager_event(pu->chan, EVENT_FLAG_CALL, "UnParkedCall",
-                       "Exten: %s\r\n"
-                       "Channel: %s\r\n"
-                       "Parkinglot: %s\r\n"
-                       "From: %s\r\n"
-                       "CallerIDNum: %s\r\n"
-                       "CallerIDName: %s\r\n"
-                       "ConnectedLineNum: %s\r\n"
-                       "ConnectedLineName: %s\r\n"
-                       "Uniqueid: %s\r\n",
-                       pu->parkingexten, ast_channel_name(pu->chan), pu->parkinglot->name,
-                       ast_channel_name(chan),
-                       S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"),
-                       S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"),
-                       S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"),
-                       S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"),
-                       ast_channel_uniqueid(pu->chan)
-                       );
-
-               /* Stop entertaining the caller. */
-               switch (pu->hold_method) {
-               case AST_CONTROL_HOLD:
-                       ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
-                       break;
-               case AST_CONTROL_RINGING:
-                       ast_indicate(pu->chan, -1);
-                       break;
-               default:
-                       break;
-               }
-               pu->hold_method = 0;
-
-               parkinglot_unref(pu->parkinglot);
-               ast_free(pu);
-       }
-       AST_LIST_UNLOCK(&parkinglot->parkings);
-
-       if (peer) {
-               /* Update connected line between retrieving call and parked call. */
-               struct ast_party_connected_line connected;
-
-               ast_party_connected_line_init(&connected);
-
-               /* Send our caller-id to peer. */
-               ast_channel_lock(chan);
-               ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
-               ast_channel_unlock(chan);
-               connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
-               if (ast_channel_connected_line_sub(chan, peer, &connected, 0) &&
-                       ast_channel_connected_line_macro(chan, peer, &connected, 0, 0)) {
-                       ast_channel_update_connected_line(peer, &connected, NULL);
-               }
-
-               /*
-                * Get caller-id from peer.
-                *
-                * Update the retrieving call before it is answered if possible
-                * for best results.  Some phones do not support updating the
-                * connected line information after connection.
-                */
-               ast_channel_lock(peer);
-               ast_connected_line_copy_from_caller(&connected, ast_channel_caller(peer));
-               ast_channel_unlock(peer);
-               connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
-               if (ast_channel_connected_line_sub(peer, chan, &connected, 0) &&
-                       ast_channel_connected_line_macro(peer, chan, &connected, 1, 0)) {
-                       ast_channel_update_connected_line(chan, &connected, NULL);
-               }
-
-               ast_party_connected_line_free(&connected);
-       }
-
-       /* JK02: it helps to answer the channel if not already up */
-       if (ast_channel_state(chan) != AST_STATE_UP) {
-               ast_answer(chan);
-       }
-
-       if (peer) {
-               struct ast_datastore *features_datastore;
-               struct ast_dial_features *dialfeatures;
-
-               /* Play a courtesy to the source(s) configured to prefix the bridge connecting */
-               if (!ast_strlen_zero(courtesytone)) {
-                       static const char msg[] = "courtesy tone";
-
-                       switch (parkedplay) {
-                       case 0:/* Courtesy tone to pickup chan */
-                               res = play_message_to_chans(chan, peer, -1, msg, courtesytone);
-                               break;
-                       case 1:/* Courtesy tone to parked chan */
-                               res = play_message_to_chans(chan, peer, 1, msg, courtesytone);
-                               break;
-                       case 2:/* Courtesy tone to both chans */
-                               res = play_message_to_chans(chan, peer, 0, msg, courtesytone);
-                               break;
-                       default:
-                               res = 0;
-                               break;
-                       }
-                       if (res) {
-                               ast_autoservice_chan_hangup_peer(chan, peer);
-                               parkinglot_unref(parkinglot);
-                               return -1;
-                       }
-               }
-
-               res = ast_channel_make_compatible(chan, peer);
-               if (res < 0) {
-                       ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", ast_channel_name(chan), ast_channel_name(peer));
-                       ast_autoservice_chan_hangup_peer(chan, peer);
-                       parkinglot_unref(parkinglot);
-                       return -1;
-               }
-               /* This runs sorta backwards, since we give the incoming channel control, as if it
-                  were the person called. */
-               ast_verb(3, "Channel %s connected to parked call %d\n", ast_channel_name(chan), park);
-
-               pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer));
-               ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
-               memset(&config, 0, sizeof(struct ast_bridge_config));
-
-               /* Get datastore for peer and apply it's features to the callee side of the bridge config */
-               ast_channel_lock(peer);
-               features_datastore = ast_channel_datastore_find(peer, &dial_features_info, NULL);
-               if (features_datastore && (dialfeatures = features_datastore->data)) {
-                       ast_copy_flags(&config.features_callee, &dialfeatures->my_features,
-                               AST_FLAGS_ALL);
-               }
-               ast_channel_unlock(peer);
-
-               if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
-               }
-               if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
-               }
-               if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
-               }
-               if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
-               }
-               if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT);
-               }
-               if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT);
-               }
-               if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON);
-               }
-               if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) {
-                       ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON);
-               }
-
-               res = ast_bridge_call(chan, peer, &config);
-
-               pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer));
-               ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer));
-
-               /* Simulate the PBX hanging up */
-               ast_autoservice_chan_hangup_peer(chan, peer);
-       } else {
-               if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) {
-                       ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark",
-                               ast_channel_name(chan));
-               }
-               ast_verb(3, "Channel %s tried to retrieve nonexistent parked call %d\n",
-                       ast_channel_name(chan), park);
-               res = -1;
-       }
-
-       parkinglot_unref(parkinglot);
-       return res;
-}
-
 /*!
  * \brief Unreference parkinglot object.
  */
@@ -6282,6 +5533,10 @@ static void process_applicationmap_line(struct ast_variable *var)
                return;
        }
 
+       /*
+        * We will parse and require correct syntax for the ActivatedBy
+        * option, but the ActivatedBy option is not honored anymore.
+        */
        if (ast_strlen_zero(args.activatedby)) {
                ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH);
        } else if (!strcasecmp(args.activatedby, "caller")) {
@@ -7235,8 +6490,6 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
 {
        int i;
        struct ast_call_feature *feature;
-       struct ao2_iterator iter;
-       struct ast_parkinglot *curlot;
 #define HFS_FORMAT "%-25s %-7s %-7s\n"
 
        switch (cmd) {
@@ -7292,28 +6545,7 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
        }
        AST_RWLIST_UNLOCK(&feature_groups);
 
-       iter = ao2_iterator_init(parkinglots, 0);
-       while ((curlot = ao2_iterator_next(&iter))) {
-               ast_cli(a->fd, "\nCall parking (Parking lot: %s)\n", curlot->name);
-               ast_cli(a->fd, "------------\n");
-               ast_cli(a->fd,"%-22s:      %s\n", "Parking extension", curlot->cfg.parkext);
-               ast_cli(a->fd,"%-22s:      %s\n", "Parking context", curlot->cfg.parking_con);
-               ast_cli(a->fd,"%-22s:      %d-%d\n", "Parked call extensions",
-                       curlot->cfg.parking_start, curlot->cfg.parking_stop);
-               ast_cli(a->fd,"%-22s:      %u ms\n", "Parkingtime", curlot->cfg.parkingtime);
-               ast_cli(a->fd,"%-22s:      %s\n", "Comeback to origin",
-                               (curlot->cfg.comebacktoorigin ? "yes" : "no"));
-               ast_cli(a->fd,"%-22s:      %s%s\n", "Comeback context",
-                               curlot->cfg.comebackcontext, (curlot->cfg.comebacktoorigin ?
-                                       " (comebacktoorigin=yes, not used)" : ""));
-               ast_cli(a->fd,"%-22s:      %d\n", "Comeback dial time",
-                               curlot->cfg.comebackdialtime);
-               ast_cli(a->fd,"%-22s:      %s\n", "MusicOnHold class", curlot->cfg.mohclass);
-               ast_cli(a->fd,"%-22s:      %s\n", "Enabled", AST_CLI_YESNO(!curlot->disabled));
-               ast_cli(a->fd,"\n");
-               ao2_ref(curlot, -1);
-       }
-       ao2_iterator_destroy(&iter);
+       ast_cli(a->fd, "\n");
 
        return CLI_SUCCESS;
 }
@@ -7511,8 +6743,8 @@ static int action_bridge(struct mansession *s, const struct message *m)
                return 0;
        }
 
-       tobj->chan = tmpchana;
-       tobj->peer = tmpchanb;
+       tobj->chan = tmpchanb;
+       tobj->peer = tmpchana;
        tobj->return_to_pbx = 1;
 
        if (ast_true(playtone)) {
@@ -7545,6 +6777,7 @@ static int action_bridge(struct mansession *s, const struct message *m)
                                "Channel1: %s\r\n"
                                "Channel2: %s\r\n", ast_channel_name(tmpchana), ast_channel_name(tmpchanb));
 
+/* BUGBUG there seems to be no COLP update here. */
        bridge_call_thread_launch(tobj);
 
        astman_send_ack(s, m, "Launched bridge thread with success");
@@ -7552,180 +6785,11 @@ static int action_bridge(struct mansession *s, const struct message *m)
        return 0;
 }
 
-/*!
- * \brief CLI command to list parked calls
- * \param e
- * \param cmd
- * \param a
- *
- * Check right usage, lock parking lot, display parked calls, unlock parking lot list.
- * \retval CLI_SUCCESS on success.
- * \retval CLI_SHOWUSAGE on incorrect number of arguments.
- * \retval NULL when tab completion is used.
- */
-static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       struct parkeduser *cur;
-       int numparked = 0;
-       struct ao2_iterator iter;
-       struct ast_parkinglot *curlot;
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "parkedcalls show";
-               e->usage =
-                       "Usage: parkedcalls show\n"
-                       "       List currently parked calls\n";
-               return NULL;
-       case CLI_GENERATE:
-               return NULL;
-       }
-
-       if (a->argc > e->args)
-               return CLI_SHOWUSAGE;
-
-       ast_cli(a->fd, "%-10s %-25s (%-15s %-12s %4s) %s\n", "Num", "Channel",
-               "Context", "Extension", "Pri", "Timeout");
-
-       iter = ao2_iterator_init(parkinglots, 0);
-       while ((curlot = ao2_iterator_next(&iter))) {
-               int lotparked = 0;
-
-               /* subtract ref for iterator and for configured parking lot */
-               ast_cli(a->fd, "*** Parking lot: %s (%d)\n", curlot->name,
-                       ao2_ref(curlot, 0) - 2 - (curlot == default_parkinglot));
-
-               AST_LIST_LOCK(&curlot->parkings);
-               AST_LIST_TRAVERSE(&curlot->parkings, cur, list) {
-                       ast_cli(a->fd, "%-10.10s %-25s (%-15s %-12s %4d) %6lds\n",
-                               cur->parkingexten, ast_channel_name(cur->chan), cur->context, cur->exten,
-                               cur->priority,
-                               (long) (cur->start.tv_sec + (cur->parkingtime / 1000) - time(NULL)));
-                       ++lotparked;
-               }
-               AST_LIST_UNLOCK(&curlot->parkings);
-               if (lotparked) {
-                       numparked += lotparked;
-                       ast_cli(a->fd, "   %d parked call%s in parking lot %s\n", lotparked,
-                               ESS(lotparked), curlot->name);
-               }
-
-               ao2_ref(curlot, -1);
-       }
-       ao2_iterator_destroy(&iter);
-
-       ast_cli(a->fd, "---\n%d parked call%s in total.\n", numparked, ESS(numparked));
-
-       return CLI_SUCCESS;
-}
-
 static struct ast_cli_entry cli_features[] = {
        AST_CLI_DEFINE(handle_feature_show, "Lists configured features"),
        AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"),
-       AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"),
 };
 
-static int manager_parkinglot_list(struct mansession *s, const struct message *m)
-{
-       const char *id = astman_get_header(m, "ActionID");
-       char idText[256] = "";
-       struct ao2_iterator iter;
-       struct ast_parkinglot *curlot;
-
-       if (!ast_strlen_zero(id))
-               snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
-       astman_send_ack(s, m, "Parking lots will follow");
-
-       iter = ao2_iterator_init(parkinglots, 0);
-       while ((curlot = ao2_iterator_next(&iter))) {
-               astman_append(s, "Event: Parkinglot\r\n"
-                       "Name: %s\r\n"
-                       "StartExten: %d\r\n"
-                       "StopExten: %d\r\n"
-                       "Timeout: %d\r\n"
-                       "\r\n",
-                       curlot->name,
-                       curlot->cfg.parking_start,
-                       curlot->cfg.parking_stop,
-                       curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime);
-               ao2_ref(curlot, -1);
-       }
-
-       astman_append(s,
-               "Event: ParkinglotsComplete\r\n"
-               "%s"
-               "\r\n",idText);
-
-       return RESULT_SUCCESS;
-}
-
-/*!
- * \brief Dump parking lot status
- * \param s
- * \param m
- *
- * Lock parking lot, iterate list and append parked calls status, unlock parking lot.
- * \return Always RESULT_SUCCESS
- */
-static int manager_parking_status(struct mansession *s, const struct message *m)
-{
-       struct parkeduser *cur;
-       const char *id = astman_get_header(m, "ActionID");
-       char idText[256] = "";
-       struct ao2_iterator iter;
-       struct ast_parkinglot *curlot;
-       int numparked = 0;
-       long now = time(NULL);
-
-       if (!ast_strlen_zero(id))
-               snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
-       astman_send_ack(s, m, "Parked calls will follow");
-
-       iter = ao2_iterator_init(parkinglots, 0);
-       while ((curlot = ao2_iterator_next(&iter))) {
-               AST_LIST_LOCK(&curlot->parkings);
-               AST_LIST_TRAVERSE(&curlot->parkings, cur, list) {
-                       astman_append(s, "Event: ParkedCall\r\n"
-                               "Parkinglot: %s\r\n"
-                               "Exten: %d\r\n"
-                               "Channel: %s\r\n"
-                               "From: %s\r\n"
-                               "Timeout: %ld\r\n"
-                               "Duration: %ld\r\n"
-                               "CallerIDNum: %s\r\n"
-                               "CallerIDName: %s\r\n"
-                               "ConnectedLineNum: %s\r\n"
-                               "ConnectedLineName: %s\r\n"
-                               "%s"
-                               "\r\n",
-                               curlot->name,
-                               cur->parkingnum, ast_channel_name(cur->chan), cur->peername,
-                               (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now,
-                               now - (long) cur->start.tv_sec,
-                               S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""),        /* XXX in other places it is <unknown> */
-                               S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""),
-                               S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""),  /* XXX in other places it is <unknown> */
-                               S_COR(ast_channel_connected(cur->chan)->id.name.valid, ast_channel_connected(cur->chan)->id.name.str, ""),
-                               idText);
-                       ++numparked;
-               }
-               AST_LIST_UNLOCK(&curlot->parkings);
-               ao2_ref(curlot, -1);
-       }
-       ao2_iterator_destroy(&iter);
-
-       astman_append(s,
-               "Event: ParkedCallsComplete\r\n"
-               "Total: %d\r\n"
-               "%s"
-               "\r\n",
-               numparked, idText);
-
-       return RESULT_SUCCESS;
-}
-
 /*!
  * \brief Create manager event for parked calls
  * \param s
@@ -8210,8 +7274,11 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con
                calldurationlimit->tv_usec = (config->timelimit % 1000) * 1000;
                ast_verb(3, "Setting call duration limit to %.3lf seconds.\n",
                        calldurationlimit->tv_sec + calldurationlimit->tv_usec / 1000000.0);
-               config->timelimit = play_to_caller = play_to_callee =
-               config->play_warning = config->warning_freq = 0;
+               play_to_caller = 0;
+               play_to_callee = 0;
+               config->timelimit = 0;
+               config->play_warning = 0;
+               config->warning_freq = 0;
        } else {
                ast_verb(4, "Limit Data for this call:\n");
                ast_verb(4, "timelimit      = %ld ms (%.3lf s)\n", config->timelimit, config->timelimit / 1000.0);
@@ -8242,12 +7309,17 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con
  */
 static int bridge_exec(struct ast_channel *chan, const char *data)
 {
-       struct ast_channel *current_dest_chan, *final_dest_chan, *chans[2];
+       struct ast_channel *current_dest_chan;
+       struct ast_channel *final_dest_chan;
+       struct ast_channel *chans[2];
        char *tmp_data  = NULL;
        struct ast_flags opts = { 0, };
        struct ast_bridge_config bconfig = { { 0, }, };
        char *opt_args[OPT_ARG_ARRAY_SIZE];
        struct timeval calldurationlimit = { 0, };
+       const char *context;
+       const char *extension;
+       int priority;
 
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(dest_chan);
@@ -8357,8 +7429,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
                        "Channel1: %s\r\n"
                        "Channel2: %s\r\n", ast_channel_name(chan), ast_channel_name(final_dest_chan));
 
-               /* Maybe we should return this channel to the PBX? */
-               ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
+               bridge_failed_peer_goto(chan, final_dest_chan);
 
                pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE");
                current_dest_chan = ast_channel_unref(current_dest_chan);
@@ -8406,60 +7477,28 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
        if (ast_test_flag(&opts, OPT_CALLER_PARK))
                ast_set_flag(&(bconfig.features_caller), AST_FEATURE_PARKCALL);
 
-       /*
-        * Don't let the after-bridge code run the h-exten.  We want to
-        * continue in the dialplan.
-        */
-       ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT);
+       /* Setup after bridge goto location. */
+       if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
+               ast_channel_lock(chan);
+               context = ast_strdupa(ast_channel_context(chan));
+               extension = ast_strdupa(ast_channel_exten(chan));
+               priority = ast_channel_priority(chan);
+               ast_channel_unlock(chan);
+               ast_after_bridge_set_go_on(final_dest_chan, context, extension, priority,
+                       opt_args[OPT_ARG_CALLEE_GO_ON]);
+       } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) {
+               ast_channel_lock(final_dest_chan);
+               context = ast_strdupa(ast_channel_context(final_dest_chan));
+               extension = ast_strdupa(ast_channel_exten(final_dest_chan));
+               priority = ast_channel_priority(final_dest_chan);
+               ast_channel_unlock(final_dest_chan);
+               ast_after_bridge_set_goto(final_dest_chan, context, extension, priority);
+       }
+
        ast_bridge_call(chan, final_dest_chan, &bconfig);
 
        /* The bridge has ended, set BRIDGERESULT to SUCCESS. */
        pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS");
-
-       /* If the other channel has not been hung up, return it to the PBX */
-       if (!ast_check_hangup(final_dest_chan)) {
-               if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
-                       char *caller_context;
-                       char *caller_extension;
-                       int caller_priority;
-                       int goto_opt;
-
-                       ast_channel_lock(chan);
-                       caller_context = ast_strdupa(ast_channel_context(chan));
-                       caller_extension = ast_strdupa(ast_channel_exten(chan));
-                       caller_priority = ast_channel_priority(chan);
-                       ast_channel_unlock(chan);
-
-                       if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) {
-                               ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]);
-                               /* Set current dialplan position to bridger dialplan position */
-                               goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority)
-                                       /* Then perform the goto */
-                                       || ast_parseable_goto(final_dest_chan, opt_args[OPT_ARG_CALLEE_GO_ON]);
-                       } else { /* F() */
-                               goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority + 1);
-                       }
-                       if (goto_opt || ast_pbx_start(final_dest_chan)) {
-                               ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-                       }
-               } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) {
-                       ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n",
-                               ast_channel_context(final_dest_chan), ast_channel_exten(final_dest_chan),
-                               ast_channel_priority(final_dest_chan), ast_channel_name(final_dest_chan));
-
-                       if (ast_pbx_start(final_dest_chan)) {
-                               ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", ast_channel_name(final_dest_chan));
-                               ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-                       } else {
-                               ast_debug(1, "SUCCESS continuing PBX on chan %s\n", ast_channel_name(final_dest_chan));
-                       }
-               } else {
-                       ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-               }
-       } else {
-               ast_debug(1, "chan %s was hungup\n", ast_channel_name(final_dest_chan));
-               ast_autoservice_chan_hangup_peer(chan, final_dest_chan);
-       }
 done:
        ast_free((char *) bconfig.warning_sound);
        ast_free((char *) bconfig.end_sound);
@@ -9130,10 +8169,7 @@ static void features_shutdown(void)
        ast_custom_function_unregister(&feature_function);
        ast_manager_unregister("Bridge");
        ast_manager_unregister("Park");
-       ast_manager_unregister("Parkinglots");
-       ast_manager_unregister("ParkedCalls");
-       ast_unregister_application(parkcall);
-       ast_unregister_application(parkedcall);
+
        ast_unregister_application(app_bridge);
 
        pthread_cancel(parking_thread);
@@ -9161,12 +8197,7 @@ int ast_features_init(void)
                return -1;
        }
        ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL);
-       res = ast_register_application2(parkedcall, parked_call_exec, NULL, NULL, NULL);
-       if (!res)
-               res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL);
        if (!res) {
-               ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
-               ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list);
                ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
                ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
        }
@@ -9182,4 +8213,3 @@ int ast_features_init(void)
 
        return res;
 }
-
index b559d552d2255a7a863908f514a4e3ad1acd9a1a..8822261f6f7b720f4f2a1f08f654a9fa9ba66acf 100644 (file)
@@ -635,6 +635,10 @@ void ast_frame_subclass2str(struct ast_frame *f, char *subclass, size_t slen, ch
                /* Should never happen */
                snprintf(subclass, slen, "IAX Frametype %d", f->subclass.integer);
                break;
+       case AST_FRAME_BRIDGE_ACTION:
+               /* Should never happen */
+               snprintf(subclass, slen, "Bridge Frametype %d", f->subclass.integer);
+               break;
        case AST_FRAME_TEXT:
                ast_copy_string(subclass, "N/A", slen);
                if (moreinfo) {
@@ -722,6 +726,10 @@ void ast_frame_type2str(enum ast_frame_type frame_type, char *ftype, size_t len)
                /* Should never happen */
                ast_copy_string(ftype, "IAX Specific", len);
                break;
+       case AST_FRAME_BRIDGE_ACTION:
+               /* Should never happen */
+               ast_copy_string(ftype, "Bridge Specific", len);
+               break;
        case AST_FRAME_TEXT:
                ast_copy_string(ftype, "Text", len);
                break;
index c9b2fbe1e1a74c9a2190f591d7e741586a5e9bf8..c28e6169b24d3a9e49367d1ad4812af98b67eae7 100644 (file)
@@ -94,6 +94,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis.h"
 #include "asterisk/test.h"
 #include "asterisk/json.h"
+#include "asterisk/bridging.h"
 
 /*** DOCUMENTATION
        <manager name="Ping" language="en_US">
@@ -966,6 +967,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                         manager.conf will be present upon starting a new session.</para>
                </description>
        </manager>
+       <manager name="BlindTransfer" language="en_US">
+               <synopsis>
+                       Blind transfer channel(s) to the given destination
+               </synopsis>
+               <syntax>
+                       <parameter name="Channel" required="true">
+                       </parameter>
+                       <parameter name="Context">
+                       </parameter>
+                       <parameter name="Exten">
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Redirect all channels currently bridged to the specified channel to the specified destination.</para>
+               </description>
+               <see-also>
+                       <ref type="manager">Redirect</ref>
+               </see-also>
+       </manager>
  ***/
 
 /*! \addtogroup Group_AMI AMI functions
@@ -3538,7 +3558,8 @@ static int action_status(struct mansession *s, const struct message *m)
        const char *cvariables = astman_get_header(m, "Variables");
        char *variables = ast_strdupa(S_OR(cvariables, ""));
        struct ast_channel *c;
-       char bridge[256];
+       struct ast_bridge *bridge;
+       char bridge_text[256];
        struct timeval now = ast_tvnow();
        long elapsed_seconds = 0;
        int channels = 0;
@@ -3607,44 +3628,52 @@ static int action_status(struct mansession *s, const struct message *m)
                }
 
                channels++;
-               if (ast_channel_internal_bridged_channel(c)) {
-                       snprintf(bridge, sizeof(bridge), "BridgedChannel: %s\r\nBridgedUniqueid: %s\r\n", ast_channel_name(ast_channel_internal_bridged_channel(c)), ast_channel_uniqueid(ast_channel_internal_bridged_channel(c)));
+               bridge = ast_channel_get_bridge(c);
+               if (bridge) {
+                       snprintf(bridge_text, sizeof(bridge_text), "BridgeID: %s\r\n",
+                               bridge->uniqueid);
+                       ao2_ref(bridge, -1);
                } else {
-                       bridge[0] = '\0';
+                       bridge_text[0] = '\0';
                }
                if (ast_channel_pbx(c)) {
                        if (ast_channel_cdr(c)) {
                                elapsed_seconds = now.tv_sec - ast_channel_cdr(c)->start.tv_sec;
                        }
                        astman_append(s,
-                       "Event: Status\r\n"
-                       "Privilege: Call\r\n"
-                       "Channel: %s\r\n"
-                       "CallerIDNum: %s\r\n"
-                       "CallerIDName: %s\r\n"
-                       "ConnectedLineNum: %s\r\n"
-                       "ConnectedLineName: %s\r\n"
-                       "Accountcode: %s\r\n"
-                       "ChannelState: %d\r\n"
-                       "ChannelStateDesc: %s\r\n"
-                       "Context: %s\r\n"
-                       "Extension: %s\r\n"
-                       "Priority: %d\r\n"
-                       "Seconds: %ld\r\n"
-                       "%s"
-                       "Uniqueid: %s\r\n"
-                       "%s"
-                       "%s"
-                       "\r\n",
-                       ast_channel_name(c),
-                       S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "<unknown>"),
-                       S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "<unknown>"),
-                       S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
-                       S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
-                       ast_channel_accountcode(c),
-                       ast_channel_state(c),
-                       ast_state2str(ast_channel_state(c)), ast_channel_context(c),
-                       ast_channel_exten(c), ast_channel_priority(c), (long)elapsed_seconds, bridge, ast_channel_uniqueid(c), ast_str_buffer(str), idText);
+                               "Event: Status\r\n"
+                               "Privilege: Call\r\n"
+                               "Channel: %s\r\n"
+                               "CallerIDNum: %s\r\n"
+                               "CallerIDName: %s\r\n"
+                               "ConnectedLineNum: %s\r\n"
+                               "ConnectedLineName: %s\r\n"
+                               "Accountcode: %s\r\n"
+                               "ChannelState: %d\r\n"
+                               "ChannelStateDesc: %s\r\n"
+                               "Context: %s\r\n"
+                               "Extension: %s\r\n"
+                               "Priority: %d\r\n"
+                               "Seconds: %ld\r\n"
+                               "%s"
+                               "Uniqueid: %s\r\n"
+                               "%s"
+                               "%s"
+                               "\r\n",
+                               ast_channel_name(c),
+                               S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, "<unknown>"),
+                               S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, "<unknown>"),
+                               S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
+                               S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
+                               ast_channel_accountcode(c),
+                               ast_channel_state(c),
+                               ast_state2str(ast_channel_state(c)),
+                               ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
+                               (long) elapsed_seconds,
+                               bridge_text,
+                               ast_channel_uniqueid(c),
+                               ast_str_buffer(str),
+                               idText);
                } else {
                        astman_append(s,
                                "Event: Status\r\n"
@@ -3667,8 +3696,11 @@ static int action_status(struct mansession *s, const struct message *m)
                                S_COR(ast_channel_connected(c)->id.number.valid, ast_channel_connected(c)->id.number.str, "<unknown>"),
                                S_COR(ast_channel_connected(c)->id.name.valid, ast_channel_connected(c)->id.name.str, "<unknown>"),
                                ast_channel_accountcode(c),
-                               ast_state2str(ast_channel_state(c)), bridge, ast_channel_uniqueid(c),
-                               ast_str_buffer(str), idText);
+                               ast_state2str(ast_channel_state(c)),
+                               bridge_text,
+                               ast_channel_uniqueid(c),
+                               ast_str_buffer(str),
+                               idText);
                }
 
                ast_channel_unlock(c);
@@ -3804,12 +3836,6 @@ static int action_redirect(struct mansession *s, const struct message *m)
 
        if (ast_strlen_zero(name2)) {
                /* Single channel redirect in progress. */
-               if (ast_channel_pbx(chan)) {
-                       ast_channel_lock(chan);
-                       /* don't let the after-bridge code run the h-exten */
-                       ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT);
-                       ast_channel_unlock(chan);
-               }
                res = ast_async_goto(chan, context, exten, pi);
                if (!res) {
                        astman_send_ack(s, m, "Redirect successful");
@@ -3837,16 +3863,12 @@ static int action_redirect(struct mansession *s, const struct message *m)
        /* Dual channel redirect in progress. */
        if (ast_channel_pbx(chan)) {
                ast_channel_lock(chan);
-               /* don't let the after-bridge code run the h-exten */
-               ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT
-                       | AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
+               ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
                ast_channel_unlock(chan);
        }
        if (ast_channel_pbx(chan2)) {
                ast_channel_lock(chan2);
-               /* don't let the after-bridge code run the h-exten */
-               ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_HANGUP_DONT
-                       | AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
+               ast_set_flag(ast_channel_flags(chan2), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT);
                ast_channel_unlock(chan2);
        }
        res = ast_async_goto(chan, context, exten, pi);
@@ -3882,6 +3904,51 @@ static int action_redirect(struct mansession *s, const struct message *m)
        return 0;
 }
 
+static int action_blind_transfer(struct mansession *s, const struct message *m)
+{
+       const char *name = astman_get_header(m, "Channel");
+       const char *exten = astman_get_header(m, "Exten");
+       const char *context = astman_get_header(m, "Context");
+       RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+       if (ast_strlen_zero(name)) {
+               astman_send_error(s, m, "No channel specified");
+               return 0;
+       }
+
+       if (ast_strlen_zero(exten)) {
+               astman_send_error(s, m, "No extension specified");
+               return 0;
+       }
+
+       chan = ast_channel_get_by_name(name);
+       if (!chan) {
+               astman_send_error(s, m, "Channel specified does not exist");
+               return 0;
+       }
+
+       if (ast_strlen_zero(context)) {
+               context = ast_channel_context(chan);
+       }
+
+       switch (ast_bridge_transfer_blind(chan, exten, context, NULL, NULL)) {
+       case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+               astman_send_error(s, m, "Transfer not permitted");
+               break;
+       case AST_BRIDGE_TRANSFER_INVALID:
+               astman_send_error(s, m, "Transfer invalid");
+               break;
+       case AST_BRIDGE_TRANSFER_FAIL:
+               astman_send_error(s, m, "Transfer failed");
+               break;
+       case AST_BRIDGE_TRANSFER_SUCCESS:
+               astman_send_ack(s, m, "Transfer succeeded");
+               break;
+       }
+
+       return 0;
+}
+
 static int action_atxfer(struct mansession *s, const struct message *m)
 {
        const char *name = astman_get_header(m, "Channel");
@@ -5683,7 +5750,7 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
                return -1;
        }
 
-       cat_str = authority_to_str (category, &auth);
+       cat_str = authority_to_str(category, &auth);
        ast_str_set(&buf, 0,
                        "Event: %s\r\nPrivilege: %s\r\n",
                         event, cat_str);
@@ -7510,6 +7577,10 @@ static int __init_manager(int reload, int by_external_config)
                return -1;
        }
 
+       if (manager_bridging_init()) {
+               return -1;
+       }
+
        if (!registered) {
                /* Register default actions */
                ast_manager_register_xml_core("Ping", 0, action_ping);
@@ -7547,6 +7618,7 @@ static int __init_manager(int reload, int by_external_config)
                ast_manager_register_xml_core("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
                ast_manager_register_xml_core("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);
                ast_manager_register_xml_core("Filter", EVENT_FLAG_SYSTEM, action_filter);
+               ast_manager_register_xml_core("BlindTransfer", EVENT_FLAG_CALL, action_blind_transfer);
 
 #ifdef TEST_FRAMEWORK
                stasis_subscribe(ast_test_suite_topic(), test_suite_event_cb, NULL);
@@ -8025,3 +8097,44 @@ struct ast_datastore *astman_datastore_find(struct mansession *s, const struct a
 
        return datastore;
 }
+
+static void manager_event_blob_dtor(void *obj)
+{
+       struct ast_manager_event_blob *ev = obj;
+       ast_string_field_free_memory(ev);
+}
+
+struct ast_manager_event_blob *
+__attribute__((format(printf, 3, 4)))
+ast_manager_event_blob_create(
+       int event_flags,
+       const char *manager_event,
+       const char *extra_fields_fmt,
+       ...)
+{
+       RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+       va_list argp;
+
+       ast_assert(extra_fields_fmt != NULL);
+       ast_assert(manager_event != NULL);
+
+       ev = ao2_alloc(sizeof(*ev), manager_event_blob_dtor);
+       if (!ev) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(ev, 20)) {
+               return NULL;
+       }
+
+       ev->manager_event = manager_event;
+       ev->event_flags = event_flags;
+
+       va_start(argp, extra_fields_fmt);
+       ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
+                                     argp);
+       va_end(argp);
+
+       ao2_ref(ev, +1);
+       return ev;
+}
diff --git a/main/manager_bridging.c b/main/manager_bridging.c
new file mode 100644 (file)
index 0000000..01ce68a
--- /dev/null
@@ -0,0 +1,470 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief The Asterisk Management Interface - AMI (bridge event handling)
+ *
+ * \author Kinsey Moore <kmoore@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/manager.h"
+#include "asterisk/stasis_message_router.h"
+
+/*! \brief Message router for cached bridge state snapshot updates */
+static struct stasis_message_router *bridge_state_router;
+
+/*** DOCUMENTATION
+       <managerEvent language="en_US" name="BridgeCreate">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a bridge is created.</synopsis>
+                       <syntax>
+                               <parameter name="BridgeUniqueid">
+                               </parameter>
+                               <parameter name="BridgeType">
+                                       <para>The type of bridge</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="BridgeDestroy">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a bridge is destroyed.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="BridgeEnter">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel enters a bridge.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="Uniqueid">
+                                       <para>The uniqueid of the channel entering the bridge</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="BridgeLeave">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel leaves a bridge.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="Uniqueid">
+                                       <para>The uniqueid of the channel leaving the bridge</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <manager name="BridgeList" language="en_US">
+               <synopsis>
+                       Get a list of bridges in the system.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="BridgeType">
+                               <para>Optional type for filtering the resulting list of bridges.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Returns a list of bridges, optionally filtering on a bridge type.</para>
+               </description>
+       </manager>
+       <manager name="BridgeInfo" language="en_US">
+               <synopsis>
+                       Get information about a bridge.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="BridgeUniqueid" required="true">
+                               <para>The unique ID of the bridge about which to retreive information.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Returns detailed information about a bridge and the channels in it.</para>
+               </description>
+       </manager>
+ ***/
+
+struct ast_str *ast_manager_build_bridge_state_string(
+       const struct ast_bridge_snapshot *snapshot,
+       const char *suffix)
+{
+       struct ast_str *out = ast_str_create(128);
+       int res = 0;
+       if (!out) {
+               return NULL;
+       }
+       res = ast_str_set(&out, 0,
+               "BridgeUniqueid%s: %s\r\n"
+               "BridgeType%s: %s\r\n",
+               suffix, snapshot->uniqueid,
+               suffix, snapshot->technology);
+
+       if (!res) {
+               return NULL;
+       }
+
+       return out;
+}
+
+/*! \brief Typedef for callbacks that get called on channel snapshot updates */
+typedef struct ast_manager_event_blob *(*bridge_snapshot_monitor)(
+       struct ast_bridge_snapshot *old_snapshot,
+       struct ast_bridge_snapshot *new_snapshot);
+
+/*! \brief Handle bridge creation */
+static struct ast_manager_event_blob *bridge_create(
+       struct ast_bridge_snapshot *old_snapshot,
+       struct ast_bridge_snapshot *new_snapshot)
+{
+       if (!new_snapshot || old_snapshot) {
+               return NULL;
+       }
+
+       return ast_manager_event_blob_create(
+               EVENT_FLAG_CALL, "BridgeCreate", NO_EXTRA_FIELDS);
+}
+
+/*! \brief Handle bridge destruction */
+static struct ast_manager_event_blob *bridge_destroy(
+       struct ast_bridge_snapshot *old_snapshot,
+       struct ast_bridge_snapshot *new_snapshot)
+{
+       if (new_snapshot || !old_snapshot) {
+               return NULL;
+       }
+
+       return ast_manager_event_blob_create(
+               EVENT_FLAG_CALL, "BridgeDestroy", NO_EXTRA_FIELDS);
+}
+
+
+bridge_snapshot_monitor bridge_monitors[] = {
+       bridge_create,
+       bridge_destroy,
+};
+
+static void bridge_snapshot_update(void *data, struct stasis_subscription *sub,
+                                   struct stasis_topic *topic,
+                                   struct stasis_message *message)
+{
+       RAII_VAR(struct ast_str *, bridge_event_string, NULL, ast_free);
+       struct stasis_cache_update *update;
+       struct ast_bridge_snapshot *old_snapshot;
+       struct ast_bridge_snapshot *new_snapshot;
+       size_t i;
+
+       update = stasis_message_data(message);
+
+       if (ast_bridge_snapshot_type() != update->type) {
+               return;
+       }
+
+       old_snapshot = stasis_message_data(update->old_snapshot);
+       new_snapshot = stasis_message_data(update->new_snapshot);
+
+       for (i = 0; i < ARRAY_LEN(bridge_monitors); ++i) {
+               RAII_VAR(struct ast_manager_event_blob *, event, NULL, ao2_cleanup);
+
+               event = bridge_monitors[i](old_snapshot, new_snapshot);
+               if (!event) {
+                       continue;
+               }
+
+               /* If we haven't already, build the channel event string */
+               if (!bridge_event_string) {
+                       bridge_event_string =
+                               ast_manager_build_bridge_state_string(
+                                       new_snapshot ? new_snapshot : old_snapshot, "");
+                       if (!bridge_event_string) {
+                               return;
+                       }
+               }
+
+               manager_event(event->event_flags, event->manager_event, "%s%s",
+                       ast_str_buffer(bridge_event_string),
+                       event->extra_fields);
+       }
+}
+
+static void bridge_merge_cb(void *data, struct stasis_subscription *sub,
+                                   struct stasis_topic *topic,
+                                   struct stasis_message *message)
+{
+       struct ast_bridge_merge_message *merge_msg = stasis_message_data(message);
+       RAII_VAR(struct ast_str *, to_text, NULL, ast_free);
+       RAII_VAR(struct ast_str *, from_text, NULL, ast_free);
+
+       ast_assert(merge_msg->to != NULL);
+       ast_assert(merge_msg->from != NULL);
+
+       to_text = ast_manager_build_bridge_state_string(merge_msg->to, "");
+       from_text = ast_manager_build_bridge_state_string(merge_msg->from, "From");
+
+       /*** DOCUMENTATION
+               <managerEventInstance>
+                       <synopsis>Raised when two bridges are merged.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='BridgeCreate']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="BridgeUniqueidFrom">
+                                       <para>The uniqueid of the bridge being dissolved in the merge</para>
+                               </parameter>
+                               <parameter name="BridgeTypeFrom">
+                                       <para>The type of bridge that is being dissolved in the merge</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       ***/
+       manager_event(EVENT_FLAG_CALL, "BridgeMerge",
+               "%s"
+               "%s",
+               ast_str_buffer(to_text),
+               ast_str_buffer(from_text));
+}
+
+static void channel_enter_cb(void *data, struct stasis_subscription *sub,
+                                   struct stasis_topic *topic,
+                                   struct stasis_message *message)
+{
+       struct ast_bridge_blob *blob = stasis_message_data(message);
+       RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+       RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+
+       bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+       channel_text = ast_manager_build_channel_state_string(blob->channel);
+
+       manager_event(EVENT_FLAG_CALL, "BridgeEnter",
+               "%s"
+               "%s",
+               ast_str_buffer(bridge_text),
+               ast_str_buffer(channel_text));
+}
+
+static void channel_leave_cb(void *data, struct stasis_subscription *sub,
+                                   struct stasis_topic *topic,
+                                   struct stasis_message *message)
+{
+       struct ast_bridge_blob *blob = stasis_message_data(message);
+       RAII_VAR(struct ast_str *, bridge_text, NULL, ast_free);
+       RAII_VAR(struct ast_str *, channel_text, NULL, ast_free);
+
+       bridge_text = ast_manager_build_bridge_state_string(blob->bridge, "");
+       channel_text = ast_manager_build_channel_state_string(blob->channel);
+
+       manager_event(EVENT_FLAG_CALL, "BridgeLeave",
+               "%s"
+               "%s",
+               ast_str_buffer(bridge_text),
+               ast_str_buffer(channel_text));
+}
+
+static int filter_bridge_type_cb(void *obj, void *arg, int flags)
+{
+       char *bridge_type = arg;
+       struct ast_bridge_snapshot *snapshot = stasis_message_data(obj);
+       /* unlink all the snapshots that do not match the bridge type */
+       return strcmp(bridge_type, snapshot->technology) ? CMP_MATCH : 0;
+}
+
+static int send_bridge_list_item_cb(void *obj, void *arg, void *data, int flags)
+{
+       struct ast_bridge_snapshot *snapshot = stasis_message_data(obj);
+       struct mansession *s = arg;
+       char *id_text = data;
+       RAII_VAR(struct ast_str *, bridge_info, ast_manager_build_bridge_state_string(snapshot, ""), ast_free);
+
+       astman_append(s,
+               "Event: BridgeListItem\r\n"
+               "%s"
+               "%s"
+               "\r\n",
+               ast_str_buffer(bridge_info),
+               id_text);
+       return 0;
+}
+
+static int manager_bridges_list(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       const char *type_filter = astman_get_header(m, "BridgeType");
+       RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+       RAII_VAR(struct ao2_container *, bridges, NULL, ao2_cleanup);
+
+       if (!id_text) {
+               astman_send_error(s, m, "Internal error");
+               return -1;
+       }
+
+       if (!ast_strlen_zero(id)) {
+               ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+       }
+
+       bridges = stasis_cache_dump(ast_bridge_topic_all_cached(), ast_bridge_snapshot_type());
+       if (!bridges) {
+               astman_send_error(s, m, "Internal error");
+               return -1;
+       }
+
+       astman_send_ack(s, m, "Bridge listing will follow");
+
+       if (!ast_strlen_zero(type_filter)) {
+               char *type_filter_dup = ast_strdupa(type_filter);
+               ao2_callback(bridges, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, filter_bridge_type_cb, type_filter_dup);
+       }
+
+       ao2_callback_data(bridges, OBJ_NODATA, send_bridge_list_item_cb, s, ast_str_buffer(id_text));
+
+       astman_append(s,
+               "Event: BridgeListComplete\r\n"
+               "%s"
+               "\r\n",
+               ast_str_buffer(id_text));
+
+       return 0;
+}
+
+static int send_bridge_info_item_cb(void *obj, void *arg, void *data, int flags)
+{
+       char *uniqueid = obj;
+       struct mansession *s = arg;
+       char *id_text = data;
+
+       astman_append(s,
+               "Event: BridgeInfoChannel\r\n"
+               "Uniqueid: %s\r\n"
+               "%s"
+               "\r\n",
+               uniqueid,
+               id_text);
+       return 0;
+}
+
+static int manager_bridge_info(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       const char *bridge_uniqueid = astman_get_header(m, "BridgeUniqueid");
+       RAII_VAR(struct ast_str *, id_text, ast_str_create(128), ast_free);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_str *, bridge_info, NULL, ast_free);
+       struct ast_bridge_snapshot *snapshot;
+
+       if (!id_text) {
+               astman_send_error(s, m, "Internal error");
+               return -1;
+       }
+
+       if (ast_strlen_zero(bridge_uniqueid)) {
+               astman_send_error(s, m, "BridgeUniqueid must be provided");
+               return -1;
+       }
+
+       if (!ast_strlen_zero(id)) {
+               ast_str_set(&id_text, 0, "ActionID: %s\r\n", id);
+       }
+
+       msg = stasis_cache_get(ast_bridge_topic_all_cached(), ast_bridge_snapshot_type(), bridge_uniqueid);
+       if (!msg) {
+               astman_send_error(s, m, "Specified BridgeUniqueid not found");
+               return -1;
+       }
+
+       astman_send_ack(s, m, "Bridge channel listing will follow");
+
+       snapshot = stasis_message_data(msg);
+       bridge_info = ast_manager_build_bridge_state_string(snapshot, "");
+
+       ao2_callback_data(snapshot->channels, OBJ_NODATA, send_bridge_info_item_cb, s, ast_str_buffer(id_text));
+
+       astman_append(s,
+               "Event: BridgeInfoComplete\r\n"
+               "%s"
+               "%s"
+               "\r\n",
+               ast_str_buffer(bridge_info),
+               ast_str_buffer(id_text));
+
+       return 0;
+}
+
+static void manager_bridging_shutdown(void)
+{
+       stasis_message_router_unsubscribe(bridge_state_router);
+       bridge_state_router = NULL;
+       ast_manager_unregister("BridgeList");
+       ast_manager_unregister("BridgeInfo");
+}
+
+int manager_bridging_init(void)
+{
+       int ret = 0;
+
+       if (bridge_state_router) {
+               /* Already initialized */
+               return 0;
+       }
+
+       ast_register_atexit(manager_bridging_shutdown);
+
+       bridge_state_router = stasis_message_router_create(
+               stasis_caching_get_topic(ast_bridge_topic_all_cached()));
+
+       if (!bridge_state_router) {
+               return -1;
+       }
+
+       ret |= stasis_message_router_add(bridge_state_router,
+                                        stasis_cache_update_type(),
+                                        bridge_snapshot_update,
+                                        NULL);
+
+       ret |= stasis_message_router_add(bridge_state_router,
+                                        ast_bridge_merge_message_type(),
+                                        bridge_merge_cb,
+                                        NULL);
+
+       ret |= stasis_message_router_add(bridge_state_router,
+                                        ast_channel_entered_bridge_type(),
+                                        channel_enter_cb,
+                                        NULL);
+
+       ret |= stasis_message_router_add(bridge_state_router,
+                                        ast_channel_left_bridge_type(),
+                                        channel_leave_cb,
+                                        NULL);
+
+       ret |= ast_manager_register_xml_core("BridgeList", 0, manager_bridges_list);
+       ret |= ast_manager_register_xml_core("BridgeInfo", 0, manager_bridge_info);
+
+       /* If somehow we failed to add any routes, just shut down the whole
+        * thing and fail it.
+        */
+       if (ret) {
+               manager_bridging_shutdown();
+               return -1;
+       }
+
+       return 0;
+}
index 0cab365628086268b10410a20c68ac192cdd9052..fb579dd9505cca0f2200ca41683562fc1e4de681 100644 (file)
@@ -284,83 +284,18 @@ struct ast_str *ast_manager_build_channel_state_string_suffix(
 }
 
 struct ast_str *ast_manager_build_channel_state_string(
-       const struct ast_channel_snapshot *snapshot)
+               const struct ast_channel_snapshot *snapshot)
 {
        return ast_manager_build_channel_state_string_suffix(snapshot, "");
 }
 
-/*! \brief Struct containing info for an AMI channel event to send out. */
-struct snapshot_manager_event {
-       /*! event_flags manager_event() flags parameter. */
-       int event_flags;
-       /*!  manager_event manager_event() category. */
-       const char *manager_event;
-       AST_DECLARE_STRING_FIELDS(
-               /* extra fields to include in the event. */
-               AST_STRING_FIELD(extra_fields);
-               );
-};
-
-static void snapshot_manager_event_dtor(void *obj)
-{
-       struct snapshot_manager_event *ev = obj;
-       ast_string_field_free_memory(ev);
-}
-
-/*!
- * \brief Construct a \ref snapshot_manager_event.
- * \param event_flags manager_event() flags parameter.
- * \param manager_event manager_event() category.
- * \param extra_fields_fmt Format string for extra fields to include.
- *                         Or NO_EXTRA_FIELDS for no extra fields.
- * \return New \ref snapshot_manager_event object.
- * \return \c NULL on error.
- */
-static struct snapshot_manager_event *
-__attribute__((format(printf, 3, 4)))
-snapshot_manager_event_create(
-       int event_flags,
-       const char *manager_event,
-       const char *extra_fields_fmt,
-       ...)
-{
-       RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup);
-       va_list argp;
-
-       ast_assert(extra_fields_fmt != NULL);
-       ast_assert(manager_event != NULL);
-
-       ev = ao2_alloc(sizeof(*ev), snapshot_manager_event_dtor);
-       if (!ev) {
-               return NULL;
-       }
-
-       if (ast_string_field_init(ev, 20)) {
-               return NULL;
-       }
-
-       ev->manager_event = manager_event;
-       ev->event_flags = event_flags;
-
-       va_start(argp, extra_fields_fmt);
-       ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
-                                     argp);
-       va_end(argp);
-
-       ao2_ref(ev, +1);
-       return ev;
-}
-
-/*! GCC warns about blank or NULL format strings. So, shenanigans! */
-#define NO_EXTRA_FIELDS "%s", ""
-
 /*! \brief Typedef for callbacks that get called on channel snapshot updates */
-typedef struct snapshot_manager_event *(*snapshot_monitor)(
+typedef struct ast_manager_event_blob *(*channel_snapshot_monitor)(
        struct ast_channel_snapshot *old_snapshot,
        struct ast_channel_snapshot *new_snapshot);
 
 /*! \brief Handle channel state changes */
-static struct snapshot_manager_event *channel_state_change(
+static struct ast_manager_event_blob *channel_state_change(
        struct ast_channel_snapshot *old_snapshot,
        struct ast_channel_snapshot *new_snapshot)
 {
@@ -377,7 +312,7 @@ static struct snapshot_manager_event *channel_state_change(
         */
 
        if (!old_snapshot) {
-               return snapshot_manager_event_create(
+               return ast_manager_event_blob_create(
                        EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS);
        }
 
@@ -385,7 +320,7 @@ static struct snapshot_manager_event *channel_state_change(
        is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
 
        if (!was_hungup && is_hungup) {
-               return snapshot_manager_event_create(
+               return ast_manager_event_blob_create(
                        EVENT_FLAG_CALL, "Hangup",
                        "Cause: %d\r\n"
                        "Cause-txt: %s\r\n",
@@ -394,7 +329,7 @@ static struct snapshot_manager_event *channel_state_change(
        }
 
        if (old_snapshot->state != new_snapshot->state) {
-               return snapshot_manager_event_create(
+               return ast_manager_event_blob_create(
                        EVENT_FLAG_CALL, "Newstate", NO_EXTRA_FIELDS);
        }
 
@@ -402,7 +337,7 @@ static struct snapshot_manager_event *channel_state_change(
        return NULL;
 }
 
-static struct snapshot_manager_event *channel_newexten(
+static struct ast_manager_event_blob *channel_newexten(
        struct ast_channel_snapshot *old_snapshot,
        struct ast_channel_snapshot *new_snapshot)
 {
@@ -421,7 +356,7 @@ static struct snapshot_manager_event *channel_newexten(
        }
 
        /* DEPRECATED: Extension field deprecated in 12; remove in 14 */
-       return snapshot_manager_event_create(
+       return ast_manager_event_blob_create(
                EVENT_FLAG_CALL, "Newexten",
                "Extension: %s\r\n"
                "Application: %s\r\n"
@@ -431,7 +366,7 @@ static struct snapshot_manager_event *channel_newexten(
                new_snapshot->data);
 }
 
-static struct snapshot_manager_event *channel_new_callerid(
+static struct ast_manager_event_blob *channel_new_callerid(
        struct ast_channel_snapshot *old_snapshot,
        struct ast_channel_snapshot *new_snapshot)
 {
@@ -444,14 +379,14 @@ static struct snapshot_manager_event *channel_new_callerid(
                return NULL;
        }
 
-       return snapshot_manager_event_create(
+       return ast_manager_event_blob_create(
                EVENT_FLAG_CALL, "NewCallerid",
                "CID-CallingPres: %d (%s)\r\n",
                new_snapshot->caller_pres,
                ast_describe_caller_presentation(new_snapshot->caller_pres));
 }
 
-snapshot_monitor monitors[] = {
+channel_snapshot_monitor channel_monitors[] = {
        channel_state_change,
        channel_newexten,
        channel_new_callerid
@@ -476,9 +411,9 @@ static void channel_snapshot_update(void *data, struct stasis_subscription *sub,
        old_snapshot = stasis_message_data(update->old_snapshot);
        new_snapshot = stasis_message_data(update->new_snapshot);
 
-       for (i = 0; i < ARRAY_LEN(monitors); ++i) {
-               RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup);
-               ev = monitors[i](old_snapshot, new_snapshot);
+       for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
+               RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+               ev = channel_monitors[i](old_snapshot, new_snapshot);
 
                if (!ev) {
                        continue;
diff --git a/main/parking.c b/main/parking.c
new file mode 100644 (file)
index 0000000..2a5b72e
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Parking Core
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/parking.h"
+#include "asterisk/channel.h"
+
+/*! \brief Message type for parked calls */
+static struct stasis_message_type *parked_call_type;
+
+/*! \brief Topic for parking lots */
+static struct stasis_topic *parking_topic;
+
+/*! \brief Function Callback for handling blind transfers to park applications */
+static ast_park_blind_xfer_fn ast_park_blind_xfer_func = NULL;
+
+/*! \brief Function Callback for handling a bridge channel trying to park itself */
+static ast_bridge_channel_park_fn ast_bridge_channel_park_func = NULL;
+
+void ast_parking_stasis_init(void)
+{
+       parked_call_type = stasis_message_type_create("ast_parked_call");
+       parking_topic = stasis_topic_create("ast_parking");
+}
+
+void ast_parking_stasis_disable(void)
+{
+       ao2_cleanup(parked_call_type);
+       ao2_cleanup(parking_topic);
+       parked_call_type = NULL;
+       parking_topic = NULL;
+}
+
+struct stasis_topic *ast_parking_topic(void)
+{
+       return parking_topic;
+}
+
+struct stasis_message_type *ast_parked_call_type(void)
+{
+       return parked_call_type;
+}
+
+/*! \brief Destructor for parked_call_payload objects */
+static void parked_call_payload_destructor(void *obj)
+{
+       struct ast_parked_call_payload *park_obj = obj;
+
+       ao2_cleanup(park_obj->parkee);
+       ao2_cleanup(park_obj->parker);
+       ast_string_field_free_memory(park_obj);
+}
+
+struct ast_parked_call_payload *ast_parked_call_payload_create(enum ast_parked_call_event_type event_type,
+               struct ast_channel_snapshot *parkee_snapshot, struct ast_channel_snapshot *parker_snapshot,
+               struct ast_channel_snapshot *retriever_snapshot, const char *parkinglot,
+               unsigned int parkingspace, unsigned long int timeout,
+               unsigned long int duration)
+{
+       RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+
+       payload = ao2_alloc(sizeof(*payload), parked_call_payload_destructor);
+       if (!payload) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(payload, 32)) {
+               return NULL;
+       }
+
+       payload->event_type = event_type;
+
+       ao2_ref(parkee_snapshot, +1);
+       payload->parkee = parkee_snapshot;
+
+       if (parker_snapshot) {
+               ao2_ref(parker_snapshot, +1);
+               payload->parker = parker_snapshot;
+       }
+
+       if (retriever_snapshot) {
+               ao2_ref(retriever_snapshot, +1);
+               payload->retriever = retriever_snapshot;
+       }
+
+       if (parkinglot) {
+               ast_string_field_set(payload, parkinglot, parkinglot);
+       }
+
+       payload->parkingspace = parkingspace;
+       payload->timeout = timeout;
+       payload->duration = duration;
+
+       /* Bump the ref count by one since RAII_VAR is going to eat one when we leave. */
+       ao2_ref(payload, +1);
+       return payload;
+}
+
+void ast_install_park_blind_xfer_func(ast_park_blind_xfer_fn park_blind_xfer_func)
+{
+       ast_park_blind_xfer_func = park_blind_xfer_func;
+}
+
+void ast_install_bridge_channel_park_func(ast_bridge_channel_park_fn bridge_channel_park_func)
+{
+       ast_bridge_channel_park_func = bridge_channel_park_func;
+}
+
+void ast_uninstall_park_blind_xfer_func(void)
+{
+       ast_park_blind_xfer_func = NULL;
+}
+
+void ast_uninstall_bridge_channel_park_func(void)
+{
+       ast_bridge_channel_park_func = NULL;
+}
+
+int ast_park_blind_xfer(struct ast_bridge *bridge, struct ast_bridge_channel *parker,
+               struct ast_exten *park_exten)
+{
+       static int warned = 0;
+       if (ast_park_blind_xfer_func) {
+               return ast_park_blind_xfer_func(bridge, parker, park_exten);
+       }
+
+       if (warned++ % 10 == 0) {
+               ast_verb(3, "%s attempted to blind transfer to a parking extension, but no parking blind transfer function is loaded.\n",
+                       ast_channel_name(parker->chan));
+       }
+
+       return -1;
+}
+
+struct ast_exten *ast_get_parking_exten(const char *exten_str, struct ast_channel *chan, const char *context)
+{
+       struct ast_exten *exten;
+       struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+       const char *app_at_exten;
+
+       ast_debug(4, "Checking if %s@%s is a parking exten\n", exten_str, context);
+       exten = pbx_find_extension(chan, NULL, &q, context, exten_str, 1, NULL, NULL,
+               E_MATCH);
+       if (!exten) {
+               return NULL;
+       }
+
+       app_at_exten = ast_get_extension_app(exten);
+       if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) {
+               return NULL;
+       }
+
+       return exten;
+}
+
+void ast_bridge_channel_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data)
+{
+       /* Run installable function */
+       if (ast_bridge_channel_park_func) {
+               return ast_bridge_channel_park_func(bridge_channel, parkee_uuid, parker_uuid, app_data);
+       }
+}
index 6359c056c2ca924b344e59a14f3693d280e503b0..8408048f2de48cf907e12fc1c96dc37d23c39b76 100644 (file)
@@ -9395,8 +9395,12 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
        } tmpvars = { 0, };
 
        ast_channel_lock(chan);
-       if (ast_channel_pbx(chan)) { /* This channel is currently in the PBX */
-               ast_explicit_goto(chan, context, exten, priority + 1);
+       /* Channels in a bridge or running a PBX can be sent directly to the specified destination */
+       if (ast_channel_is_bridged(chan) || ast_channel_pbx(chan)) {
+               if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+                       priority += 1;
+               }
+               ast_explicit_goto(chan, context, exten, priority);
                ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
                ast_channel_unlock(chan);
                return res;
@@ -9441,8 +9445,7 @@ int ast_async_goto(struct ast_channel *chan, const char *context, const char *ex
 
        /* Masquerade into tmp channel */
        if (ast_channel_masquerade(tmpchan, chan)) {
-               /* Failed to set up the masquerade.  It's probably chan_local
-                * in the middle of optimizing itself out.  Sad. :( */
+               /* Failed to set up the masquerade. */
                ast_hangup(tmpchan);
                tmpchan = NULL;
                res = -1;
index b25cb95b36d58969301497cee38be25e1eb92f80..8e58f658d1f19c0d8f4355cbd610301d2ba5865b 100644 (file)
@@ -928,497 +928,6 @@ struct ast_rtp_glue *ast_rtp_instance_get_glue(const char *type)
        return glue;
 }
 
-static enum ast_bridge_result local_bridge_loop(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_instance *instance0, struct ast_rtp_instance *instance1, int timeoutms, int flags, struct ast_frame **fo, struct ast_channel **rc, void *pvt0, void *pvt1)
-{
-       enum ast_bridge_result res = AST_BRIDGE_FAILED;
-       struct ast_channel *who = NULL, *other = NULL, *cs[3] = { NULL, };
-       struct ast_frame *fr = NULL;
-       struct timeval start;
-
-       /* Start locally bridging both instances */
-       if (instance0->engine->local_bridge && instance0->engine->local_bridge(instance0, instance1)) {
-               ast_debug(1, "Failed to locally bridge %s to %s, backing out.\n", ast_channel_name(c0), ast_channel_name(c1));
-               ast_channel_unlock(c0);
-               ast_channel_unlock(c1);
-               return AST_BRIDGE_FAILED_NOWARN;
-       }
-       if (instance1->engine->local_bridge && instance1->engine->local_bridge(instance1, instance0)) {
-               ast_debug(1, "Failed to locally bridge %s to %s, backing out.\n", ast_channel_name(c1), ast_channel_name(c0));
-               if (instance0->engine->local_bridge) {
-                       instance0->engine->local_bridge(instance0, NULL);
-               }
-               ast_channel_unlock(c0);
-               ast_channel_unlock(c1);
-               return AST_BRIDGE_FAILED_NOWARN;
-       }
-
-       ast_channel_unlock(c0);
-       ast_channel_unlock(c1);
-
-       instance0->bridged = instance1;
-       instance1->bridged = instance0;
-
-       ast_poll_channel_add(c0, c1);
-
-       /* Hop into a loop waiting for a frame from either channel */
-       cs[0] = c0;
-       cs[1] = c1;
-       cs[2] = NULL;
-       start = ast_tvnow();
-       for (;;) {
-               int ms;
-               /* If the underlying formats have changed force this bridge to break */
-               if ((ast_format_cmp(ast_channel_rawreadformat(c0), ast_channel_rawwriteformat(c1)) == AST_FORMAT_CMP_NOT_EQUAL) ||
-                       (ast_format_cmp(ast_channel_rawreadformat(c1), ast_channel_rawwriteformat(c0)) == AST_FORMAT_CMP_NOT_EQUAL)) {
-                       ast_debug(1, "rtp-engine-local-bridge: Oooh, formats changed, backing out\n");
-                       res = AST_BRIDGE_FAILED_NOWARN;
-                       break;
-               }
-               /* Check if anything changed */
-               if ((ast_channel_tech_pvt(c0) != pvt0) ||
-                   (ast_channel_tech_pvt(c1) != pvt1) ||
-                   (ast_channel_masq(c0) || ast_channel_masqr(c0) || ast_channel_masq(c1) || ast_channel_masqr(c1)) ||
-                   (ast_channel_monitor(c0) || ast_channel_audiohooks(c0) || ast_channel_monitor(c1) || ast_channel_audiohooks(c1)) ||
-                   (!ast_framehook_list_is_empty(ast_channel_framehooks(c0)) || !ast_framehook_list_is_empty(ast_channel_framehooks(c1)))) {
-                       ast_debug(1, "rtp-engine-local-bridge: Oooh, something is weird, backing out\n");
-                       /* If a masquerade needs to happen we have to try to read in a frame so that it actually happens. Without this we risk being called again and going into a loop */
-                       if ((ast_channel_masq(c0) || ast_channel_masqr(c0)) && (fr = ast_read(c0))) {
-                               ast_frfree(fr);
-                       }
-                       if ((ast_channel_masq(c1) || ast_channel_masqr(c1)) && (fr = ast_read(c1))) {
-                               ast_frfree(fr);
-                       }
-                       res = AST_BRIDGE_RETRY;
-                       break;
-               }
-               /* Wait on a channel to feed us a frame */
-               ms = ast_remaining_ms(start, timeoutms);
-               if (!(who = ast_waitfor_n(cs, 2, &ms))) {
-                       if (!ms) {
-                               res = AST_BRIDGE_RETRY;
-                               break;
-                       }
-                       ast_debug(2, "rtp-engine-local-bridge: Ooh, empty read...\n");
-                       if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
-                               break;
-                       }
-                       continue;
-               }
-               /* Read in frame from channel */
-               fr = ast_read(who);
-               other = (who == c0) ? c1 : c0;
-               /* Depending on the frame we may need to break out of our bridge */
-               if (!fr || ((fr->frametype == AST_FRAME_DTMF_BEGIN || fr->frametype == AST_FRAME_DTMF_END) &&
-                           ((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) |
-                           ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1)))) {
-                       /* Record received frame and who */
-                       *fo = fr;
-                       *rc = who;
-                       ast_debug(1, "rtp-engine-local-bridge: Ooh, got a %s\n", fr ? "digit" : "hangup");
-                       res = AST_BRIDGE_COMPLETE;
-                       break;
-               } else if ((fr->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
-                       if ((fr->subclass.integer == AST_CONTROL_HOLD) ||
-                           (fr->subclass.integer == AST_CONTROL_UNHOLD) ||
-                           (fr->subclass.integer == AST_CONTROL_VIDUPDATE) ||
-                           (fr->subclass.integer == AST_CONTROL_SRCUPDATE) ||
-                           (fr->subclass.integer == AST_CONTROL_T38_PARAMETERS) ||
-                           (fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
-                               /* If we are going on hold, then break callback mode and P2P bridging */
-                               if (fr->subclass.integer == AST_CONTROL_HOLD) {
-                                       if (instance0->engine->local_bridge) {
-                                               instance0->engine->local_bridge(instance0, NULL);
-                                       }
-                                       if (instance1->engine->local_bridge) {
-                                               instance1->engine->local_bridge(instance1, NULL);
-                                       }
-                                       instance0->bridged = NULL;
-                                       instance1->bridged = NULL;
-                               } else if (fr->subclass.integer == AST_CONTROL_UNHOLD) {
-                                       if (instance0->engine->local_bridge) {
-                                               instance0->engine->local_bridge(instance0, instance1);
-                                       }
-                                       if (instance1->engine->local_bridge) {
-                                               instance1->engine->local_bridge(instance1, instance0);
-                                       }
-                                       instance0->bridged = instance1;
-                                       instance1->bridged = instance0;
-                               }
-                               /* Since UPDATE_BRIDGE_PEER is only used by the bridging code, don't forward it */
-                               if (fr->subclass.integer != AST_CONTROL_UPDATE_RTP_PEER) {
-                                       ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-                               }
-                               ast_frfree(fr);
-                       } else if (fr->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
-                               if (ast_channel_connected_line_sub(who, other, fr, 1) &&
-                                       ast_channel_connected_line_macro(who, other, fr, other == c0, 1)) {
-                                       ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-                               }
-                               ast_frfree(fr);
-                       } else if (fr->subclass.integer == AST_CONTROL_REDIRECTING) {
-                               if (ast_channel_redirecting_sub(who, other, fr, 1) &&
-                                       ast_channel_redirecting_macro(who, other, fr, other == c0, 1)) {
-                                       ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-                               }
-                               ast_frfree(fr);
-                       } else if (fr->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
-                               ast_channel_hangupcause_hash_set(other, fr->data.ptr, fr->datalen);
-                               ast_frfree(fr);
-                       } else {
-                               *fo = fr;
-                               *rc = who;
-                               ast_debug(1, "rtp-engine-local-bridge: Got a FRAME_CONTROL (%d) frame on channel %s\n", fr->subclass.integer, ast_channel_name(who));
-                               res = AST_BRIDGE_COMPLETE;
-                               break;
-                       }
-               } else {
-                       if ((fr->frametype == AST_FRAME_DTMF_BEGIN) ||
-                           (fr->frametype == AST_FRAME_DTMF_END) ||
-                           (fr->frametype == AST_FRAME_VOICE) ||
-                           (fr->frametype == AST_FRAME_VIDEO) ||
-                           (fr->frametype == AST_FRAME_IMAGE) ||
-                           (fr->frametype == AST_FRAME_HTML) ||
-                           (fr->frametype == AST_FRAME_MODEM) ||
-                           (fr->frametype == AST_FRAME_TEXT)) {
-                               ast_write(other, fr);
-                       }
-
-                       ast_frfree(fr);
-               }
-               /* Swap priority */
-               cs[2] = cs[0];
-               cs[0] = cs[1];
-               cs[1] = cs[2];
-       }
-
-       /* Stop locally bridging both instances */
-       if (instance0->engine->local_bridge) {
-               instance0->engine->local_bridge(instance0, NULL);
-       }
-       if (instance1->engine->local_bridge) {
-               instance1->engine->local_bridge(instance1, NULL);
-       }
-
-       instance0->bridged = NULL;
-       instance1->bridged = NULL;
-
-       ast_poll_channel_del(c0, c1);
-
-       return res;
-}
-
-static enum ast_bridge_result remote_bridge_loop(struct ast_channel *c0,
-       struct ast_channel *c1,
-       struct ast_rtp_instance *instance0,
-       struct ast_rtp_instance *instance1,
-       struct ast_rtp_instance *vinstance0,
-       struct ast_rtp_instance *vinstance1,
-       struct ast_rtp_instance *tinstance0,
-       struct ast_rtp_instance *tinstance1,
-       struct ast_rtp_glue *glue0,
-       struct ast_rtp_glue *glue1,
-       struct ast_format_cap *cap0,
-       struct ast_format_cap *cap1,
-       int timeoutms,
-       int flags,
-       struct ast_frame **fo,
-       struct ast_channel **rc,
-       void *pvt0,
-       void *pvt1)
-{
-       enum ast_bridge_result res = AST_BRIDGE_FAILED;
-       struct ast_channel *who = NULL, *other = NULL, *cs[3] = { NULL, };
-       struct ast_format_cap *oldcap0 = ast_format_cap_dup(cap0);
-       struct ast_format_cap *oldcap1 = ast_format_cap_dup(cap1);
-       struct ast_sockaddr ac1 = {{0,}}, vac1 = {{0,}}, tac1 = {{0,}}, ac0 = {{0,}}, vac0 = {{0,}}, tac0 = {{0,}};
-       struct ast_sockaddr t1 = {{0,}}, vt1 = {{0,}}, tt1 = {{0,}}, t0 = {{0,}}, vt0 = {{0,}}, tt0 = {{0,}};
-       struct ast_frame *fr = NULL;
-       struct timeval start;
-
-       if (!oldcap0 || !oldcap1) {
-               ast_channel_unlock(c0);
-               ast_channel_unlock(c1);
-               goto remote_bridge_cleanup;
-       }
-       /* Test the first channel */
-       if (!(glue0->update_peer(c0, instance1, vinstance1, tinstance1, cap1, 0))) {
-               ast_rtp_instance_get_remote_address(instance1, &ac1);
-               if (vinstance1) {
-                       ast_rtp_instance_get_remote_address(vinstance1, &vac1);
-               }
-               if (tinstance1) {
-                       ast_rtp_instance_get_remote_address(tinstance1, &tac1);
-               }
-       } else {
-               ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-       }
-
-       /* Test the second channel */
-       if (!(glue1->update_peer(c1, instance0, vinstance0, tinstance0, cap0, 0))) {
-               ast_rtp_instance_get_remote_address(instance0, &ac0);
-               if (vinstance0) {
-                       ast_rtp_instance_get_remote_address(instance0, &vac0);
-               }
-               if (tinstance0) {
-                       ast_rtp_instance_get_remote_address(instance0, &tac0);
-               }
-       } else {
-               ast_log(LOG_WARNING, "Channel '%s' failed to talk to '%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-       }
-
-       ast_channel_unlock(c0);
-       ast_channel_unlock(c1);
-
-       instance0->bridged = instance1;
-       instance1->bridged = instance0;
-
-       ast_poll_channel_add(c0, c1);
-
-       /* Go into a loop handling any stray frames that may come in */
-       cs[0] = c0;
-       cs[1] = c1;
-       cs[2] = NULL;
-       start = ast_tvnow();
-       for (;;) {
-               int ms;
-               /* Check if anything changed */
-               if ((ast_channel_tech_pvt(c0) != pvt0) ||
-                   (ast_channel_tech_pvt(c1) != pvt1) ||
-                   (ast_channel_masq(c0) || ast_channel_masqr(c0) || ast_channel_masq(c1) || ast_channel_masqr(c1)) ||
-                   (ast_channel_monitor(c0) || ast_channel_audiohooks(c0) || ast_channel_monitor(c1) || ast_channel_audiohooks(c1)) ||
-                   (!ast_framehook_list_is_empty(ast_channel_framehooks(c0)) || !ast_framehook_list_is_empty(ast_channel_framehooks(c1)))) {
-                       ast_debug(1, "Oooh, something is weird, backing out\n");
-                       res = AST_BRIDGE_RETRY;
-                       break;
-               }
-
-               /* Check if they have changed their address */
-               ast_rtp_instance_get_remote_address(instance1, &t1);
-               if (vinstance1) {
-                       ast_rtp_instance_get_remote_address(vinstance1, &vt1);
-               }
-               if (tinstance1) {
-                       ast_rtp_instance_get_remote_address(tinstance1, &tt1);
-               }
-               if (glue1->get_codec) {
-                       ast_format_cap_remove_all(cap1);
-                       glue1->get_codec(c1, cap1);
-               }
-
-               ast_rtp_instance_get_remote_address(instance0, &t0);
-               if (vinstance0) {
-                       ast_rtp_instance_get_remote_address(vinstance0, &vt0);
-               }
-               if (tinstance0) {
-                       ast_rtp_instance_get_remote_address(tinstance0, &tt0);
-               }
-               if (glue0->get_codec) {
-                       ast_format_cap_remove_all(cap0);
-                       glue0->get_codec(c0, cap0);
-               }
-
-               if ((ast_sockaddr_cmp(&t1, &ac1)) ||
-                   (vinstance1 && ast_sockaddr_cmp(&vt1, &vac1)) ||
-                   (tinstance1 && ast_sockaddr_cmp(&tt1, &tac1)) ||
-                   (!ast_format_cap_identical(cap1, oldcap1))) {
-                       char tmp_buf[512] = { 0, };
-                       ast_debug(1, "Oooh, '%s' changed end address to %s (format %s)\n",
-                                 ast_channel_name(c1), ast_sockaddr_stringify(&t1),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
-                       ast_debug(1, "Oooh, '%s' changed end vaddress to %s (format %s)\n",
-                                 ast_channel_name(c1), ast_sockaddr_stringify(&vt1),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
-                       ast_debug(1, "Oooh, '%s' changed end taddress to %s (format %s)\n",
-                                 ast_channel_name(c1), ast_sockaddr_stringify(&tt1),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap1));
-                       ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-                                 ast_channel_name(c1), ast_sockaddr_stringify(&ac1),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
-                       ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-                                 ast_channel_name(c1), ast_sockaddr_stringify(&vac1),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
-                       ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-                                 ast_channel_name(c1), ast_sockaddr_stringify(&tac1),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap1));
-                       if (glue0->update_peer(c0,
-                                              ast_sockaddr_isnull(&t1)  ? NULL : instance1,
-                                              ast_sockaddr_isnull(&vt1) ? NULL : vinstance1,
-                                              ast_sockaddr_isnull(&tt1) ? NULL : tinstance1,
-                                              cap1, 0)) {
-                               ast_log(LOG_WARNING, "Channel '%s' failed to update to '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-                       }
-                       ast_sockaddr_copy(&ac1, &t1);
-                       ast_sockaddr_copy(&vac1, &vt1);
-                       ast_sockaddr_copy(&tac1, &tt1);
-                       ast_format_cap_copy(oldcap1, cap1);
-               }
-               if ((ast_sockaddr_cmp(&t0, &ac0)) ||
-                   (vinstance0 && ast_sockaddr_cmp(&vt0, &vac0)) ||
-                   (tinstance0 && ast_sockaddr_cmp(&tt0, &tac0)) ||
-                   (!ast_format_cap_identical(cap0, oldcap0))) {
-                       char tmp_buf[512] = { 0, };
-                       ast_debug(1, "Oooh, '%s' changed end address to %s (format %s)\n",
-                                 ast_channel_name(c0), ast_sockaddr_stringify(&t0),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), cap0));
-                       ast_debug(1, "Oooh, '%s' was %s/(format %s)\n",
-                                 ast_channel_name(c0), ast_sockaddr_stringify(&ac0),
-                                 ast_getformatname_multiple(tmp_buf, sizeof(tmp_buf), oldcap0));
-                       if (glue1->update_peer(c1, t0.len ? instance0 : NULL,
-                                               vt0.len ? vinstance0 : NULL,
-                                               tt0.len ? tinstance0 : NULL,
-                                               cap0, 0)) {
-                               ast_log(LOG_WARNING, "Channel '%s' failed to update to '%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-                       }
-                       ast_sockaddr_copy(&ac0, &t0);
-                       ast_sockaddr_copy(&vac0, &vt0);
-                       ast_sockaddr_copy(&tac0, &tt0);
-                       ast_format_cap_copy(oldcap0, cap0);
-               }
-
-               ms = ast_remaining_ms(start, timeoutms);
-               /* Wait for frame to come in on the channels */
-               if (!(who = ast_waitfor_n(cs, 2, &ms))) {
-                       if (!ms) {
-                               res = AST_BRIDGE_RETRY;
-                               break;
-                       }
-                       ast_debug(1, "Ooh, empty read...\n");
-                       if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
-                               break;
-                       }
-                       continue;
-               }
-               fr = ast_read(who);
-               other = (who == c0) ? c1 : c0;
-               if (!fr || ((fr->frametype == AST_FRAME_DTMF_BEGIN || fr->frametype == AST_FRAME_DTMF_END) &&
-                           (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) ||
-                            ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) {
-                       /* Break out of bridge */
-                       *fo = fr;
-                       *rc = who;
-                       ast_debug(1, "Oooh, got a %s\n", fr ? "digit" : "hangup");
-                       res = AST_BRIDGE_COMPLETE;
-                       break;
-               } else if ((fr->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) {
-                       if ((fr->subclass.integer == AST_CONTROL_HOLD) ||
-                           (fr->subclass.integer == AST_CONTROL_UNHOLD) ||
-                           (fr->subclass.integer == AST_CONTROL_VIDUPDATE) ||
-                           (fr->subclass.integer == AST_CONTROL_SRCUPDATE) ||
-                           (fr->subclass.integer == AST_CONTROL_T38_PARAMETERS) ||
-                               (fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
-                               if (fr->subclass.integer == AST_CONTROL_HOLD) {
-                                       /* If we someone went on hold we want the other side to reinvite back to us */
-                                       if (who == c0) {
-                                               glue1->update_peer(c1, NULL, NULL, NULL, 0, 0);
-                                       } else {
-                                               glue0->update_peer(c0, NULL, NULL, NULL, 0, 0);
-                                       }
-                               } else if (fr->subclass.integer == AST_CONTROL_UNHOLD ||
-                                       fr->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER) {
-                                       /* If they went off hold they should go back to being direct, or if we have
-                                        * been told to force a peer update, go ahead and do it. */
-                                       if (who == c0) {
-                                               glue1->update_peer(c1, instance0, vinstance0, tinstance0, cap0, 0);
-                                       } else {
-                                               glue0->update_peer(c0, instance1, vinstance1, tinstance1, cap1, 0);
-                                       }
-                               }
-                               /* Update local address information */
-                               ast_rtp_instance_get_remote_address(instance0, &t0);
-                               ast_sockaddr_copy(&ac0, &t0);
-                               ast_rtp_instance_get_remote_address(instance1, &t1);
-                               ast_sockaddr_copy(&ac1, &t1);
-                               /* Update codec information */
-                               if (glue0->get_codec && ast_channel_tech_pvt(c0)) {
-                                       ast_format_cap_remove_all(cap0);
-                                       ast_format_cap_remove_all(oldcap0);
-                                       glue0->get_codec(c0, cap0);
-                                       ast_format_cap_append(oldcap0, cap0);
-
-                               }
-                               if (glue1->get_codec && ast_channel_tech_pvt(c1)) {
-                                       ast_format_cap_remove_all(cap1);
-                                       ast_format_cap_remove_all(oldcap1);
-                                       glue1->get_codec(c1, cap1);
-                                       ast_format_cap_append(oldcap1, cap1);
-                               }
-                               /* Since UPDATE_BRIDGE_PEER is only used by the bridging code, don't forward it */
-                               if (fr->subclass.integer != AST_CONTROL_UPDATE_RTP_PEER) {
-                                       ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-                               }
-                               ast_frfree(fr);
-                       } else if (fr->subclass.integer == AST_CONTROL_CONNECTED_LINE) {
-                               if (ast_channel_connected_line_sub(who, other, fr, 1) &&
-                                       ast_channel_connected_line_macro(who, other, fr, other == c0, 1)) {
-                                       ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-                               }
-                               ast_frfree(fr);
-                       } else if (fr->subclass.integer == AST_CONTROL_REDIRECTING) {
-                               if (ast_channel_redirecting_sub(who, other, fr, 1) &&
-                                       ast_channel_redirecting_macro(who, other, fr, other == c0, 1)) {
-                                       ast_indicate_data(other, fr->subclass.integer, fr->data.ptr, fr->datalen);
-                               }
-                               ast_frfree(fr);
-                       } else if (fr->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
-                               ast_channel_hangupcause_hash_set(other, fr->data.ptr, fr->datalen);
-                               ast_frfree(fr);
-                       } else {
-                               *fo = fr;
-                               *rc = who;
-                               ast_debug(1, "Got a FRAME_CONTROL (%d) frame on channel %s\n", fr->subclass.integer, ast_channel_name(who));
-                               res = AST_BRIDGE_COMPLETE;
-                               goto remote_bridge_cleanup;
-                       }
-               } else {
-                       if ((fr->frametype == AST_FRAME_DTMF_BEGIN) ||
-                           (fr->frametype == AST_FRAME_DTMF_END) ||
-                           (fr->frametype == AST_FRAME_VOICE) ||
-                           (fr->frametype == AST_FRAME_VIDEO) ||
-                           (fr->frametype == AST_FRAME_IMAGE) ||
-                           (fr->frametype == AST_FRAME_HTML) ||
-                           (fr->frametype == AST_FRAME_MODEM) ||
-                           (fr->frametype == AST_FRAME_TEXT)) {
-                               ast_write(other, fr);
-                       }
-                       ast_frfree(fr);
-               }
-               /* Swap priority */
-               cs[2] = cs[0];
-               cs[0] = cs[1];
-               cs[1] = cs[2];
-       }
-
-       if (ast_test_flag(ast_channel_flags(c0), AST_FLAG_ZOMBIE)) {
-               ast_debug(1, "Channel '%s' Zombie cleardown from bridge\n", ast_channel_name(c0));
-       } else if (ast_channel_tech_pvt(c0) != pvt0) {
-               ast_debug(1, "Channel c0->'%s' pvt changed, in bridge with c1->'%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-       } else if (glue0 != ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) {
-               ast_debug(1, "Channel c0->'%s' technology changed, in bridge with c1->'%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-       } else if (glue0->update_peer(c0, NULL, NULL, NULL, 0, 0)) {
-               ast_log(LOG_WARNING, "Channel '%s' failed to break RTP bridge\n", ast_channel_name(c0));
-       }
-       if (ast_test_flag(ast_channel_flags(c1), AST_FLAG_ZOMBIE)) {
-               ast_debug(1, "Channel '%s' Zombie cleardown from bridge\n", ast_channel_name(c1));
-       } else if (ast_channel_tech_pvt(c1) != pvt1) {
-               ast_debug(1, "Channel c1->'%s' pvt changed, in bridge with c0->'%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-       } else if (glue1 != ast_rtp_instance_get_glue(ast_channel_tech(c1)->type)) {
-               ast_debug(1, "Channel c1->'%s' technology changed, in bridge with c0->'%s'\n", ast_channel_name(c1), ast_channel_name(c0));
-       } else if (glue1->update_peer(c1, NULL, NULL, NULL, 0, 0)) {
-               ast_log(LOG_WARNING, "Channel '%s' failed to break RTP bridge\n", ast_channel_name(c1));
-       }
-
-       instance0->bridged = NULL;
-       instance1->bridged = NULL;
-
-       ast_poll_channel_del(c0, c1);
-
-remote_bridge_cleanup:
-       ast_format_cap_destroy(oldcap0);
-       ast_format_cap_destroy(oldcap1);
-
-       return res;
-}
-
 /*!
  * \brief Conditionally unref an rtp instance
  */
@@ -1430,184 +939,14 @@ static void unref_instance_cond(struct ast_rtp_instance **instance)
        }
 }
 
-enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
+struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance)
 {
-       struct ast_rtp_instance *instance0 = NULL, *instance1 = NULL,
-                       *vinstance0 = NULL, *vinstance1 = NULL,
-                       *tinstance0 = NULL, *tinstance1 = NULL;
-       struct ast_rtp_glue *glue0, *glue1;
-       struct ast_sockaddr addr1 = { {0, }, }, addr2 = { {0, }, };
-       enum ast_rtp_glue_result audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID, video_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
-       enum ast_rtp_glue_result audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID, video_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
-       enum ast_bridge_result res = AST_BRIDGE_FAILED;
-       enum ast_rtp_dtmf_mode dmode;
-       struct ast_format_cap *cap0 = ast_format_cap_alloc_nolock();
-       struct ast_format_cap *cap1 = ast_format_cap_alloc_nolock();
-       int unlock_chans = 1;
-       int read_ptime0, read_ptime1, write_ptime0, write_ptime1;
-
-       if (!cap0 || !cap1) {
-               unlock_chans = 0;
-               goto done;
-       }
-
-       /* Lock both channels so we can look for the glue that binds them together */
-       ast_channel_lock_both(c0, c1);
-
-       /* Ensure neither channel got hungup during lock avoidance */
-       if (ast_check_hangup(c0) || ast_check_hangup(c1)) {
-               ast_log(LOG_WARNING, "Got hangup while attempting to bridge '%s' and '%s'\n", ast_channel_name(c0), ast_channel_name(c1));
-               goto done;
-       }
-
-       /* Grab glue that binds each channel to something using the RTP engine */
-       if (!(glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) || !(glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {
-               ast_debug(1, "Can't find native functions for channel '%s'\n", glue0 ? ast_channel_name(c1) : ast_channel_name(c0));
-               goto done;
-       }
-
-       audio_glue0_res = glue0->get_rtp_info(c0, &instance0);
-       video_glue0_res = glue0->get_vrtp_info ? glue0->get_vrtp_info(c0, &vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
-
-       audio_glue1_res = glue1->get_rtp_info(c1, &instance1);
-       video_glue1_res = glue1->get_vrtp_info ? glue1->get_vrtp_info(c1, &vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
-
-       /* Apply any limitations on direct media bridging that may be present */
-       if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
-               if (glue0->allow_rtp_remote && !(glue0->allow_rtp_remote(c0, instance1))) {
-                       /* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
-                       audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-               } else if (glue1->allow_rtp_remote && !(glue1->allow_rtp_remote(c1, instance0))) {
-                       audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-               }
-       }
-       if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
-               if (glue0->allow_vrtp_remote && !(glue0->allow_vrtp_remote(c0, instance1))) {
-                       /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
-                       video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-               } else if (glue1->allow_vrtp_remote && !(glue1->allow_vrtp_remote(c1, instance0))) {
-                       video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-               }
-       }
-
-       /* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
-       if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
-               audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
-       }
-       if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
-               audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
-       }
-
-       /* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
-       if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID || audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {
-               res = AST_BRIDGE_FAILED_NOWARN;
-               goto done;
-       }
-
-
-       /* If address families differ, force a local bridge */
-       ast_rtp_instance_get_remote_address(instance0, &addr1);
-       ast_rtp_instance_get_remote_address(instance1, &addr2);
-
-       if (addr1.ss.ss_family != addr2.ss.ss_family ||
-          (ast_sockaddr_is_ipv4_mapped(&addr1) != ast_sockaddr_is_ipv4_mapped(&addr2))) {
-               audio_glue0_res = AST_RTP_GLUE_RESULT_LOCAL;
-               audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
-       }
-
-       /* If we need to get DTMF see if we can do it outside of the RTP stream itself */
-       dmode = ast_rtp_instance_dtmf_mode_get(instance0);
-       if ((flags & AST_BRIDGE_DTMF_CHANNEL_0) && dmode) {
-               res = AST_BRIDGE_FAILED_NOWARN;
-               goto done;
-       }
-       dmode = ast_rtp_instance_dtmf_mode_get(instance1);
-       if ((flags & AST_BRIDGE_DTMF_CHANNEL_1) && dmode) {
-               res = AST_BRIDGE_FAILED_NOWARN;
-               goto done;
-       }
-
-       /* If we have gotten to a local bridge make sure that both sides have the same local bridge callback and that they are DTMF compatible */
-       if ((audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL || audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL)
-               && (instance0->engine->local_bridge != instance1->engine->local_bridge
-                       || (instance0->engine->dtmf_compatible && !instance0->engine->dtmf_compatible(c0, instance0, c1, instance1)))) {
-               res = AST_BRIDGE_FAILED_NOWARN;
-               goto done;
-       }
-
-       /* Make sure that codecs match */
-       if (glue0->get_codec){
-               glue0->get_codec(c0, cap0);
-       }
-       if (glue1->get_codec) {
-               glue1->get_codec(c1, cap1);
-       }
-       if (!ast_format_cap_is_empty(cap0) && !ast_format_cap_is_empty(cap1) && !ast_format_cap_has_joint(cap0, cap1)) {
-               char tmp0[256] = { 0, };
-               char tmp1[256] = { 0, };
-               ast_debug(1, "Channel codec0 = %s is not codec1 = %s, cannot native bridge in RTP.\n",
-                       ast_getformatname_multiple(tmp0, sizeof(tmp0), cap0),
-                       ast_getformatname_multiple(tmp1, sizeof(tmp1), cap1));
-               res = AST_BRIDGE_FAILED_NOWARN;
-               goto done;
-       }
-
-       read_ptime0 = (ast_codec_pref_getsize(&instance0->codecs.pref, ast_channel_rawreadformat(c0))).cur_ms;
-       read_ptime1 = (ast_codec_pref_getsize(&instance1->codecs.pref, ast_channel_rawreadformat(c1))).cur_ms;
-       write_ptime0 = (ast_codec_pref_getsize(&instance0->codecs.pref, ast_channel_rawwriteformat(c0))).cur_ms;
-       write_ptime1 = (ast_codec_pref_getsize(&instance1->codecs.pref, ast_channel_rawwriteformat(c1))).cur_ms;
-
-       if (read_ptime0 != write_ptime1 || read_ptime1 != write_ptime0) {
-               ast_debug(1, "Packetization differs between RTP streams (%d != %d or %d != %d). Cannot native bridge in RTP\n",
-                               read_ptime0, write_ptime1, read_ptime1, write_ptime0);
-               res = AST_BRIDGE_FAILED_NOWARN;
-               goto done;
-       }
-
-       instance0->glue = glue0;
-       instance1->glue = glue1;
-       instance0->chan = c0;
-       instance1->chan = c1;
-
-       /* Depending on the end result for bridging either do a local bridge or remote bridge */
-       if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL || audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {
-               ast_verb(3, "Locally bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
-               res = local_bridge_loop(c0, c1, instance0, instance1, timeoutms, flags, fo, rc, ast_channel_tech_pvt(c0), ast_channel_tech_pvt(c1));
-       } else {
-               ast_verb(3, "Remotely bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
-               res = remote_bridge_loop(c0, c1, instance0, instance1, vinstance0, vinstance1,
-                               tinstance0, tinstance1, glue0, glue1, cap0, cap1, timeoutms, flags,
-                               fo, rc, ast_channel_tech_pvt(c0), ast_channel_tech_pvt(c1));
-       }
-
-       instance0->glue = NULL;
-       instance1->glue = NULL;
-       instance0->chan = NULL;
-       instance1->chan = NULL;
-
-       unlock_chans = 0;
-
-done:
-       if (unlock_chans) {
-               ast_channel_unlock(c0);
-               ast_channel_unlock(c1);
-       }
-       ast_format_cap_destroy(cap1);
-       ast_format_cap_destroy(cap0);
-
-       unref_instance_cond(&instance0);
-       unref_instance_cond(&instance1);
-       unref_instance_cond(&vinstance0);
-       unref_instance_cond(&vinstance1);
-       unref_instance_cond(&tinstance0);
-       unref_instance_cond(&tinstance1);
-
-       return res;
+       return instance->bridged;
 }
 
-struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance)
+void ast_rtp_instance_set_bridged(struct ast_rtp_instance *instance, struct ast_rtp_instance *bridged)
 {
-       return instance->bridged;
+       instance->bridged = bridged;
 }
 
 void ast_rtp_instance_early_bridge_make_compatible(struct ast_channel *c0, struct ast_channel *c1)
diff --git a/main/stasis_bridging.c b/main/stasis_bridging.c
new file mode 100644 (file)
index 0000000..2ee4fcf
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Stasis Messages and Data Types for Bridge Objects
+ *
+ * \author Kinsey Moore <kmoore@digium.com>
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astobj2.h"
+#include "asterisk/stasis.h"
+#include "asterisk/channel.h"
+#include "asterisk/stasis_bridging.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+
+#define SNAPSHOT_CHANNELS_BUCKETS 13
+
+/*!
+ * @{ \brief Define bridge message types.
+ */
+STASIS_MESSAGE_TYPE_DEFN(ast_bridge_snapshot_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_bridge_merge_message_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_entered_bridge_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_left_bridge_type);
+/*! @} */
+
+/*! \brief Aggregate topic for bridge messages */
+static struct stasis_topic *bridge_topic_all;
+
+/*! \brief Caching aggregate topic for bridge snapshots */
+static struct stasis_caching_topic *bridge_topic_all_cached;
+
+/*! \brief Topic pool for individual bridge topics */
+static struct stasis_topic_pool *bridge_topic_pool;
+
+/*! \brief Destructor for bridge snapshots */
+static void bridge_snapshot_dtor(void *obj)
+{
+       struct ast_bridge_snapshot *snapshot = obj;
+       ast_string_field_free_memory(snapshot);
+       ao2_cleanup(snapshot->channels);
+       snapshot->channels = NULL;
+}
+
+struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge)
+{
+       RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+       struct ast_bridge_channel *bridge_channel;
+
+       snapshot = ao2_alloc(sizeof(*snapshot), bridge_snapshot_dtor);
+       if (!snapshot || ast_string_field_init(snapshot, 128)) {
+               return NULL;
+       }
+
+       snapshot->channels = ast_str_container_alloc(SNAPSHOT_CHANNELS_BUCKETS);
+       if (!snapshot->channels) {
+               return NULL;
+       }
+
+       AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+               if (ast_str_container_add(snapshot->channels,
+                               ast_channel_uniqueid(bridge_channel->chan))) {
+                       return NULL;
+               }
+       }
+
+       ast_string_field_set(snapshot, uniqueid, bridge->uniqueid);
+       ast_string_field_set(snapshot, technology, bridge->technology->name);
+
+       snapshot->feature_flags = bridge->feature_flags;
+       snapshot->num_channels = bridge->num_channels;
+       snapshot->num_active = bridge->num_active;
+
+       ao2_ref(snapshot, +1);
+       return snapshot;
+}
+
+struct stasis_topic *ast_bridge_topic(struct ast_bridge *bridge)
+{
+       struct stasis_topic *bridge_topic = stasis_topic_pool_get_topic(bridge_topic_pool, bridge->uniqueid);
+       if (!bridge_topic) {
+               return ast_bridge_topic_all();
+       }
+       return bridge_topic;
+}
+
+struct stasis_topic *ast_bridge_topic_all(void)
+{
+       return bridge_topic_all;
+}
+
+struct stasis_caching_topic *ast_bridge_topic_all_cached(void)
+{
+       return bridge_topic_all_cached;
+}
+
+void ast_bridge_publish_state(struct ast_bridge *bridge)
+{
+       RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       ast_assert(bridge != NULL);
+
+       snapshot = ast_bridge_snapshot_create(bridge);
+       if (!snapshot) {
+               return;
+       }
+
+       msg = stasis_message_create(ast_bridge_snapshot_type(), snapshot);
+       if (!msg) {
+               return;
+       }
+
+       stasis_publish(ast_bridge_topic(bridge), msg);
+}
+
+static void bridge_publish_state_from_blob(struct ast_bridge_blob *obj)
+{
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       ast_assert(obj != NULL);
+
+       msg = stasis_message_create(ast_bridge_snapshot_type(), obj->bridge);
+       if (!msg) {
+               return;
+       }
+
+       stasis_publish(stasis_topic_pool_get_topic(bridge_topic_pool, obj->bridge->uniqueid), msg);
+}
+
+/*! \brief Destructor for bridge merge messages */
+static void bridge_merge_message_dtor(void *obj)
+{
+       struct ast_bridge_merge_message *msg = obj;
+
+       ao2_cleanup(msg->to);
+       msg->to = NULL;
+       ao2_cleanup(msg->from);
+       msg->from = NULL;
+}
+
+/*! \brief Bridge merge message creation helper */
+static struct ast_bridge_merge_message *bridge_merge_message_create(struct ast_bridge *to, struct ast_bridge *from)
+{
+       RAII_VAR(struct ast_bridge_merge_message *, msg, NULL, ao2_cleanup);
+
+       msg = ao2_alloc(sizeof(*msg), bridge_merge_message_dtor);
+       if (!msg) {
+               return NULL;
+       }
+
+       msg->to = ast_bridge_snapshot_create(to);
+       if (!msg->to) {
+               return NULL;
+       }
+
+       msg->from = ast_bridge_snapshot_create(from);
+       if (!msg->from) {
+               return NULL;
+       }
+
+       ao2_ref(msg, +1);
+       return msg;
+}
+
+void ast_bridge_publish_merge(struct ast_bridge *to, struct ast_bridge *from)
+{
+       RAII_VAR(struct ast_bridge_merge_message *, merge_msg, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       ast_assert(to != NULL);
+       ast_assert(from != NULL);
+
+       merge_msg = bridge_merge_message_create(to, from);
+       if (!merge_msg) {
+               return;
+       }
+
+       msg = stasis_message_create(ast_bridge_merge_message_type(), merge_msg);
+       if (!msg) {
+               return;
+       }
+
+       stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+static void bridge_blob_dtor(void *obj)
+{
+       struct ast_bridge_blob *event = obj;
+       ao2_cleanup(event->bridge);
+       event->bridge = NULL;
+       ao2_cleanup(event->channel);
+       event->channel = NULL;
+       ast_json_unref(event->blob);
+       event->blob = NULL;
+}
+
+struct stasis_message *ast_bridge_blob_create(
+       struct stasis_message_type *message_type,
+       struct ast_bridge *bridge,
+       struct ast_channel *chan,
+       struct ast_json *blob)
+{
+       RAII_VAR(struct ast_bridge_blob *, obj, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       obj = ao2_alloc(sizeof(*obj), bridge_blob_dtor);
+       if (!obj) {
+               return NULL;
+       }
+
+       if (bridge) {
+               obj->bridge = ast_bridge_snapshot_create(bridge);
+               if (obj->bridge == NULL) {
+                       return NULL;
+               }
+       }
+
+       if (chan) {
+               obj->channel = ast_channel_snapshot_create(chan);
+               if (obj->channel == NULL) {
+                       return NULL;
+               }
+       }
+
+       if (blob) {
+               obj->blob = ast_json_ref(blob);
+       }
+
+       msg = stasis_message_create(message_type, obj);
+       if (!msg) {
+               return NULL;
+       }
+
+       ao2_ref(msg, +1);
+       return msg;
+}
+
+const char *ast_bridge_blob_json_type(struct ast_bridge_blob *obj)
+{
+       if (obj == NULL) {
+               return NULL;
+       }
+
+       return ast_json_string_get(ast_json_object_get(obj->blob, "type"));
+}
+
+void ast_bridge_publish_enter(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       msg = ast_bridge_blob_create(ast_channel_entered_bridge_type(), bridge, chan, NULL);
+       if (!msg) {
+               return;
+       }
+
+       /* enter blob first, then state */
+       stasis_publish(ast_bridge_topic(bridge), msg);
+       bridge_publish_state_from_blob(stasis_message_data(msg));
+}
+
+void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       msg = ast_bridge_blob_create(ast_channel_left_bridge_type(), bridge, chan, NULL);
+       if (!msg) {
+               return;
+       }
+
+       /* state first, then leave blob (opposite of enter, preserves nesting of events) */
+       bridge_publish_state_from_blob(stasis_message_data(msg));
+       stasis_publish(ast_bridge_topic(bridge), msg);
+}
+
+struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot)
+{
+       RAII_VAR(struct ast_json *, json_chan, NULL, ast_json_unref);
+       int r = 0;
+
+       if (snapshot == NULL) {
+               return NULL;
+       }
+
+       json_chan = ast_json_object_create();
+       if (!json_chan) { ast_log(LOG_ERROR, "Error creating channel json object\n"); return NULL; }
+
+       r = ast_json_object_set(json_chan, "bridge-uniqueid", ast_json_string_create(snapshot->uniqueid));
+       if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; }
+       r = ast_json_object_set(json_chan, "bridge-technology", ast_json_string_create(snapshot->technology));
+       if (r) { ast_log(LOG_ERROR, "Error adding attrib to channel json object\n"); return NULL; }
+
+       return ast_json_ref(json_chan);
+}
+
+void ast_stasis_bridging_shutdown(void)
+{
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_snapshot_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_merge_message_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_entered_bridge_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_left_bridge_type);
+       ao2_cleanup(bridge_topic_all);
+       bridge_topic_all = NULL;
+       bridge_topic_all_cached = stasis_caching_unsubscribe(bridge_topic_all_cached);
+       ao2_cleanup(bridge_topic_pool);
+       bridge_topic_pool = NULL;
+}
+
+/*! \brief snapshot ID getter for caching topic */
+static const char *bridge_snapshot_get_id(struct stasis_message *msg)
+{
+       struct ast_bridge_snapshot *snapshot;
+       if (stasis_message_type(msg) != ast_bridge_snapshot_type()) {
+               return NULL;
+       }
+       snapshot = stasis_message_data(msg);
+       return snapshot->uniqueid;
+}
+
+int ast_stasis_bridging_init(void)
+{
+       STASIS_MESSAGE_TYPE_INIT(ast_bridge_snapshot_type);
+       STASIS_MESSAGE_TYPE_INIT(ast_bridge_merge_message_type);
+       STASIS_MESSAGE_TYPE_INIT(ast_channel_entered_bridge_type);
+       STASIS_MESSAGE_TYPE_INIT(ast_channel_left_bridge_type);
+       bridge_topic_all = stasis_topic_create("ast_bridge_topic_all");
+       bridge_topic_all_cached = stasis_caching_topic_create(bridge_topic_all, bridge_snapshot_get_id);
+       bridge_topic_pool = stasis_topic_pool_create(bridge_topic_all);
+       return !bridge_topic_all
+               || !bridge_topic_all_cached
+               || !bridge_topic_pool ? -1 : 0;
+}
index 47715e63ea0f9b05abbd7935fbd33e64cb0aeeb8..d004da2278f2504284064f3f8ebba85d591f17b8 100644 (file)
@@ -160,3 +160,4 @@ char *__ast_str_helper2(struct ast_str **buf, ssize_t maxlen, const char *src, s
        return (*buf)->__AST_STR_STR;
 }
 
+
index 2ed719093fb9b65e33eda543fdbbfb2858d1967e..667e097e8c524a30993a5a720d9cb3b9ab754f7b 100644 (file)
@@ -75,6 +75,10 @@ ael/pval.o: ael/pval.c
 clean::
        rm -f snmp/*.[oi] ael/*.[oi] ais/*.[oi] stasis_http/*.[oi]
        rm -f res_sip/*.[oi] stasis/*.[oi]
+       rm -f parking/*.o parking/*.i
+
+$(if $(filter res_parking,$(EMBEDDED_MODS)),modules.link,res_parking.so): $(subst .c,.o,$(wildcard parking/*.c))
+$(subst .c,.o,$(wildcard parking/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_parking)
 
 # Dependencies for res_stasis_http_*.so are generated, so they're in this file
 include stasis_http.make
diff --git a/res/parking/parking_applications.c b/res/parking/parking_applications.c
new file mode 100644 (file)
index 0000000..097329b
--- /dev/null
@@ -0,0 +1,801 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Call Parking Applications
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/say.h"
+#include "asterisk/features.h"
+#include "asterisk/bridging_basic.h"
+
+/*** DOCUMENTATION
+       <application name="Park" language="en_US">
+               <synopsis>
+                       Park yourself.
+               </synopsis>
+               <syntax>
+                       <parameter name="parking_lot_name">
+                               <para>Specify in which parking lot to park a call.</para>
+                               <para>The parking lot used is selected in the following order:</para>
+                               <para>1) parking_lot_name option to this application</para>
+                               <para>2) <variable>PARKINGLOT</variable> variable</para>
+                               <para>3) <literal>CHANNEL(parkinglot)</literal> function
+                               (Possibly preset by the channel driver.)</para>
+                               <para>4) Default parking lot.</para>
+                       </parameter>
+                       <parameter name="options">
+                               <para>A list of options for this parked call.</para>
+                               <optionlist>
+                                       <option name="r">
+                                               <para>Send ringing instead of MOH to the parked call.</para>
+                                       </option>
+                                       <option name="R">
+                                               <para>Randomize the selection of a parking space.</para>
+                                       </option>
+                                       <option name="s">
+                                               <para>Silence announcement of the parking space number.</para>
+                                       </option>
+                                       <option name="c" argsep=",">
+                                               <argument name="context" required="false" />
+                                               <argument name="extension" required="false" />
+                                               <argument name="priority" required="true" />
+                                               <para>If the parking times out, go to this place in the dialplan
+                                                       instead of where the parking lot defines the call should go.
+                                               </para>
+                                       </option>
+                                       <option name="t">
+                                               <argument name="duration" required="true" />
+                                               <para>Use a timeout of <literal>duration</literal> seconds instead
+                                                       of the timeout specified by the parking lot.</para>
+                                       </option>
+                               </optionlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Used to park yourself (typically in combination with an attended
+                       transfer to know the parking space).</para>
+                       <para>If you set the <variable>PARKINGEXTEN</variable> variable to a
+                               parking space extension in the parking lot, Park() will attempt to park the
+                               call on that extension. If the extension is already in use then execution
+                               will continue at the next priority.
+                       </para>
+               </description>
+               <see-also>
+                       <ref type="application">ParkedCall</ref>
+               </see-also>
+       </application>
+
+       <application name="ParkedCall" language="en_US">
+               <synopsis>
+                       Retrieve a parked call.
+               </synopsis>
+               <syntax>
+                       <parameter name="parking_lot_name">
+                               <para>Specify from which parking lot to retrieve a parked call.</para>
+                               <para>The parking lot used is selected in the following order:</para>
+                               <para>1) parking_lot_name option</para>
+                               <para>2) <variable>PARKINGLOT</variable> variable</para>
+                               <para>3) <literal>CHANNEL(parkinglot)</literal> function
+                               (Possibly preset by the channel driver.)</para>
+                               <para>4) Default parking lot.</para>
+                       </parameter>
+                       <parameter name="parking_space">
+                               <para>Parking space to retrieve a parked call from.
+                               If not provided then the first available parked call in the
+                               parking lot will be retrieved.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Used to retrieve a parked call from a parking lot.</para>
+                       <note>
+                               <para>If a parking lot's parkext option is set, then Parking lots
+                               will automatically create and manage dialplan extensions in
+                               the parking lot context. If that is the case then you will not
+                               need to manage parking extensions yourself, just include the
+                               parking context of the parking lot.</para>
+                       </note>
+               </description>
+               <see-also>
+                       <ref type="application">Park</ref>
+               </see-also>
+       </application>
+
+       <application name="ParkAndAnnounce" language="en_US">
+               <synopsis>
+                       Park and Announce.
+               </synopsis>
+               <syntax>
+                       <parameter name="parking_lot_name">
+                               <para>Specify in which parking lot to park a call.</para>
+                               <para>The parking lot used is selected in the following order:</para>
+                               <para>1) parking_lot_name option to this application</para>
+                               <para>2) <variable>PARKINGLOT</variable> variable</para>
+                               <para>3) <literal>CHANNEL(parkinglot)</literal> function
+                               (Possibly preset by the channel driver.)</para>
+                               <para>4) Default parking lot.</para>
+                       </parameter>
+                       <parameter name="options">
+                               <para>A list of options for this parked call.</para>
+                               <optionlist>
+                                       <option name="r">
+                                               <para>Send ringing instead of MOH to the parked call.</para>
+                                       </option>
+                                       <option name="R">
+                                               <para>Randomize the selection of a parking space.</para>
+                                       </option>
+                                       <option name="c" argsep=",">
+                                               <argument name="context" required="false" />
+                                               <argument name="extension" required="false" />
+                                               <argument name="priority" required="true" />
+                                               <para>If the parking times out, go to this place in the dialplan
+                                                       instead of where the parking lot defines the call should go.
+                                               </para>
+                                       </option>
+                                       <option name="t">
+                                               <argument name="duration" required="true" />
+                                               <para>Use a timeout of <literal>duration</literal> seconds instead
+                                                       of the timeout specified by the parking lot.</para>
+                                       </option>
+                               </optionlist>
+                       </parameter>
+                       <parameter name="announce_template" required="true" argsep=":">
+                               <argument name="announce" required="true">
+                                       <para>Colon-separated list of files to announce. The word
+                                       <literal>PARKED</literal> will be replaced by a say_digits of the extension in which
+                                       the call is parked.</para>
+                               </argument>
+                               <argument name="announce1" multiple="true" />
+                       </parameter>
+                       <parameter name="dial" required="true">
+                               <para>The app_dial style resource to call to make the
+                               announcement. Console/dsp calls the console.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Park a call into the parkinglot and announce the call to another channel.</para>
+                       <para>The variable <variable>PARKEDAT</variable> will contain the parking extension
+                       into which the call was placed.  Use with the Local channel to allow the dialplan to make
+                       use of this information.</para>
+               </description>
+               <see-also>
+                       <ref type="application">Park</ref>
+                       <ref type="application">ParkedCall</ref>
+               </see-also>
+       </application>
+ ***/
+
+/* Park a call */
+
+enum park_args {
+       OPT_ARG_COMEBACK,
+       OPT_ARG_TIMEOUT,
+       OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
+};
+
+enum park_flags {
+       MUXFLAG_RINGING = (1 << 0),
+       MUXFLAG_RANDOMIZE = (1 << 1),
+       MUXFLAG_NOANNOUNCE = (1 << 2),
+       MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
+       MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
+};
+
+AST_APP_OPTIONS(park_opts, {
+       AST_APP_OPTION('r', MUXFLAG_RINGING),
+       AST_APP_OPTION('R', MUXFLAG_RANDOMIZE),
+       AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
+       AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
+       AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
+});
+
+static int apply_option_timeout (int *var, char *timeout_arg)
+{
+       if (ast_strlen_zero(timeout_arg)) {
+               ast_log(LOG_ERROR, "No duration value provided for the timeout ('t') option.\n");
+               return -1;
+       }
+
+       if (sscanf(timeout_arg, "%d", var) != 1 || *var < 0) {
+               ast_log(LOG_ERROR, "Duration value provided for timeout ('t') option must be 0 or greater.\n");
+               return -1;
+       }
+
+       return 0;
+}
+
+static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name)
+{
+       char *parse;
+       struct ast_flags flags = { 0 };
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(lot_name);
+               AST_APP_ARG(options);
+               AST_APP_ARG(other);     /* Any remaining unused arguments */
+       );
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (args.options) {
+               char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
+               ast_app_parse_options(park_opts, &flags, opts, args.options);
+               if (ast_test_flag(&flags, MUXFLAG_TIMEOUT_OVERRIDE)) {
+                       if (apply_option_timeout(time_limit, opts[OPT_ARG_TIMEOUT])) {
+                               return -1;
+                       }
+               }
+
+               if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) {
+                       *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]);
+               }
+
+               if (ast_test_flag(&flags, MUXFLAG_NOANNOUNCE)) {
+                       if (disable_announce) {
+                               *disable_announce = 1;
+                       }
+               }
+
+               if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
+                       *use_ringing = 1;
+               }
+
+               if (ast_test_flag(&flags, MUXFLAG_RANDOMIZE)) {
+                       *randomize = 1;
+               }
+       }
+
+       if (!ast_strlen_zero(args.lot_name)) {
+               *lot_name = ast_strdup(args.lot_name);
+       }
+
+       return 0;
+}
+
+static void park_common_datastore_destroy(void *data)
+{
+       struct park_common_datastore *datastore = data;
+       ast_free(datastore->parker_uuid);
+       ast_free(datastore->comeback_override);
+       ast_free(datastore);
+}
+
+static const struct ast_datastore_info park_common_info = {
+       .type = "park entry data",
+       .destroy = park_common_datastore_destroy,
+};
+
+static void wipe_park_common_datastore(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       ast_channel_lock(chan);
+       datastore = ast_channel_datastore_find(chan, &park_common_info, NULL);
+       if (datastore) {
+               ast_channel_datastore_remove(chan, datastore);
+               ast_datastore_free(datastore);
+       }
+       ast_channel_unlock(chan);
+}
+
+static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce)
+{
+       struct ast_datastore *datastore = NULL;
+       struct park_common_datastore *park_datastore;
+
+       wipe_park_common_datastore(parkee);
+
+       if (!(datastore = ast_datastore_alloc(&park_common_info, NULL))) {
+               return -1;
+       }
+
+       if (!(park_datastore = ast_calloc(1, sizeof(*park_datastore)))) {
+               ast_datastore_free(datastore);
+               return -1;
+       }
+
+       park_datastore->parker_uuid = ast_strdup(parker_uuid);
+       park_datastore->randomize = randomize;
+       park_datastore->time_limit = time_limit;
+       park_datastore->silence_announce = silence_announce;
+
+       if (comeback_override) {
+               park_datastore->comeback_override = ast_strdup(comeback_override);
+       }
+
+
+       datastore->data = park_datastore;
+       ast_channel_lock(parkee);
+       ast_channel_datastore_add(parkee, datastore);
+       ast_channel_unlock(parkee);
+
+       return 0;
+}
+
+void get_park_common_datastore_data(struct ast_channel *parkee, char **parker_uuid, char **comeback_override,
+               int *randomize, int *time_limit, int *silence_announce)
+{
+       struct ast_datastore *datastore;
+       struct park_common_datastore *data;
+
+       ast_channel_lock(parkee);
+       if (!(datastore = ast_channel_datastore_find(parkee, &park_common_info, NULL))) {
+               ast_channel_unlock(parkee);
+               return;
+       }
+
+       data = datastore->data;
+
+       if (!data) {
+               /* This data should always be populated if this datastore was appended to the channel */
+               ast_assert(0);
+       }
+
+       *parker_uuid = ast_strdup(data->parker_uuid);
+       *randomize = data->randomize;
+       *time_limit = data->time_limit;
+       *silence_announce = data->silence_announce;
+
+       if (data->comeback_override) {
+               *comeback_override = ast_strdup(data->comeback_override);
+       }
+
+       ast_channel_unlock(parkee);
+}
+
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
+               int *silence_announcements)
+{
+       int use_ringing = 0;
+       int randomize = 0;
+       int time_limit = -1;
+       char *lot_name;
+
+       struct ast_bridge *parking_bridge;
+       RAII_VAR(char *, comeback_override, NULL, ast_free);
+       RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
+       RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+
+       if (app_data) {
+               park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
+       }
+
+       lot_name = lot_name_app_arg;
+
+       /* If the name of the parking lot isn't specified in the arguments, find it based on the channel. */
+       if (ast_strlen_zero(lot_name)) {
+               ast_channel_lock(parker);
+               lot_name = ast_strdupa(find_channel_parking_lot_name(parker));
+               ast_channel_unlock(parker);
+       }
+
+       lot = parking_lot_find_by_name(lot_name);
+
+       if (!lot) {
+               ast_log(LOG_ERROR, "Could not find parking lot: '%s'\n", lot_name);
+               return NULL;
+       }
+
+       ao2_lock(lot);
+       parking_bridge = parking_lot_get_bridge(lot);
+       ao2_unlock(lot);
+
+       if (parking_bridge) {
+               /* Apply relevant bridge roles and such to the parking channel */
+               parking_channel_set_roles(parkee, lot, use_ringing);
+               setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
+                       silence_announcements ? *silence_announcements : 0);
+               return parking_bridge;
+       }
+
+       /* Couldn't get the parking bridge. Epic failure. */
+       return NULL;
+}
+
+/* XXX BUGBUG - determining the parker when transferred to deep park priority
+ *     Currently all parking by the park application is treated as calls parking themselves.
+ *     However, it's possible for calls to be transferred here when the Park application is
+ *     set after the first priority of an extension. In that case, there used to be a variable
+ *     (BLINDTRANSFER) set indicating which channel placed that call here.
+ *
+ *     If BLINDTRANSFER is set, this channel name will need to be referenced in Park events
+ *     generated by stasis. Ideally we would get a whole channel snapshot and use that for the
+ *     parker, but that would likely require applying the channel snapshot to a channel datastore
+ *     on all transfers. Alternatively just the name of the parking channel could be applied along
+ *     with an indication that it's dead.
+ */
+int park_app_exec(struct ast_channel *chan, const char *data)
+{
+       RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+
+       struct ast_bridge_features chan_features;
+       int res;
+       int silence_announcements = 0;
+       const char *blind_transfer;
+
+       ast_channel_lock(chan);
+       if ((blind_transfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
+               blind_transfer = ast_strdupa(blind_transfer);
+       }
+       ast_channel_unlock(chan);
+
+       /* Handle the common parking setup stuff */
+       if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
+               if (!silence_announcements && !blind_transfer) {
+                       ast_stream_and_wait(chan, "pbx-parkingfailed", "");
+               }
+               return 0;
+       }
+
+       /* Initialize bridge features for the channel. */
+       res = ast_bridge_features_init(&chan_features);
+       if (res) {
+               ast_bridge_features_cleanup(&chan_features);
+               return -1;
+       }
+
+       /* Now for the fun part... park it! */
+       ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
+
+       /*
+        * If the bridge was broken for a hangup that isn't real, then
+        * don't run the h extension, because the channel isn't really
+        * hung up.  This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
+        */
+       res = -1;
+
+       ast_channel_lock(chan);
+       if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+               res = 0;
+       }
+       ast_channel_unlock(chan);
+
+       ast_bridge_features_cleanup(&chan_features);
+
+       return res;
+}
+
+/* Retrieve a parked call */
+
+int parked_call_app_exec(struct ast_channel *chan, const char *data)
+{
+       RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+       RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); /* Parked user being retrieved */
+       RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+       struct ast_bridge *retrieval_bridge;
+       int res;
+       int target_space = -1;
+       struct ast_bridge_features chan_features;
+       char *parse;
+       char *lot_name;
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(lot_name);
+               AST_APP_ARG(parking_space);
+               AST_APP_ARG(other);     /* Any remaining unused arguments */
+       );
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       /* Answer the channel if needed */
+       if (ast_channel_state(chan) != AST_STATE_UP) {
+               ast_answer(chan);
+       }
+
+       lot_name = args.lot_name;
+
+       /* If the name of the parking lot isn't in the arguments, find it based on the channel. */
+       if (ast_strlen_zero(lot_name)) {
+               ast_channel_lock(chan);
+               lot_name = ast_strdupa(find_channel_parking_lot_name(chan));
+               ast_channel_unlock(chan);
+       }
+
+       lot = parking_lot_find_by_name(lot_name);
+
+       if (!lot) {
+               ast_log(LOG_ERROR, "Could not find the requested parking lot\n");
+               ast_stream_and_wait(chan, "pbx-invalidpark", "");
+               return -1;
+       }
+
+       if (!ast_strlen_zero(args.parking_space)) {
+               if (sscanf(args.parking_space, "%d", &target_space) != 1 || target_space < 0) {
+                       ast_stream_and_wait(chan, "pbx-invalidpark", "");
+                       ast_log(LOG_ERROR, "value '%s' for parking_space argument is invalid. Must be an integer greater than 0.\n", args.parking_space);
+                       return -1;
+               }
+       }
+
+       /* Attempt to get the parked user from the parking lot */
+       pu = parking_lot_retrieve_parked_user(lot, target_space);
+       if (!pu) {
+               ast_stream_and_wait(chan, "pbx-invalidpark", "");
+               return -1;
+       }
+
+       /* The parked call needs to know who is retrieving it before we move it out of the parking bridge */
+       pu->retriever = ast_channel_snapshot_create(chan);
+
+       /* Create bridge */
+       retrieval_bridge = ast_bridge_basic_new();
+       if (!retrieval_bridge) {
+               return -1;
+       }
+
+       /* Move the parkee into the new bridge */
+       if (ast_bridge_move(retrieval_bridge, lot->parking_bridge, pu->chan, NULL, 0)) {
+               ast_bridge_destroy(retrieval_bridge);
+               return -1;
+       }
+
+       /* Initialize our bridge features */
+       res = ast_bridge_features_init(&chan_features);
+       if (res) {
+               ast_bridge_destroy(retrieval_bridge);
+               ast_bridge_features_cleanup(&chan_features);
+               return -1;
+       }
+
+       /* Set the features */
+       parked_call_retrieve_enable_features(chan, lot, AST_FEATURE_FLAG_BYCALLER);
+
+       /* If the parkedplay option is set for the caller to hear, play that tone now. */
+       if (lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLER) {
+               ast_stream_and_wait(chan, lot->cfg->courtesytone, NULL);
+       }
+
+       /* Now we should try to join the new bridge ourselves... */
+       ast_bridge_join(retrieval_bridge, chan, NULL, &chan_features, NULL, 1);
+
+       ast_bridge_features_cleanup(&chan_features);
+
+       return 0;
+}
+
+struct park_announce_subscription_data {
+       char *parkee_uuid;
+       char *dial_string;
+       char *announce_string;
+};
+
+static void park_announce_subscription_data_destroy(void *data)
+{
+       struct park_announce_subscription_data *pa_data = data;
+       ast_free(pa_data->parkee_uuid);
+       ast_free(pa_data->dial_string);
+       ast_free(pa_data->announce_string);
+       ast_free(pa_data);
+}
+
+static struct park_announce_subscription_data *park_announce_subscription_data_create(const char *parkee_uuid,
+               const char *dial_string,
+               const char *announce_string)
+{
+       struct park_announce_subscription_data *pa_data;
+
+       if (!(pa_data = ast_calloc(1, sizeof(*pa_data)))) {
+               return NULL;
+       }
+
+       if (!(pa_data->parkee_uuid = ast_strdup(parkee_uuid))
+               || !(pa_data->dial_string = ast_strdup(dial_string))
+               || !(pa_data->announce_string = ast_strdup(announce_string))) {
+               park_announce_subscription_data_destroy(pa_data);
+               return NULL;
+       }
+
+       return pa_data;
+}
+
+static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
+{
+       struct ast_channel *dchan;
+       struct outgoing_helper oh = { 0, };
+       int outstate;
+       struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
+       char buf[13];
+       char *dial_tech;
+       char *cur_announce;
+       struct ast_format tmpfmt;
+
+       dial_tech = strsep(&dial_string, "/");
+       ast_verb(3, "Dial Tech,String: (%s,%s)\n", dial_tech, dial_string);
+
+       if (!cap_slin) {
+               ast_log(LOG_WARNING, "PARK: Failed to announce park.\n");
+               goto announce_cleanup;
+       }
+       ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
+
+       snprintf(buf, sizeof(buf), "%d", parkingspace);
+       oh.vars = ast_variable_new("_PARKEDAT", buf, "");
+       dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, dial_string, 30000,
+               &outstate,
+               parkee_snapshot->caller_number,
+               parkee_snapshot->caller_name,
+               &oh);
+
+       ast_variables_destroy(oh.vars);
+       if (!dchan) {
+               ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
+               goto announce_cleanup;
+       }
+
+       ast_verb(4, "Announce Template: %s\n", announce_string);
+
+       for (cur_announce = strsep(&announce_string, ":"); cur_announce; cur_announce = strsep(&announce_string, ":")) {
+               ast_verb(4, "Announce:%s\n", cur_announce);
+               if (!strcmp(cur_announce, "PARKED")) {
+                       ast_say_digits(dchan, parkingspace, "", ast_channel_language(dchan));
+               } else {
+                       int dres = ast_streamfile(dchan, cur_announce, ast_channel_language(dchan));
+                       if (!dres) {
+                               dres = ast_waitstream(dchan, "");
+                       } else {
+                               ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", cur_announce, ast_channel_name(dchan));
+                       }
+               }
+       }
+
+       ast_stopstream(dchan);
+       ast_hangup(dchan);
+
+announce_cleanup:
+       cap_slin = ast_format_cap_destroy(cap_slin);
+}
+
+static void park_announce_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+       struct park_announce_subscription_data *pa_data = data;
+       char *dial_string = pa_data->dial_string;
+
+       struct ast_parked_call_payload *payload = stasis_message_data(message);
+
+       if (stasis_subscription_final_message(sub, message)) {
+               park_announce_subscription_data_destroy(data);
+               return;
+       }
+
+       if (payload->event_type != PARKED_CALL) {
+               /* We are only concerned with calls parked */
+               return;
+       }
+
+       if (strcmp(payload->parkee->uniqueid, pa_data->parkee_uuid)) {
+               /* We are only concerned with the parkee we are subscribed for. */
+               return;
+       }
+
+       if (!ast_strlen_zero(dial_string)) {
+               announce_to_dial(dial_string, pa_data->announce_string, payload->parkingspace, payload->parkee);
+       }
+
+       *dial_string = '\0'; /* If we observe this dial string on a second pass, we don't want to do anything with it. */
+}
+
+int park_and_announce_app_exec(struct ast_channel *chan, const char *data)
+{
+       struct ast_bridge_features chan_features;
+       char *parse;
+       int res;
+       int silence_announcements = 1;
+
+       struct stasis_subscription *parking_subscription;
+       struct park_announce_subscription_data *pa_data;
+
+       RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(lot_name);
+               AST_APP_ARG(options);
+               AST_APP_ARG(announce_template);
+               AST_APP_ARG(dial);
+               AST_APP_ARG(others);/* Any remaining unused arguments */
+       );
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_ERROR, "ParkAndAnnounce has required arguments. No arguments were provided.\n");
+               return -1;
+       }
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.announce_template)) {
+               /* improperly configured arguments for the application */
+               ast_log(LOG_ERROR, "ParkAndAnnounce requires the announce_template argument.\n");
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.dial)) {
+               /* improperly configured arguments */
+               ast_log(LOG_ERROR, "ParkAndAnnounce requires the dial argument.\n");
+               return -1;
+       }
+
+       if (!strchr(args.dial, '/')) {
+               ast_log(LOG_ERROR, "ParkAndAnnounce dial string '%s' is improperly formed.\n", args.dial);
+               return -1;
+       }
+
+       /* Handle the common parking setup stuff */
+       if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
+               return 0;
+       }
+
+       /* Initialize bridge features for the channel. */
+       res = ast_bridge_features_init(&chan_features);
+       if (res) {
+               ast_bridge_features_cleanup(&chan_features);
+               return -1;
+       }
+
+       /* subscribe to the parking message so that we can announce once it is parked */
+       pa_data = park_announce_subscription_data_create(ast_channel_uniqueid(chan), args.dial, args.announce_template);
+       if (!pa_data) {
+               return -1;
+       }
+
+       if (!(parking_subscription = stasis_subscribe(ast_parking_topic(), park_announce_update_cb, pa_data))) {
+               /* Failed to create subscription */
+               park_announce_subscription_data_destroy(pa_data);
+               return -1;
+       }
+
+       /* Now for the fun part... park it! */
+       ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
+
+       /* Toss the subscription since we aren't bridged at this point. */
+       stasis_unsubscribe(parking_subscription);
+
+       /*
+        * If the bridge was broken for a hangup that isn't real, then
+        * don't run the h extension, because the channel isn't really
+        * hung up.  This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
+        */
+       res = -1;
+
+       ast_channel_lock(chan);
+       if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+               res = 0;
+       }
+       ast_channel_unlock(chan);
+
+       ast_bridge_features_cleanup(&chan_features);
+
+       return res;
+}
diff --git a/res/parking/parking_bridge.c b/res/parking/parking_bridge.c
new file mode 100644 (file)
index 0000000..b9ceb52
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Parking Bridge Class
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+#include "asterisk/logger.h"
+#include "res_parking.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/say.h"
+
+struct ast_bridge_parking
+{
+       struct ast_bridge base;
+
+       /* private stuff for parking */
+       struct parking_lot *lot;
+};
+
+/*!
+ * \internal
+ * \brief ast_bridge parking class destructor
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \note XXX Stub... and it might go unused.
+ *
+ * \return Nothing
+ */
+static void bridge_parking_destroy(struct ast_bridge_parking *self)
+{
+       ast_bridge_base_v_table.destroy(&self->base);
+}
+
+static void bridge_parking_dissolving(struct ast_bridge_parking *self)
+{
+       struct parking_lot *lot = self->lot;
+
+       /* Unlink the parking bridge from the parking lot that owns it */
+       lot->parking_bridge = NULL;
+       ao2_ref(lot, -1);
+
+       /* Disassociate the bridge from the parking lot as well. */
+       self->lot = NULL;
+
+       ast_bridge_base_v_table.dissolving(&self->base);
+}
+
+static void destroy_parked_user(void *obj)
+{
+       struct parked_user *pu = obj;
+
+       ao2_cleanup(pu->lot);
+       pu->lot = NULL;
+
+       ao2_cleanup(pu->parker);
+       pu->parker = NULL;
+}
+
+/*!
+ * \internal
+ * \since 12
+ * \brief Construct a parked_user struct assigned to the specified parking lot
+ *
+ * \param lot The parking lot we are assigning the user to
+ * \param parkee The channel being parked
+ * \param parker The channel performing the park operation (may be the same channel)
+ * \param use_random_space if true, prioritize using a random parking space instead
+ *        of ${PARKINGEXTEN} and/or automatic assignment from the parking lot
+ * \param time_limit If using a custom timeout, this should be supplied so that the
+ *        parked_user struct can provide this information for manager events. If <0,
+ *        use the parking lot limit instead.
+ *
+ * \retval NULL on failure
+ * \retval reference to the parked user
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, struct ast_channel *parker, int use_random_space, int time_limit)
+{
+       struct parked_user *new_parked_user;
+       int preferred_space = -1; /* Initialize to use parking lot defaults */
+       int parking_space;
+       const char *parkingexten;
+
+       if (lot->mode == PARKINGLOT_DISABLED) {
+               ast_log(LOG_NOTICE, "Tried to park in a parking lot that is no longer able to be parked to.\n");
+               return NULL;
+       }
+
+       new_parked_user = ao2_alloc(sizeof(*new_parked_user), destroy_parked_user);
+       if (!new_parked_user) {
+               return NULL;
+       }
+
+
+       if (use_random_space) {
+               preferred_space = ast_random() % (lot->cfg->parking_stop - lot->cfg->parking_start + 1);
+               preferred_space += lot->cfg->parking_start;
+       } else {
+               ast_channel_lock(chan);
+               if ((parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN"))) {
+                       parkingexten = ast_strdupa(parkingexten);
+               }
+               ast_channel_unlock(chan);
+
+               if (!ast_strlen_zero(parkingexten)) {
+                       if (sscanf(parkingexten, "%30d", &preferred_space) != 1 || preferred_space <= 0) {
+                               ast_log(LOG_WARNING, "PARKINGEXTEN='%s' is not a valid parking space.\n", parkingexten);
+                               ao2_ref(new_parked_user, -1);
+                               return NULL;
+                       }
+               }
+       }
+
+
+       /* We need to keep the lot locked between parking_lot_get_space and actually placing it in the lot. Or until we decide not to. */
+       ao2_lock(lot);
+
+       parking_space = parking_lot_get_space(lot, preferred_space);
+       if (parking_space == -1) {
+               ast_log(LOG_NOTICE, "Failed to get parking space in lot '%s'. All full.\n", lot->name);
+               ao2_ref(new_parked_user, -1);
+               ao2_unlock(lot);
+               return NULL;
+       }
+
+       lot->next_space = ((parking_space + 1) - lot->cfg->parking_start) % (lot->cfg->parking_stop - lot->cfg->parking_start + 1) + lot->cfg->parking_start;
+       new_parked_user->chan = chan;
+       new_parked_user->parking_space = parking_space;
+
+       /* Have the parked user take a reference to the parking lot. This reference should be immutable and released at destruction */
+       new_parked_user->lot = lot;
+       ao2_ref(lot, +1);
+
+       new_parked_user->start = ast_tvnow();
+       new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime;
+       new_parked_user->parker = ast_channel_snapshot_create(parker);
+       if (!new_parked_user->parker) {
+               ao2_ref(new_parked_user, -1);
+               ao2_unlock(lot);
+               return NULL;
+       }
+
+       /* Insert into the parking lot's parked user list. We can unlock the lot now. */
+       ao2_link(lot->parked_users, new_parked_user);
+       ao2_unlock(lot);
+
+       return new_parked_user;
+}
+
+/* TODO CEL events for parking */
+
+/*!
+ * \internal
+ * \brief ast_bridge parking push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon
+ * \param bridge_channel Bridge channel to push
+ * \param swap Bridge channel to swap places with if not NULL
+ *
+ * \note On entry, self is already locked
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_parking_push(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+       struct parked_user *pu;
+       int randomize = 0;
+       int time_limit = -1;
+       int silence = 0;
+       const char *blind_transfer;
+       RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+       RAII_VAR(char *, parker_uuid, NULL, ast_free);
+       RAII_VAR(char *, comeback_override, NULL, ast_free);
+
+       ast_bridge_base_v_table.push(&self->base, bridge_channel, swap);
+
+       /* Answer the channel if needed */
+       if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
+               ast_answer(bridge_channel->chan);
+       }
+
+       if (swap) {
+               ao2_lock(swap);
+               pu = swap->bridge_pvt;
+               if (!pu) {
+                       /* This should be impossible since the only way a channel can enter in the first place
+                        * is if it has a parked user associated with it */
+                       publish_parked_call_failure(bridge_channel->chan);
+                       ao2_unlock(swap);
+                       return -1;
+               }
+
+               /* Give the swap channel's parked user reference to the incoming channel */
+               pu->chan = bridge_channel->chan;
+               bridge_channel->bridge_pvt = pu;
+               swap->bridge_pvt = NULL;
+
+               /* TODO Add a parked call swap message type to relay information about parked channel swaps */
+
+               ao2_unlock(swap);
+
+               parking_set_duration(bridge_channel->features, pu);
+
+               return 0;
+       }
+
+       get_park_common_datastore_data(bridge_channel->chan, &parker_uuid, &comeback_override, &randomize, &time_limit, &silence);
+       parker = ast_channel_get_by_name(parker_uuid);
+
+       /* If the parker and the parkee are the same channel pointer, then the channel entered using
+        * the park application. It's possible the the blindtransfer channel is still alive (particularly
+        * when a multichannel bridge is parked), so try to get the real parker if possible. */
+       ast_channel_lock(bridge_channel->chan);
+       blind_transfer = S_OR(pbx_builtin_getvar_helper(bridge_channel->chan, "BLINDTRANSFER"),
+               ast_channel_name(bridge_channel->chan));
+       if (blind_transfer) {
+               blind_transfer = ast_strdupa(blind_transfer);
+       }
+       ast_channel_unlock(bridge_channel->chan);
+
+       if (parker == bridge_channel->chan) {
+               struct ast_channel *real_parker = ast_channel_get_by_name(blind_transfer);
+               if (real_parker) {
+                       ao2_cleanup(parker);
+                       parker = real_parker;
+               }
+       }
+
+       if (!parker) {
+               return -1;
+       }
+
+       pu = generate_parked_user(self->lot, bridge_channel->chan, parker, randomize, time_limit);
+       if (!pu) {
+               publish_parked_call_failure(bridge_channel->chan);
+               return -1;
+       }
+
+       /* If a comeback_override was provided, set it for the parked user's comeback string. */
+       if (comeback_override) {
+               strncpy(pu->comeback, comeback_override, sizeof(pu->comeback));
+               pu->comeback[sizeof(pu->comeback) - 1] = '\0';
+       }
+
+       /* Generate ParkedCall Stasis Message */
+       publish_parked_call(pu, PARKED_CALL);
+
+       /* If the parkee and the parker are the same and silence_announce isn't set, play the announcement to the parkee */
+       if (!strcmp(blind_transfer, ast_channel_name(bridge_channel->chan)) && !silence) {
+               char saynum_buf[16];
+               snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 0, pu->parking_space);
+               ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
+       }
+
+       /* Apply parking duration limits */
+       parking_set_duration(bridge_channel->features, pu);
+
+       /* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */
+       bridge_channel->bridge_pvt = pu;
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge parking pull method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to pull.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_parking_pull(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
+{
+       RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
+       ast_bridge_base_v_table.pull(&self->base, bridge_channel);
+
+       /* Take over the bridge channel's pu reference. It will be released when we are done. */
+       pu = bridge_channel->bridge_pvt;
+       bridge_channel->bridge_pvt = NULL;
+
+       /* This should only happen if the exiting channel was swapped out */
+       if (!pu) {
+               return;
+       }
+
+       /* If we got here without the resolution being set, that's because the call was hung up for some reason without
+        * timing out or being picked up. There may be some forcible park removals later, but the resolution should be
+        * handled in those cases */
+       ao2_lock(pu);
+       if (pu->resolution == PARK_UNSET) {
+               pu->resolution = PARK_ABANDON;
+       }
+       ao2_unlock(pu);
+
+       switch (pu->resolution) {
+       case PARK_UNSET:
+               /* This should be impossible now since the resolution is forcibly set to abandon if it was unset at this point. Resolution
+                  isn't allowed to be changed when it isn't currently PARK_UNSET. */
+               return;
+       case PARK_ABANDON:
+               /* Since the call was abandoned without additional handling, we need to issue the give up event and unpark the user. */
+               publish_parked_call(pu, PARKED_CALL_GIVEUP);
+               unpark_parked_user(pu);
+               return;
+       case PARK_FORCED:
+               /* PARK_FORCED is currently unused, but it is expected that it would be handled similar to PARK_ANSWERED.
+                * There is currently no event related to forced parked calls either */
+               return;
+       case PARK_ANSWERED:
+               /* If answered or forced, the channel should be pulled from the bridge as part of that process and unlinked from
+                * the parking lot afterwards. We do need to apply bridge features though and play the courtesy tone if set. */
+               publish_parked_call(pu, PARKED_CALL_UNPARKED);
+               parked_call_retrieve_enable_features(bridge_channel->chan, pu->lot, AST_FEATURE_FLAG_BYCALLEE);
+
+               if (pu->lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLEE) {
+                       ast_bridge_channel_queue_playfile(bridge_channel, NULL, pu->lot->cfg->courtesytone, NULL);
+               }
+
+               return;
+       case PARK_TIMEOUT:
+               /* Timeout is similar to abandon because it simply sets the bridge state to end and doesn't
+                * actually pull the channel. Because of that, unpark should happen in here. */
+               publish_parked_call(pu, PARKED_CALL_TIMEOUT);
+               unpark_parked_user(pu);
+               return;
+       }
+}
+
+/*!
+ * \internal
+ * \brief ast_bridge parking notify_masquerade method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel that was masqueraded.
+ *
+ * \note On entry, self is already locked.
+ * \note XXX Stub... and it will probably go unused.
+ *
+ * \return Nothing
+ */
+static void bridge_parking_notify_masquerade(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
+{
+       ast_bridge_base_v_table.notify_masquerade(&self->base, bridge_channel);
+}
+
+static void bridge_parking_get_merge_priority(struct ast_bridge_parking *self)
+{
+       ast_bridge_base_v_table.get_merge_priority(&self->base);
+}
+
+struct ast_bridge_methods ast_bridge_parking_v_table = {
+       .name = "parking",
+       .destroy = (ast_bridge_destructor_fn) bridge_parking_destroy,
+       .dissolving = (ast_bridge_dissolving_fn) bridge_parking_dissolving,
+       .push = (ast_bridge_push_channel_fn) bridge_parking_push,
+       .pull = (ast_bridge_pull_channel_fn) bridge_parking_pull,
+       .notify_masquerade = (ast_bridge_notify_masquerade_fn) bridge_parking_notify_masquerade,
+       .get_merge_priority = (ast_bridge_merge_priority_fn) bridge_parking_get_merge_priority,
+};
+
+static struct ast_bridge *ast_bridge_parking_init(struct ast_bridge_parking *self, struct parking_lot *bridge_lot)
+{
+       if (!self) {
+               return NULL;
+       }
+
+       /* If no lot is defined for the bridge, then we aren't allowing the bridge to be initialized. */
+       if (!bridge_lot) {
+               ao2_ref(self, -1);
+               return NULL;
+       }
+
+       /* It doesn't need to be a reference since the bridge only lives as long as the parking lot lives. */
+       self->lot = bridge_lot;
+
+       return &self->base;
+}
+
+struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot)
+{
+       void *bridge;
+
+       bridge = ast_bridge_alloc(sizeof(struct ast_bridge_parking), &ast_bridge_parking_v_table);
+       bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
+               AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
+               | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM);
+       bridge = ast_bridge_parking_init(bridge, bridge_lot);
+       bridge = ast_bridge_register(bridge);
+       return bridge;
+}
diff --git a/res/parking/parking_bridge_features.c b/res/parking/parking_bridge_features.c
new file mode 100644 (file)
index 0000000..ddb5e7f
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Parking Bridge DTMF and Interval features
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/utils.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/logger.h"
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_features.h"
+#include "asterisk/features.h"
+#include "asterisk/say.h"
+#include "asterisk/datastore.h"
+#include "asterisk/stasis.h"
+
+struct parked_subscription_datastore {
+       struct stasis_subscription *parked_subscription;
+};
+
+struct parked_subscription_data {
+       char *parkee_uuid;
+       char parker_uuid[0];
+};
+
+static void parked_subscription_datastore_destroy(void *data)
+{
+       struct parked_subscription_datastore *subscription_datastore = data;
+
+       stasis_unsubscribe(subscription_datastore->parked_subscription);
+       subscription_datastore->parked_subscription = NULL;
+
+       ast_free(subscription_datastore);
+}
+
+static const struct ast_datastore_info parked_subscription_info = {
+       .type = "park subscription",
+       .destroy = parked_subscription_datastore_destroy,
+};
+
+static void wipe_subscription_datastore(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+
+       ast_channel_lock(chan);
+
+       datastore = ast_channel_datastore_find(chan, &parked_subscription_info, NULL);
+       if (datastore) {
+               ast_channel_datastore_remove(chan, datastore);
+               ast_datastore_free(datastore);
+       }
+       ast_channel_unlock(chan);
+}
+
+static void parker_parked_call_message_response(struct ast_parked_call_payload *message, struct parked_subscription_data *data,
+       struct stasis_subscription *sub)
+{
+       const char *parkee_to_act_on = data->parkee_uuid;
+       char saynum_buf[16];
+       struct ast_channel_snapshot *parkee_snapshot = message->parkee;
+       RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+
+       if (strcmp(parkee_to_act_on, parkee_snapshot->uniqueid)) {
+               return;
+       }
+
+       if (message->event_type != PARKED_CALL && message->event_type != PARKED_CALL_FAILED) {
+               /* We only care about these two event types */
+               return;
+       }
+
+       parker = ast_channel_get_by_name(data->parker_uuid);
+       if (!parker) {
+               return;
+       }
+
+       ast_channel_lock(parker);
+       bridge_channel = ast_channel_get_bridge_channel(parker);
+       ast_channel_unlock(parker);
+       if (!bridge_channel) {
+               return;
+       }
+
+       if (message->event_type == PARKED_CALL) {
+               /* queue the saynum on the bridge channel and hangup */
+               snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 1, message->parkingspace);
+               ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
+               wipe_subscription_datastore(bridge_channel->chan);
+       }
+
+       if (message->event_type == PARKED_CALL_FAILED) {
+               ast_bridge_channel_queue_playfile(bridge_channel, NULL, "pbx-parkingfailed", NULL);
+               wipe_subscription_datastore(bridge_channel->chan);
+       }
+}
+
+static void parker_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+       if (stasis_subscription_final_message(sub, message)) {
+               ast_free(data);
+               return;
+       }
+
+       if (stasis_message_type(message) == ast_parked_call_type()) {
+               struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
+               parker_parked_call_message_response(parked_call_message, data, sub);
+       }
+}
+
+static int create_parked_subscription(struct ast_channel *chan, const char *parkee_uuid)
+{
+       struct ast_datastore *datastore;
+       struct parked_subscription_datastore *parked_datastore;
+       struct parked_subscription_data *subscription_data;
+
+       char *parker_uuid = ast_strdupa(ast_channel_uniqueid(chan));
+       size_t parker_uuid_size = strlen(parker_uuid) + 1;
+
+       /* If there is already a subscription, get rid of it. */
+       wipe_subscription_datastore(chan);
+
+       if (!(datastore = ast_datastore_alloc(&parked_subscription_info, NULL))) {
+               return -1;
+       }
+
+       if (!(parked_datastore = ast_calloc(1, sizeof(*parked_datastore)))) {
+               ast_datastore_free(datastore);
+               return -1;
+       }
+
+       if (!(subscription_data = ast_calloc(1, sizeof(*subscription_data) + parker_uuid_size +
+                       strlen(parkee_uuid) + 1))) {
+               ast_datastore_free(datastore);
+               ast_free(parked_datastore);
+               return -1;
+       }
+
+       subscription_data->parkee_uuid = subscription_data->parker_uuid + parker_uuid_size;
+       strcpy(subscription_data->parkee_uuid, parkee_uuid);
+       strcpy(subscription_data->parker_uuid, parker_uuid);
+
+       if (!(parked_datastore->parked_subscription = stasis_subscribe(ast_parking_topic(), parker_update_cb, subscription_data))) {
+               return -1;
+       }
+
+       datastore->data = parked_datastore;
+
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Helper function that creates an outgoing channel and returns it immediately. This function is nearly
+ *        identical to the dial_transfer function in bridge_builtin_features.c, however it doesn't swap the
+ *        local channel and the channel that instigated the park.
+ */
+static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *exten, const char *context)
+{
+       RAII_VAR(struct ast_channel *, parkee_side_2, NULL, ao2_cleanup);
+       char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
+       struct ast_channel *parkee;
+       int cause;
+
+       /* Used for side_2 hack */
+       char *parkee_name;
+       char *semi_pos;
+
+       /* Fill the variable with the extension and context we want to call */
+       snprintf(destination, sizeof(destination), "%s@%s", exten, context);
+
+       /* Now we request that chan_local prepare to call the destination */
+       parkee = ast_request("Local", ast_channel_nativeformats(parker), parker, destination,
+               &cause);
+       if (!parkee) {
+               return NULL;
+       }
+
+       /* Before we actually dial out let's inherit appropriate information. */
+       ast_channel_lock_both(parker, parkee);
+       ast_connected_line_copy_from_caller(ast_channel_connected(parkee), ast_channel_caller(parker));
+       ast_channel_inherit_variables(parker, parkee);
+       ast_channel_datastore_inherit(parker, parkee);
+       ast_channel_unlock(parkee);
+       ast_channel_unlock(parker);
+
+       /* BUGBUG Use Richard's unreal channel stuff here instead of this hack */
+       parkee_name = ast_strdupa(ast_channel_name(parkee));
+
+       semi_pos = strrchr(parkee_name, ';');
+       if (!semi_pos) {
+               /* There should always be a semicolon present in the string if is used since it's a local channel. */
+               ast_assert(0);
+               return NULL;
+       }
+
+       parkee_name[(semi_pos - parkee_name) + 1] = '2';
+       parkee_side_2 = ast_channel_get_by_name(parkee_name);
+
+       /* We need to have the parker subscribe to the new local channel before hand. */
+       create_parked_subscription(parker, ast_channel_uniqueid(parkee_side_2));
+
+       pbx_builtin_setvar_helper(parkee_side_2, "BLINDTRANSFER", ast_channel_name(parker));
+
+       /* Since the above worked fine now we actually call it and return the channel */
+       if (ast_call(parkee, destination, 0)) {
+               ast_hangup(parkee);
+               return NULL;
+       }
+
+       return parkee;
+}
+
+static int park_feature_helper(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_exten *park_exten)
+{
+       RAII_VAR(struct ast_channel *, other, NULL, ao2_cleanup);
+       RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+       RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, bridge_peers, NULL, ao2_cleanup);
+       struct ao2_iterator iter;
+
+       bridge_peers = ast_bridge_peers(bridge);
+
+       if (ao2_container_count(bridge_peers) < 2) {
+               /* There is nothing to do if there is no one to park. */
+               return 0;
+       }
+
+       if (ao2_container_count(bridge_peers) > 2) {
+               /* With a multiparty bridge, we need to do a regular blind transfer. We link the existing bridge to the parking lot with a
+                * local channel rather than transferring others. */
+               struct ast_channel *transfer_chan = NULL;
+
+               if (!park_exten) {
+                       /* This simply doesn't work. The user attempted to one-touch park the parking lot and we can't originate a local channel
+                        * without knowing an extension to transfer it to.
+                        * XXX However, when parking lots are changed to be able to register extensions then this will be doable. */
+                       ast_log(LOG_ERROR, "Can not one-touch park a multiparty bridge.\n");
+                       return 0;
+               }
+
+               transfer_chan = park_local_transfer(bridge_channel->chan,
+                       ast_get_extension_name(park_exten), ast_get_context_name(ast_get_extension_context(park_exten)));
+
+               if (!transfer_chan) {
+                       return 0;
+               }
+
+               if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL, 1)) {
+                       ast_hangup(transfer_chan);
+               }
+
+               return 0;
+       }
+
+       /* Since neither of the above cases were used, we are doing a simple park with a two party bridge. */
+
+       for (iter = ao2_iterator_init(bridge_peers, 0); (other = ao2_iterator_next(&iter)); ao2_ref(other, -1)) {
+               /* We need the channel that isn't the bridge_channel's channel. */
+               if (strcmp(ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan))) {
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&iter);
+
+       if (!other) {
+               ast_assert(0);
+               return -1;
+       }
+
+       /* Subscribe to park messages with the other channel entering */
+       if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other))) {
+               return -1;
+       }
+
+       /* Write the park frame with the intended recipient and other data out to the bridge. */
+       ast_bridge_channel_write_park(bridge_channel, ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan), ast_get_extension_app_data(park_exten));
+
+       return 0;
+}
+
+static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
+{
+       RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
+
+       if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
+               /* We aren't the parkee, so ignore this action. */
+               return;
+       }
+
+       parker = ast_channel_get_by_name(uuid_parker);
+
+       if (!parker) {
+               ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
+               publish_parked_call_failure(bridge_channel->chan);
+               return;
+       }
+
+       if (!(parking_bridge = park_common_setup(bridge_channel->chan, parker, app_data, NULL))) {
+               publish_parked_call_failure(bridge_channel->chan);
+               return;
+       }
+
+       pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", ast_channel_name(parker));
+
+       /* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
+       ao2_lock(bridge_channel);
+
+       original_bridge = bridge_channel->bridge;
+       if (!original_bridge) {
+               ao2_unlock(bridge_channel);
+               publish_parked_call_failure(bridge_channel->chan);
+               return;
+       }
+
+       ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */
+
+       ao2_unlock(bridge_channel);
+
+       if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
+               ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
+                       ast_channel_name(bridge_channel->chan));
+       }
+}
+
+static int feature_park(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       park_feature_helper(bridge, bridge_channel, NULL);
+       return 0;
+}
+
+static void parking_duration_cb_destroyer(void *hook_pvt)
+{
+       struct parked_user *user = hook_pvt;
+       ao2_ref(user, -1);
+}
+
+/*! \internal
+ * \brief Interval hook. Pulls a parked call from the parking bridge after the timeout is passed and sets the resolution to timeout.
+ *
+ * \param bridge Which bridge the channel was parked in
+ * \param bridge_channel bridge channel this interval hook is being executed on
+ * \param hook_pvt A pointer to the parked_user struct associated with the channel is stuffed in here
+ */
+static int parking_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct parked_user *user = hook_pvt;
+       struct ast_channel *chan = user->chan;
+       char *peername;
+       char parking_space[AST_MAX_EXTENSION];
+
+       /* We are still in the bridge, so it's possible for other stuff to mess with the parked call before we leave the bridge
+          to deal with this, lock the parked user, check and set resolution. */
+       ao2_lock(user);
+       if (user->resolution != PARK_UNSET) {
+               /* Abandon timeout since something else has resolved the parked user before we got to it. */
+               ao2_unlock(user);
+               return -1;
+       }
+
+       user->resolution = PARK_TIMEOUT;
+       ao2_unlock(user);
+
+       ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+
+       /* Set parking timeout channel variables */
+       snprintf(parking_space, sizeof(parking_space), "%d", user->parking_space);
+       pbx_builtin_setvar_helper(chan, "PARKING_SPACE", parking_space);
+       pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parking_space); /* Deprecated version of PARKING_SPACE */
+       pbx_builtin_setvar_helper(chan, "PARKEDLOT", user->lot->name);
+
+       peername = ast_strdupa(user->parker->name);
+       flatten_peername(peername);
+
+       pbx_builtin_setvar_helper(chan, "PARKER", peername);
+
+       /* TODO Dialplan generation for park-dial extensions */
+
+       /* async_goto the proper PBX destination - this should happen when we come out of the bridge */
+       if (!ast_strlen_zero(user->comeback)) {
+               ast_async_parseable_goto(chan, user->comeback);
+       } else {
+               comeback_goto(user, user->lot);
+       }
+
+       return -1;
+}
+
+void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload)
+{
+       int numeric_value;
+       int hangup_after;
+
+       if (sscanf(payload, "%u %u", &hangup_after, &numeric_value) != 2) {
+               /* If say_parking_space is called with a non-numeric string, we have a problem. */
+               ast_assert(0);
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+               return;
+       }
+
+       ast_say_digits(bridge_channel->chan, numeric_value, "", ast_channel_language(bridge_channel->chan));
+
+       if (hangup_after) {
+               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+       }
+}
+
+void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user)
+{
+       unsigned int time_limit;
+
+       time_limit = user->time_limit * 1000;
+
+       if (!time_limit) {
+               /* There is no duration limit that we need to apply. */
+               return;
+       }
+
+       /* If the time limit has already been passed, set a really low time limit so we can kick them out immediately. */
+       time_limit = ast_remaining_ms(user->start, time_limit);
+       if (time_limit <= 0) {
+               time_limit = 1;
+       }
+
+       /* The interval hook is going to need a reference to the parked_user */
+       ao2_ref(user, +1);
+
+       if (ast_bridge_interval_hook(features, time_limit,
+               parking_duration_callback, user, parking_duration_cb_destroyer, 1)) {
+               ast_log(LOG_ERROR, "Failed to apply duration limits to the parking call.\n");
+       }
+}
+
+void unload_parking_bridge_features(void)
+{
+       ast_bridge_features_unregister(AST_BRIDGE_BUILTIN_PARKCALL);
+       ast_uninstall_park_blind_xfer_func();
+       ast_uninstall_bridge_channel_park_func();
+}
+
+int load_parking_bridge_features(void)
+{
+       ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park, NULL);
+       ast_install_park_blind_xfer_func(park_feature_helper);
+       ast_install_bridge_channel_park_func(park_bridge_channel);
+       return 0;
+}
diff --git a/res/parking/parking_controller.c b/res/parking/parking_controller.c
new file mode 100644 (file)
index 0000000..03d7b88
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Parking Entry, Exit, and other assorted controls.
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+#include "asterisk.h"
+
+#include "asterisk/logger.h"
+#include "res_parking.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/manager.h"
+#include "asterisk/test.h"
+#include "asterisk/features.h"
+#include "asterisk/bridging_basic.h"
+
+struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot)
+{
+       struct ast_bridge *lot_bridge;
+
+       if (lot->parking_bridge) {
+               ao2_ref(lot->parking_bridge, +1);
+               return lot->parking_bridge;
+       }
+
+       lot_bridge = bridge_parking_new(lot);
+       if (!lot_bridge) {
+               return NULL;
+       }
+
+       /* The parking lot needs a reference to the bridge as well. */
+       lot->parking_bridge = lot_bridge;
+       ao2_ref(lot->parking_bridge, +1);
+
+       return lot_bridge;
+}
+
+void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing)
+{
+       ast_channel_add_bridge_role(chan, "holding_participant");
+       if (force_ringing) {
+               ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing");
+       } else {
+               ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
+               if (!ast_strlen_zero(lot->cfg->mohclass)) {
+                       ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", lot->cfg->mohclass);
+               }
+       }
+}
+
+struct parking_limits_pvt {
+       struct parked_user *user;
+};
+
+int unpark_parked_user(struct parked_user *pu)
+{
+       if (pu->lot) {
+               ao2_unlink(pu->lot->parked_users, pu);
+               parking_lot_remove_if_unused(pu->lot);
+               return 0;
+       }
+
+       return -1;
+}
+
+int parking_lot_get_space(struct parking_lot *lot, int target_override)
+{
+       int original_target;
+       int current_target;
+       struct ao2_iterator i;
+       struct parked_user *user;
+       int wrap;
+
+       if (lot->cfg->parkfindnext) {
+               /* Use next_space if the lot already has next_space set; otherwise use lot start. */
+               original_target = lot->next_space ? lot->next_space : lot->cfg->parking_start;
+       } else {
+               original_target = lot->cfg->parking_start;
+       }
+
+       if (target_override >= lot->cfg->parking_start && target_override <= lot->cfg->parking_stop) {
+               original_target = target_override;
+       }
+
+       current_target = original_target;
+
+       wrap = lot->cfg->parking_start;
+
+       i = ao2_iterator_init(lot->parked_users, 0);
+       while ((user = ao2_iterator_next(&i))) {
+               /* Increment the wrap on each pass until we find an empty space */
+               if (wrap == user->parking_space) {
+                       wrap += 1;
+               }
+
+               if (user->parking_space < current_target) {
+                       /* It's lower than the anticipated target, so we haven't reached the target yet. */
+                       ao2_ref(user, -1);
+                       continue;
+               }
+
+               if (user->parking_space > current_target) {
+                       /* The current target is usable because all items below have been read and the next target is higher than the one we want. */
+                       ao2_ref(user, -1);
+                       break;
+               }
+
+               /* We found one already parked here. */
+               current_target += 1;
+               ao2_ref(user, -1);
+       }
+       ao2_iterator_destroy(&i);
+
+       if (current_target <= lot->cfg->parking_stop) {
+               return current_target;
+       }
+
+       if (wrap <= lot->cfg->parking_stop) {
+               return wrap;
+       }
+
+       return -1;
+}
+
+static int retrieve_parked_user_targeted(void *obj, void *arg, int flags)
+{
+       int *target = arg;
+       struct parked_user *user = obj;
+       if (user->parking_space == *target) {
+               return CMP_MATCH;
+       }
+
+       return 0;
+}
+
+struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target)
+{
+       RAII_VAR(struct parked_user *, user, NULL, ao2_cleanup);
+
+       if (target < 0) {
+               user = ao2_callback(lot->parked_users, 0, NULL, NULL);
+       } else {
+               user = ao2_callback(lot->parked_users, 0, retrieve_parked_user_targeted, &target);
+       }
+
+       if (!user) {
+               return NULL;
+       }
+
+       ao2_lock(user);
+       if (user->resolution != PARK_UNSET) {
+               /* Abandon. Something else has resolved the parked user before we got to it. */
+               ao2_unlock(user);
+               return NULL;
+       }
+
+       ao2_unlink(lot->parked_users, user);
+       user->resolution = PARK_ANSWERED;
+       ao2_unlock(user);
+
+       parking_lot_remove_if_unused(user->lot);
+
+       /* Bump the ref count by 1 since the RAII_VAR will eat the reference otherwise */
+       ao2_ref(user, +1);
+       return user;
+}
+
+void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode)
+{
+       /* Enabling features here should be additive to features that are already on the channel. */
+       struct ast_flags feature_flags = { 0 };
+       struct ast_flags *existing_features;
+
+       ast_channel_lock(chan);
+       existing_features = ast_bridge_features_ds_get(chan);
+
+       if (existing_features) {
+               feature_flags = *existing_features;
+       }
+
+       if (lot->cfg->parkedcalltransfers & recipient_mode) {
+               ast_set_flag(&feature_flags, AST_FEATURE_REDIRECT);
+               ast_set_flag(&feature_flags, AST_FEATURE_ATXFER);
+       }
+
+       if (lot->cfg->parkedcallreparking & recipient_mode) {
+               ast_set_flag(&feature_flags, AST_FEATURE_PARKCALL);
+       }
+
+       if (lot->cfg->parkedcallhangup & recipient_mode) {
+               ast_set_flag(&feature_flags, AST_FEATURE_DISCONNECT);
+       }
+
+       if (lot->cfg->parkedcallrecording & recipient_mode) {
+               ast_set_flag(&feature_flags, AST_FEATURE_AUTOMIXMON);
+       }
+
+       ast_bridge_features_ds_set(chan, &feature_flags);
+       ast_channel_unlock(chan);
+
+       return;
+}
+
+void flatten_peername(char *peername)
+{
+       int i;
+       char *dash;
+
+       /* Truncate after the dash */
+       dash = strrchr(peername, '-');
+       if (dash) {
+               *dash = '\0';
+       }
+
+       /* Replace slashes with underscores since slashes are reserved characters for extension matching */
+       for (i = 0; peername[i]; i++) {
+               if (peername[i] == '/') {
+                       /* The underscore is the flattest character of all. */
+                       peername[i] = '_';
+               }
+       }
+}
+
+int comeback_goto(struct parked_user *pu, struct parking_lot *lot)
+{
+       struct ast_channel *chan = pu->chan;
+       char *peername;
+       const char *blindtransfer;
+
+       ast_channel_lock(chan);
+       if ((blindtransfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
+               blindtransfer = ast_strdupa(blindtransfer);
+       }
+       ast_channel_unlock(chan);
+
+       peername = blindtransfer ? ast_strdupa(blindtransfer) : ast_strdupa(pu->parker->name);
+
+       /* XXX Comeback to origin mode: Generate an extension in park-dial to Dial the peer */
+
+
+       /* Flatten the peername so that it can be used for performing the timeout PBX operations */
+       flatten_peername(peername);
+
+       if (lot->cfg->comebacktoorigin) {
+               if (ast_exists_extension(chan, PARK_DIAL_CONTEXT, peername, 1, NULL)) {
+                       ast_async_goto(chan, PARK_DIAL_CONTEXT, peername, 1);
+                       return 0;
+               } else {
+                       ast_log(LOG_ERROR, "Can not start %s at %s,%s,1 because extension does not exist. Terminating call.\n",
+                               ast_channel_name(chan), PARK_DIAL_CONTEXT, peername);
+                       return -1;
+               }
+       }
+
+       if (ast_exists_extension(chan, lot->cfg->comebackcontext, peername, 1, NULL)) {
+               ast_async_goto(chan, lot->cfg->comebackcontext, peername, 1);
+               return 0;
+       }
+
+       if (ast_exists_extension(chan, lot->cfg->comebackcontext, "s", 1, NULL)) {
+               ast_verb(2, "Could not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
+                       lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
+               ast_async_goto(chan, lot->cfg->comebackcontext, "s", 1);
+               return 0;
+       }
+
+       ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
+               ast_channel_name(chan),
+               lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
+       ast_async_goto(chan, "default", "s", 1);
+
+       return 0;
+}
diff --git a/res/parking/parking_manager.c b/res/parking/parking_manager.c
new file mode 100644 (file)
index 0000000..d6a0557
--- /dev/null
@@ -0,0 +1,610 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Call Parking Manager Actions and Events
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+/*** DOCUMENTATION
+       <manager name="Parkinglots" language="en_US">
+               <synopsis>
+                       Get a list of parking lots
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+               </syntax>
+               <description>
+                       <para>List all parking lots as a series of AMI events</para>
+               </description>
+       </manager>
+       <manager name="ParkedCalls" language="en_US">
+               <synopsis>
+                       List parked calls.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="ParkingLot">
+                               <para>If specified, only show parked calls from the parking lot with this name.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>List parked calls.</para>
+               </description>
+       </manager>
+       <managerEvent language="en_US" name="ParkedCall">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel is parked.</synopsis>
+                       <syntax>
+                               <parameter name="ChannelParkee">
+                               </parameter>
+                               <parameter name="ChannelStateParkee">
+                                       <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+                               </parameter>
+                               <parameter name="ChannelStateDescParkee">
+                                       <enumlist>
+                                               <enum name="Down"/>
+                                               <enum name="Rsrvd"/>
+                                               <enum name="OffHook"/>
+                                               <enum name="Dialing"/>
+                                               <enum name="Ring"/>
+                                               <enum name="Ringing"/>
+                                               <enum name="Up"/>
+                                               <enum name="Busy"/>
+                                               <enum name="Dialing Offhook"/>
+                                               <enum name="Pre-ring"/>
+                                               <enum name="Unknown"/>
+                                       </enumlist>
+                               </parameter>
+                               <parameter name="CallerIDNumParkee">
+                               </parameter>
+                               <parameter name="CallerIDNameParkee">
+                               </parameter>
+                               <parameter name="ConnectedLineNumParkee">
+                               </parameter>
+                               <parameter name="ConnectedLineNameParkee">
+                               </parameter>
+                               <parameter name="AccountCodeParkee">
+                               </parameter>
+                               <parameter name="ContextParkee">
+                               </parameter>
+                               <parameter name="ExtenParkee">
+                               </parameter>
+                               <parameter name="PriorityParkee">
+                               </parameter>
+                               <parameter name="UniqueidParkee">
+                               </parameter>
+                               <parameter name="ChannelParker">
+                               </parameter>
+                               <parameter name="ChannelStateParker">
+                               <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+                               </parameter>
+                               <parameter name="ChannelStateDescParker">
+                                       <enumlist>
+                                               <enum name="Down"/>
+                                               <enum name="Rsrvd"/>
+                                               <enum name="OffHook"/>
+                                               <enum name="Dialing"/>
+                                               <enum name="Ring"/>
+                                               <enum name="Ringing"/>
+                                               <enum name="Up"/>
+                                               <enum name="Busy"/>
+                                               <enum name="Dialing Offhook"/>
+                                               <enum name="Pre-ring"/>
+                                               <enum name="Unknown"/>
+                                       </enumlist>
+                               </parameter>
+                               <parameter name="CallerIDNumParker">
+                               </parameter>
+                               <parameter name="CallerIDNameParker">
+                               </parameter>
+                               <parameter name="ConnectedLineNumParker">
+                               </parameter>
+                               <parameter name="ConnectedLineNameParker">
+                               </parameter>
+                               <parameter name="AccountCodeParker">
+                               </parameter>
+                               <parameter name="ContextParker">
+                               </parameter>
+                               <parameter name="ExtenParker">
+                               </parameter>
+                               <parameter name="PriorityParker">
+                               </parameter>
+                               <parameter name="UniqueidParker">
+                               </parameter>
+                               <parameter name="Parkinglot">
+                                       <para>Name of the parking lot that the parkee is parked in</para>
+                               </parameter>
+                               <parameter name="ParkingSpace">
+                                       <para>Parking Space that the parkee is parked in</para>
+                               </parameter>
+                               <parameter name="ParkingTimeout">
+                               <para>Time remaining until the parkee is forcefully removed from parking in seconds</para>
+                               </parameter>
+                               <parameter name="ParkingDuration">
+                                       <para>Time the parkee has been in the parking bridge (in seconds)</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ParkedCallTimeOut">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel leaves a parking lot due to reaching the time limit of being parked.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="ParkedCallGiveUp">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel leaves a parking lot because it hung up without being answered.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="UnParkedCall">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel leaves a parking lot because it was retrieved from the parking lot and reconnected.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
+                               <parameter name="ChannelRetriever">
+                               </parameter>
+                               <parameter name="ChannelStateRetriever">
+                                       <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+                               </parameter>
+                               <parameter name="ChannelStateDescRetriever">
+                                       <enumlist>
+                                               <enum name="Down"/>
+                                               <enum name="Rsrvd"/>
+                                               <enum name="OffHook"/>
+                                               <enum name="Dialing"/>
+                                               <enum name="Ring"/>
+                                               <enum name="Ringing"/>
+                                               <enum name="Up"/>
+                                               <enum name="Busy"/>
+                                               <enum name="Dialing Offhook"/>
+                                               <enum name="Pre-ring"/>
+                                               <enum name="Unknown"/>
+                                       </enumlist>
+                               </parameter>
+                               <parameter name="CallerIDNumRetriever">
+                               </parameter>
+                               <parameter name="CallerIDNameRetriever">
+                               </parameter>
+                               <parameter name="ConnectedLineNumRetriever">
+                               </parameter>
+                               <parameter name="ConnectedLineNameRetriever">
+                               </parameter>
+                               <parameter name="AccountCodeRetriever">
+                               </parameter>
+                               <parameter name="ContextRetriever">
+                               </parameter>
+                               <parameter name="ExtenRetriever">
+                               </parameter>
+                               <parameter name="PriorityRetriever">
+                               </parameter>
+                               <parameter name="UniqueidRetriever">
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+ ***/
+
+/*! \brief subscription to the parking lot topic */
+static struct stasis_subscription *parking_sub;
+
+static struct ast_parked_call_payload *parked_call_payload_from_failure(struct ast_channel *chan)
+{
+       RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
+
+       parkee_snapshot = ast_channel_snapshot_create(chan);
+       if (!parkee_snapshot) {
+               return NULL;
+       }
+
+       return ast_parked_call_payload_create(PARKED_CALL_FAILED, parkee_snapshot, NULL, NULL, NULL, 0, 0, 0);
+}
+
+static struct ast_parked_call_payload *parked_call_payload_from_parked_user(struct parked_user *pu, enum ast_parked_call_event_type event_type)
+{
+       RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
+       long int timeout;
+       long int duration;
+       struct timeval now = ast_tvnow();
+       const char *lot_name = pu->lot->name;
+
+       if (!pu->parker) {
+               return NULL;
+       }
+
+       parkee_snapshot = ast_channel_snapshot_create(pu->chan);
+
+       if (!parkee_snapshot) {
+               return NULL;
+       }
+
+       timeout = pu->start.tv_sec + (long) pu->time_limit - now.tv_sec;
+       duration = now.tv_sec - pu->start.tv_sec;
+
+       return ast_parked_call_payload_create(event_type, parkee_snapshot, pu->parker, pu->retriever, lot_name, pu->parking_space, timeout, duration);
+
+}
+
+/*! \brief Builds a manager string based on the contents of a parked call payload */
+static struct ast_str *manager_build_parked_call_string(const struct ast_parked_call_payload *payload)
+{
+       struct ast_str *out = ast_str_create(1024);
+       RAII_VAR(struct ast_str *, parkee_string, NULL, ast_free);
+       RAII_VAR(struct ast_str *, parker_string, NULL, ast_free);
+       RAII_VAR(struct ast_str *, retriever_string, NULL, ast_free);
+
+       if (!out) {
+               return NULL;
+       }
+
+       parkee_string = ast_manager_build_channel_state_string_suffix(payload->parkee, "Parkee");
+
+       if (payload->parker) {
+               parker_string = ast_manager_build_channel_state_string_suffix(payload->parker, "Parker");
+       }
+
+       if (payload->retriever) {
+               retriever_string = ast_manager_build_channel_state_string_suffix(payload->retriever, "Retriever");
+       }
+
+       ast_str_set(&out, 0,
+               "%s" /* parkee channel state */
+               "%s" /* parker channel state */
+               "%s" /* retriever channel state (when available) */
+               "Parkinglot: %s\r\n"
+               "ParkingSpace: %u\r\n"
+               "ParkingTimeout: %lu\r\n"
+               "ParkingDuration: %lu\r\n",
+
+               ast_str_buffer(parkee_string),
+               parker_string ? ast_str_buffer(parker_string) : "",
+               retriever_string ? ast_str_buffer(retriever_string) : "",
+               payload->parkinglot,
+               payload->parkingspace,
+               payload->timeout,
+               payload->duration);
+
+       return out;
+}
+
+static int manager_parking_status_single_lot(struct mansession *s, const struct message *m, const char *id_text, const char *lot_name)
+{
+       RAII_VAR(struct parking_lot *, curlot, NULL, ao2_cleanup);
+       struct parked_user *curuser;
+       struct ao2_iterator iter_users;
+       int total = 0;
+
+       curlot = parking_lot_find_by_name(lot_name);
+
+       if (!curlot) {
+               astman_send_error(s, m, "Requested parking lot could not be found.");
+               return RESULT_SUCCESS;
+       }
+
+       astman_send_ack(s, m, "Parked calls will follow");
+
+       iter_users = ao2_iterator_init(curlot->parked_users, 0);
+       while ((curuser = ao2_iterator_next(&iter_users))) {
+               RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+               RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+               payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
+               if (!payload) {
+                       astman_send_error(s, m, "Failed to retrieve parking data about a parked user.");
+                       return RESULT_FAILURE;
+               }
+
+               parked_call_string = manager_build_parked_call_string(payload);
+               if (!parked_call_string) {
+                       astman_send_error(s, m, "Failed to retrieve parkingd ata about a parked user.");
+                       return RESULT_FAILURE;
+               }
+
+               total++;
+
+               astman_append(s, "Event: ParkedCall\r\n"
+                       "%s" /* The parked call string */
+                       "%s" /* The action ID */
+                       "\r\n",
+                       ast_str_buffer(parked_call_string),
+                       id_text);
+
+               ao2_ref(curuser, -1);
+       }
+
+       ao2_iterator_destroy(&iter_users);
+
+       astman_append(s,
+               "Event: ParkedCallsComplete\r\n"
+               "Total: %d\r\n"
+               "%s"
+               "\r\n",
+               total, id_text);
+
+       return RESULT_SUCCESS;
+}
+
+static int manager_parking_status_all_lots(struct mansession *s, const struct message *m, const char *id_text)
+{
+       struct parked_user *curuser;
+       struct ao2_container *lot_container;
+       struct ao2_iterator iter_lots;
+       struct ao2_iterator iter_users;
+       struct parking_lot *curlot;
+       int total = 0;
+
+       lot_container = get_parking_lot_container();
+
+       if (!lot_container) {
+               ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
+               astman_send_error(s, m, "Could not create parking lot list");
+               return RESULT_SUCCESS;
+       }
+
+       iter_lots = ao2_iterator_init(lot_container, 0);
+
+       astman_send_ack(s, m, "Parked calls will follow");
+
+       while ((curlot = ao2_iterator_next(&iter_lots))) {
+               iter_users = ao2_iterator_init(curlot->parked_users, 0);
+               while ((curuser = ao2_iterator_next(&iter_users))) {
+                       RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+                       RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+                       payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
+                       if (!payload) {
+                               return RESULT_FAILURE;
+                       }
+
+                       parked_call_string = manager_build_parked_call_string(payload);
+                       if (!payload) {
+                               return RESULT_FAILURE;
+                       }
+
+                       total++;
+
+                       astman_append(s, "Event: ParkedCall\r\n"
+                               "%s" /* The parked call string */
+                               "%s" /* The action ID */
+                               "\r\n",
+                               ast_str_buffer(parked_call_string),
+                               id_text);
+
+                       ao2_ref(curuser, -1);
+               }
+               ao2_iterator_destroy(&iter_users);
+               ao2_ref(curlot, -1);
+       }
+
+       ao2_iterator_destroy(&iter_lots);
+
+       astman_append(s,
+               "Event: ParkedCallsComplete\r\n"
+               "Total: %d\r\n"
+               "%s"
+               "\r\n",
+               total, id_text);
+
+       return RESULT_SUCCESS;
+}
+
+static int manager_parking_status(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       const char *lot_name = astman_get_header(m, "ParkingLot");
+       char id_text[256] = "";
+
+       if (!ast_strlen_zero(id)) {
+               snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+       }
+
+       if (!ast_strlen_zero(lot_name)) {
+               return manager_parking_status_single_lot(s, m, id_text, lot_name);
+       }
+
+       return manager_parking_status_all_lots(s, m, id_text);
+
+}
+
+static int manager_append_event_parking_lot_data_cb(void *obj, void *arg, void *data, int flags)
+{
+       struct parking_lot *curlot = obj;
+       struct mansession *s = arg;
+       char *id_text = data;
+
+       astman_append(s, "Event: Parkinglot\r\n"
+               "Name: %s\r\n"
+               "StartSpace: %d\r\n"
+               "StopSpace: %d\r\n"
+               "Timeout: %d\r\n"
+               "%s" /* The Action ID */
+               "\r\n",
+               curlot->name,
+               curlot->cfg->parking_start,
+               curlot->cfg->parking_stop,
+               curlot->cfg->parkingtime,
+               id_text);
+
+       return 0;
+}
+
+static int manager_parking_lot_list(struct mansession *s, const struct message *m)
+{
+       const char *id = astman_get_header(m, "ActionID");
+       char id_text[256] = "";
+       struct ao2_container *lot_container;
+
+       if (!ast_strlen_zero(id)) {
+               snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
+       }
+
+       lot_container = get_parking_lot_container();
+
+       if (!lot_container) {
+               ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
+               astman_send_error(s, m, "Could not create parking lot list");
+               return -1;
+       }
+
+       astman_send_ack(s, m, "Parking lots will follow");
+
+       ao2_callback_data(lot_container, OBJ_MULTIPLE | OBJ_NODATA, manager_append_event_parking_lot_data_cb, s, id_text);
+
+       astman_append(s,
+               "Event: ParkinglotsComplete\r\n"
+               "%s"
+               "\r\n",id_text);
+
+       return RESULT_SUCCESS;
+}
+
+void publish_parked_call_failure(struct ast_channel *parkee)
+{
+       RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       payload = parked_call_payload_from_failure(parkee);
+       if (!payload) {
+               return;
+       }
+
+       msg = stasis_message_create(ast_parked_call_type(), payload);
+       if (!msg) {
+               return;
+       }
+
+       stasis_publish(ast_parking_topic(), msg);
+}
+
+void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type)
+{
+       RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       payload = parked_call_payload_from_parked_user(pu, event_type);
+       if (!payload) {
+               return;
+       }
+
+       msg = stasis_message_create(ast_parked_call_type(), payload);
+       if (!msg) {
+               return;
+       }
+
+       stasis_publish(ast_parking_topic(), msg);
+}
+
+static void parked_call_message_response(struct ast_parked_call_payload *parked_call)
+{
+       char *event_type = "";
+       RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
+
+       switch (parked_call->event_type) {
+       case PARKED_CALL:
+               event_type = "ParkedCall";
+               break;
+       case PARKED_CALL_TIMEOUT:
+               event_type = "ParkedCallTimeOut";
+               break;
+       case PARKED_CALL_GIVEUP:
+               event_type = "ParkedCallGiveUp";
+               break;
+       case PARKED_CALL_UNPARKED:
+               event_type = "UnParkedCall";
+               break;
+       case PARKED_CALL_FAILED:
+               /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */
+               return;
+       }
+
+       parked_call_string = manager_build_parked_call_string(parked_call);
+       if (!parked_call_string) {
+               ast_log(LOG_ERROR, "Failed to issue an AMI event of '%s' in response to a stasis message.\n", event_type);
+               return;
+       }
+
+       manager_event(EVENT_FLAG_CALL, event_type,
+                       "%s",
+                       ast_str_buffer(parked_call_string)
+               );
+}
+
+static void parking_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+       if (stasis_message_type(message) == ast_parked_call_type()) {
+               struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
+               parked_call_message_response(parked_call_message);
+       }
+}
+
+static void parking_manager_enable_stasis(void)
+{
+       ast_parking_stasis_init();
+       if (!parking_sub) {
+               parking_sub = stasis_subscribe(ast_parking_topic(), parking_event_cb, NULL);
+       }
+}
+
+int load_parking_manager(void)
+{
+       int res;
+
+       res = ast_manager_register_xml_core("Parkinglots", 0, manager_parking_lot_list);
+       res |= ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
+       /* TODO Add a 'Park' manager action */
+       parking_manager_enable_stasis();
+       return res ? -1 : 0;
+}
+
+static void parking_manager_disable_stasis(void)
+{
+       parking_sub = stasis_unsubscribe(parking_sub);
+       ast_parking_stasis_disable();
+}
+
+void unload_parking_manager(void)
+{
+       ast_manager_unregister("Parkinglots");
+       ast_manager_unregister("ParkedCalls");
+       parking_manager_disable_stasis();
+}
diff --git a/res/parking/parking_ui.c b/res/parking/parking_ui.c
new file mode 100644 (file)
index 0000000..5b432b6
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Call Parking CLI commands
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+static void display_parked_call(struct parked_user *user, int fd)
+{
+       ast_cli(fd, "  Space: %d\n", user->parking_space);
+       ast_cli(fd, "  Channel: %s\n", ast_channel_name(user->chan));
+       ast_cli(fd, "  Parker: %s\n", user->parker ? user->parker->name : "<unknown>");
+       ast_cli(fd, "\n");
+}
+
+static int display_parked_users_cb(void *obj, void *arg, int flags)
+{
+       int *fd = arg;
+       struct parked_user *user = obj;
+       display_parked_call(user, *fd);
+       return 0;
+}
+
+static void display_parking_lot(struct parking_lot *lot, int fd)
+{
+       ast_cli(fd, "Parking Lot: %s\n--------------------------------------------------------------------------\n", lot->name);
+       ast_cli(fd, "Parking Extension   :  %s\n", lot->cfg->parkext);
+       ast_cli(fd, "Parking Context     :  %s\n", lot->cfg->parking_con);
+       ast_cli(fd, "Parking Spaces      :  %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop);
+       ast_cli(fd, "Parking Time        :  %u sec\n", lot->cfg->parkingtime);
+       ast_cli(fd, "Comeback to Origin  :  %s\n", lot->cfg->comebacktoorigin ? "yes" : "no");
+       ast_cli(fd, "Comeback Context    :  %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : "");
+       ast_cli(fd, "Comeback Dial Time  :  %u sec\n", lot->cfg->comebackdialtime);
+       ast_cli(fd, "MusicOnHold Class   :  %s\n", lot->cfg->mohclass);
+       ast_cli(fd, "Enabled             :  %s\n", (lot->mode == PARKINGLOT_DISABLED) ? "no" : "yes");
+       ast_cli(fd, "\n");
+}
+
+static int display_parking_lot_cb(void *obj, void *arg, int flags)
+{
+       int *fd = arg;
+       struct parking_lot *lot = obj;
+       display_parking_lot(lot, *fd);
+       return 0;
+}
+
+static void cli_display_parking_lot(int fd, const char *name)
+{
+       RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+       lot = parking_lot_find_by_name(name);
+
+       /* If the parking lot couldn't be found with the search, also abort. */
+       if (!lot) {
+               ast_cli(fd, "Could not find parking lot '%s'\n\n", name);
+               return;
+       }
+
+       display_parking_lot(lot, fd);
+
+       ast_cli(fd, "Parked Calls\n------------\n");
+
+       if (!ao2_container_count(lot->parked_users)) {
+               ast_cli(fd, "  (none)\n");
+               ast_cli(fd, "\n\n");
+               return;
+       }
+
+       ao2_callback(lot->parked_users, OBJ_MULTIPLE | OBJ_NODATA, display_parked_users_cb, &fd);
+       ast_cli(fd, "\n");
+}
+
+static void cli_display_parking_lot_list(int fd)
+{
+       struct ao2_container *lot_container;
+
+       lot_container = get_parking_lot_container();
+
+       if (!lot_container) {
+               ast_cli(fd, "Failed to obtain parking lot list.\n\n");
+               return;
+       }
+
+       ao2_callback(lot_container, OBJ_MULTIPLE | OBJ_NODATA, display_parking_lot_cb, &fd);
+       ast_cli(fd, "\n");
+}
+
+struct parking_lot_complete {
+       int seeking;    /*! Nth match to return. */
+       int which;      /*! Which match currently on. */
+};
+
+static int complete_parking_lot_search(void *obj, void *arg, void *data, int flags)
+{
+       struct parking_lot_complete *search = data;
+       if (++search->which > search->seeking) {
+               return CMP_MATCH;
+       }
+       return 0;
+}
+
+static char *complete_parking_lot(const char *word, int seeking)
+{
+       char *ret = NULL;
+       struct parking_lot *lot;
+       struct ao2_container *global_lots = get_parking_lot_container();
+       struct parking_lot_complete search = {
+               .seeking = seeking,
+               };
+
+       lot = ao2_callback_data(global_lots, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+               complete_parking_lot_search, (char *) word, &search);
+
+       if (!lot) {
+               return NULL;
+       }
+
+       ret = ast_strdup(lot->name);
+       ao2_ref(lot, -1);
+       return ret;
+}
+
+/* \brief command parking show <name> */
+static char *handle_show_parking_lot_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "parking show";
+               e->usage =
+                       "Usage: parking show [name]\n"
+                       "       Shows a list of parking lots or details of a specific parking lot.";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 2) {
+                       return complete_parking_lot(a->word, a->n);
+               }
+               return NULL;
+       }
+
+       ast_cli(a->fd, "\n");
+
+       if (a->argc == 2) {
+               cli_display_parking_lot_list(a->fd);
+               return CLI_SUCCESS;
+       }
+
+       if (a->argc == 3) {
+               cli_display_parking_lot(a->fd, a->argv[2]);
+               return CLI_SUCCESS;
+       }
+
+       return CLI_SHOWUSAGE;
+}
+
+static struct ast_cli_entry cli_parking_lot[] = {
+       AST_CLI_DEFINE(handle_show_parking_lot_cmd, "Show a parking lot or a list of all parking lots."),
+};
+
+int load_parking_ui(void)
+{
+       return ast_cli_register_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
+}
+
+void unload_parking_ui(void)
+{
+       ast_cli_unregister_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
+}
diff --git a/res/parking/res_parking.h b/res/parking/res_parking.h
new file mode 100644 (file)
index 0000000..7f66d6e
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Call Parking Resource Internal API
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+#include "asterisk/pbx.h"
+#include "asterisk/bridging.h"
+#include "asterisk/parking.h"
+#include "asterisk/stasis_channels.h"
+
+#define DEFAULT_PARKING_LOT "default"
+#define DEFAULT_PARKING_EXTEN "700"
+#define PARK_DIAL_CONTEXT "park-dial"
+
+enum park_call_resolution {
+       PARK_UNSET = 0,         /*! Nothing set a resolution. This should never be observed in practice. */
+       PARK_ABANDON,           /*! The channel for the parked call hung up */
+       PARK_TIMEOUT,           /*! The parked call stayed parked until the parking lot timeout was reached and was removed */
+       PARK_FORCED,            /*! The parked call was forcibly terminated by an unusual means in Asterisk */
+       PARK_ANSWERED,          /*! The parked call was retrieved successfully */
+};
+
+enum parked_call_feature_options {
+       OPT_PARKEDPLAY = 0,
+       OPT_PARKEDTRANSFERS,
+       OPT_PARKEDREPARKING,
+       OPT_PARKEDHANGUP,
+       OPT_PARKEDRECORDING,
+};
+
+enum parking_lot_modes {
+       PARKINGLOT_NORMAL = 0,          /*! The parking lot is configured normally and can accept new calls. Disable on reload if the config isn't replaced.
+                                        *  valid transitions: PARKINGLOT_DISABLED */
+       PARKINGLOT_DYNAMIC,             /*! The parking lot is a dynamically created parking lot. It can be parked to at any time. Disabled on last parked call leaving.
+                                        *  valid transitions: PARKINGLOT_DISABLED */
+       PARKINGLOT_DISABLED,            /*! The parking lot is no longer linked to a parking lot in configuration. It can no longer be parked to.
+                                        *  and it can not be parked to. This mode has no transitions. */
+};
+
+struct parking_lot_cfg {
+       int parking_start;                        /*!< First space in the parking lot */
+       int parking_stop;                         /*!< Last space in the parking lot */
+
+       unsigned int parkingtime;                 /*!< Analogous to parkingtime config option */
+       unsigned int comebackdialtime;            /*!< Analogous to comebackdialtime config option */
+       unsigned int parkfindnext;                /*!< Analogous to parkfindnext config option */
+       unsigned int parkext_exclusive;           /*!< Analogous to parkext_exclusive config option */
+       unsigned int parkaddhints;                /*!< Analogous to parkaddhints config option */
+       unsigned int comebacktoorigin;            /*!< Analogous to comebacktoorigin config option */
+       int parkedplay;                           /*!< Analogous to parkedplay config option */
+       int parkedcalltransfers;                  /*!< Analogous to parkedcalltransfers config option */
+       int parkedcallreparking;                  /*!< Analogous to parkedcallreparking config option */
+       int parkedcallhangup;                     /*!< Analogous to parkedcallhangup config option */
+       int parkedcallrecording;                  /*!< Analogous to parkedcallrecording config option */
+
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(name);               /*!< Name of the parking lot configuration object */
+               AST_STRING_FIELD(mohclass);           /*!< Analogous to mohclass config option */
+               AST_STRING_FIELD(parkext);            /*!< Analogous to parkext config option */
+               AST_STRING_FIELD(parking_con);        /*!< Analogous to context config option */
+               AST_STRING_FIELD(comebackcontext);    /*!< Analogous to comebackcontext config option */
+               AST_STRING_FIELD(courtesytone);       /*!< Analogous to courtesytone config option */
+       );
+};
+
+struct parking_lot {
+       int next_space;                           /*!< When using parkfindnext, which space we should start searching from next time we park */
+       struct ast_bridge *parking_bridge;        /*!< Bridged where parked calls will rest until they are answered or otherwise leave */
+       struct ao2_container *parked_users;       /*!< List of parked users rigidly ordered by their parking space */
+       struct parking_lot_cfg *cfg;              /*!< Reference to configuration object for the parking lot */
+       enum parking_lot_modes mode;              /*!< Whether a parking lot is operational, being reconfigured, primed for deletion, or dynamically created. */
+       int disable_mark;                         /*!< On reload, disable this parking lot if it doesn't receive a new configuration. */
+
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(name);               /*!< Name of the parking lot object */
+       );
+};
+
+struct parked_user {
+       struct ast_channel *chan;                 /*!< Parked channel */
+       struct ast_channel_snapshot *parker;      /*!< Snapshot of the channel that parked the call at the time of parking */
+       struct ast_channel_snapshot *retriever;   /*!< Snapshot of the channel that retrieves a parked call */
+       struct timeval start;                     /*!< When the call was parked */
+       int parking_space;                        /*!< Which parking space is used */
+       char comeback[AST_MAX_CONTEXT];           /*!< Where to go on parking timeout */
+       unsigned int time_limit;                  /*!< How long this specific channel may remain in the parking lot before timing out */
+       struct parking_lot *lot;      /*!< Which parking lot the user is parked to */
+       enum park_call_resolution resolution;     /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */
+};
+
+/*!
+ * \since 12
+ * \brief If a parking lot exists in the parking lot list already, update its status to match the provided
+ *        configuration and return a reference return a reference to it. Otherwise, create a parking lot
+ *        struct based on a parking lot configuration and return a reference to the new one.
+ *
+ * \param cfg The configuration being used as a reference to build the parking lot from.
+ *
+ * \retval A reference to the new parking lot
+ * \retval NULL if it was not found and could not be be allocated
+ *
+ * \note The parking lot will need to be unreffed if it ever falls out of scope
+ * \note The parking lot will automatically be added to the parking lot container if needed as part of this process
+ */
+struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *cfg);
+
+/*!
+ * \since 12
+ * \brief Remove a parking lot from the usable lists if it is no longer involved in any calls and no configuration currently claims it
+ *
+ * \param lot Which parking lot is being checked for elimination
+ *
+ * \note This should generally be called when something is happening that could cause a parking lot to die such as a call being unparked or
+ *       a parking lot no longer existing in configurations.
+ */
+void parking_lot_remove_if_unused(struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Create a new parking bridge
+ *
+ * \param bridge_lot Parking lot which the new bridge should be based on
+ *
+ * \retval NULL if the bridge can not be created
+ * \retval Newly created parking bridge
+ */
+struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot);
+
+/*!
+ * \since 12
+ * \brief Get a reference to a parking lot's bridge. If it doesn't exist, create it and get a reference.
+ *
+ * \param lot Which parking lot we need the bridge from. This parking lot must be locked before calling this function.
+ *
+ * \retval A reference to the ast_bridge associated with the parking lot
+ * \retval NULL if it didn't already have a bridge and one couldn't be created
+ *
+ * \note This bridge will need to be unreffed if it ever falls out of scope.
+ */
+struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Get an available parking space within a parking lot.
+ *
+ * \param lot Which parking lot we are getting a space from
+ * \param target_override If there is a specific slot we want, provide it here and we'll start from that position
+ *
+ * \retval -1 if No slot can be found
+ * \retval integer value of parking space selected
+ *
+ * \note lot should be locked before this is called and unlocked only after a parked_user with the space
+ *       returned has been added to the parking lot.
+ */
+int parking_lot_get_space(struct parking_lot *lot, int target_override);
+
+/*!
+ * \since 12
+ * \brief Determine if there is a parked user in a parking space and pull it from the parking lot if there is.
+ *
+ * \param lot Parking lot being pulled from
+ * \param target If < 0   search for the first occupied space in the parking lot
+ *               If >= 0  Only pull from the indicated target
+ *
+ * \retval NULL if no parked user could be pulled from the requested parking lot at the requested parking space
+ * \retval reference to the requested parked user
+ *
+ * \note The parked user will be removed from parking lot as part of this process
+ * \note Remove this reference with ao2_cleanup once it falls out of scope.
+ */
+struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target);
+
+/*!
+ * \since 12
+ * \brief Apply features based on the parking lot feature options
+ *
+ * \param chan Which channel's feature set is being modified
+ * \param lot parking lot which establishes the features used
+ * \param recipient_mode AST_FEATURE_FLAG_BYCALLER if the user is the retriever
+ *                       AST_FEATURE_FLAG_BYCALLEE if the user is the parkee
+ */
+void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode);
+
+/*!
+ * \since 12
+ * \brief Set necessary bridge roles on a channel that is about to enter a parking lot
+ *
+ * \param chan Entering channel
+ * \param lot The parking lot the channel will be entering
+ * \param force_ringing Use ringing instead of music on hold
+ */
+void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing);
+
+/*!
+ * \since 12
+ * \brief custom callback function for ast_bridge_channel_queue_playfile which plays a parking space
+ *        and optionally hangs up the call afterwards based on the payload in playfile.
+ */
+void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload);
+
+/*!
+ * \since 12
+ * \brief Setup timeout interval feature on an ast_bridge_features for parking
+ *
+ * \param features The ast_bridge_features we are establishing the interval hook on
+ * \param user The parked_user receiving the timeout duration limits
+ */
+void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user);
+
+/*!
+ * \since 12
+ * \brief Get a pointer to the parking lot container for purposes such as iteration
+ *
+ * \retval pointer to the parking lot container.
+ */
+struct ao2_container *get_parking_lot_container(void);
+
+/*!
+ * \since 12
+ * \brief Find a parking lot based on its name
+ *
+ * \param lot_name Name of the parking lot sought
+ *
+ * \retval The parking lot if found
+ * \retval NULL if no parking lot with the name specified exists
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+struct parking_lot *parking_lot_find_by_name(const char *lot_name);
+
+/*!
+ * \since 12
+ * \brief Find parking lot name from channel
+ *
+ * \param chan The channel we want the parking lot name for
+ *
+ * \retval name of the channel's assigned parking lot if it is defined by the channel in some way
+ * \retval name of the default parking lot if it is not
+ *
+ * \note Channel needs to be locked while the returned string is in use.
+ */
+const char *find_channel_parking_lot_name(struct ast_channel *chan);
+
+/*!
+ * \since 12
+ * \brief Flattens a peer name so that it can be written to/found from PBX extensions
+ *
+ * \param peername unflattened peer name. This will be flattened in place, so expect it to change.
+ */
+void flatten_peername(char *peername);
+
+/*!
+ * \since 12
+ * \brief Set a channel's position in the PBX after timeout using the parking lot settings
+ *
+ * \param pu Parked user who is entering/reentering the PBX
+ * \param lot Parking lot the user was removed from.
+ *
+ * \retval 0 Position set successfully
+ * \retval -1 Failed to set the position
+ */
+int comeback_goto(struct parked_user *pu, struct parking_lot *lot);
+
+/*!
+ * \since 12
+ * \brief Pull a parked user out of its parking lot. Use this when you don't want to use the parked user afterwards.
+ * \param user The parked user being pulled.
+ *
+ * \retval 0 on success
+ * \retval -1 if the user didn't have its parking lot set
+ */
+int unpark_parked_user(struct parked_user *user);
+
+/*!
+ * \since 12
+ * \brief Publish a stasis parked call message for the channel indicating failure to park.
+ *
+ * \param parkee channel belonging to the failed parkee
+ */
+void publish_parked_call_failure(struct ast_channel *parkee);
+
+/*!
+ * \since 12
+ * \brief Publish a stasis parked call message for a given parked user
+ *
+ * \param pu pointer to a parked_user that we are generating the message for
+ * \param event_type What parked call event type is provoking this message
+ */
+void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type);
+
+/*!
+ * \since 12
+ * \brief Function to prepare a channel for parking by determining which parking bridge should
+ *        be used, setting up a park common datastore so that the parking bridge will have access
+ *        to necessary parking information when joining, and applying various bridge roles to the
+ *        channel.
+ *
+ * \param parkee The channel being preparred for parking
+ * \param parker The channel initiating the park; may be the parkee as well
+ * \param app_data arguments supplied to the Park application. May be NULL.
+ * \param silence_announcements optional pointer to an integer where we want to store the silence option flag
+ *        this value should be initialized to 0 prior to calling park_common_setup.
+ *
+ * \retval reference to a parking bridge if successful
+ * \retval NULL on failure
+ *
+ * \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
+ */
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
+       const char *app_data, int *silence_announcements);
+
+struct park_common_datastore {
+       char *parker_uuid;           /*!< Unique ID of the channel parking the call. */
+       char *comeback_override;     /*!< Optional goto string for where to send the call after we are done */
+       int randomize;               /*!< Pick a parking space to enter on at random */
+       int time_limit;              /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */
+       int silence_announce;        /*!< Used when a call parks itself to keep it from hearing the parked call announcement */
+};
+
+/*!
+ * \since 12
+ * \brief Function that pulls data from the park common datastore on a channel in order to apply it to
+ *        the parked user struct upon bridging.
+ *
+ * \param parkee The channel entering parking with the datastore we are checking
+ * \param parker_uuid pointer to a string pointer for placing the name of the channel that parked parkee
+ * \param comeback_override pointer to a string pointer for placing the comeback_override option
+ * \param randomize integer pointer to an integer for placing the randomize option
+ * \param time_limit integer pointer to an integer for placing the time limit option
+ * \param silence_announce pointer to an integer for placing the silence_announcements option
+ */
+void get_park_common_datastore_data(struct ast_channel *parkee,
+               char **parker_uuid, char **comeback_override,
+               int *randomize, int *time_limit, int *silence_announce);
+
+/*!
+ * \since 12
+ * \brief Execution function for the parking application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ *
+ * \note this function should only be used to register the parking application and not generally to park calls.
+ */
+int park_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Execution function for the parked call application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ */
+int parked_call_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Execution function for the park and retrieve application
+ *
+ * \param chan ast_channel entering the application
+ * \param data arguments to the application
+ *
+ * \retval 0 the application executed in such a way that the channel should proceed in the dial plan
+ * \retval -1 the channel should no longer proceed through the dial plan
+ *
+ * \note this function should only be used to register the park and announce application and not generally to park and announce.
+ */
+int park_and_announce_app_exec(struct ast_channel *chan, const char *data);
+
+/*!
+ * \since 12
+ * \brief Register CLI commands
+ *
+ * \retval 0 if successful
+ * \retval -1 on failure
+ */
+int load_parking_ui(void);
+
+/*!
+ * \since 12
+ * \brief Unregister CLI commands
+ */
+void unload_parking_ui(void);
+
+/*!
+ * \since 12
+ * \brief Register manager actions and setup subscriptions for stasis events
+ */
+int load_parking_manager(void);
+
+/*!
+ * \since 12
+ * \brief Unregister manager actions and remove subscriptions for stasis events
+ */
+void unload_parking_manager(void);
+
+/*!
+ * \since 12
+ * \brief Register bridge features for parking
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int load_parking_bridge_features(void);
+
+/*!
+ * \since 12
+ * \brief Unregister features registered by load_parking_bridge_features
+ */
+void unload_parking_bridge_features(void);
diff --git a/res/res_parking.c b/res/res_parking.c
new file mode 100644 (file)
index 0000000..72627f1
--- /dev/null
@@ -0,0 +1,831 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jonathan Rose <jrose@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Call Parking Resource
+ *
+ * \author Jonathan Rose <jrose@digium.com>
+ */
+
+/*** MODULEINFO
+       <depend>bridge_holding</depend>
+       <support_level>core</support_level>
+ ***/
+
+/*** DOCUMENTATION
+       <configInfo name="res_parking" language="en_US">
+               <configFile name="res_parking.conf">
+                       <configObject name="globals">
+                               <synopsis>Options that apply to every parking lot</synopsis>
+                       </configObject>
+                       <configObject name="parking_lot">
+                               <synopsis>Defined parking lots for res_parking to use to park calls on</synopsis>
+                               <configOption name="context" default="parkedcalls">
+                                       <synopsis>The name of the context where calls are parked and picked up from.</synopsis>
+                                       <description><para>This option is only used if parkext is set.</para></description>
+                               </configOption>
+                               <configOption name="parkext">
+                                       <synopsis>Extension to park calls to this parking lot.</synopsis>
+                                       <description><para>If this option is used, this extension will automatically be created to place calls into
+                        parking lots. In addition, if parkext_exclusive is set for this parking lot, the name of the parking lot
+                        will be included in the application's arguments so that it only parks to this parking lot. The extension
+                        will be created in <literal>context</literal>. Using this option also creates extensions for retrieving
+                        parked calls from the parking spaces in the same context.</para></description>
+                               </configOption>
+                               <configOption name="parkext_exclusive" default="no">
+                                       <synopsis>If yes, the extension registered as parkext will park exclusively to this parking lot.</synopsis>
+                               </configOption>
+                               <configOption name="parkpos" default="701-750">
+                                       <synopsis>Numerical range of parking spaces which can be used to retrieve parked calls.</synopsis>
+                                       <description><para>If parkext is set, these extensions will automatically be mapped in <literal>context</literal>
+                                               in order to pick up calls parked to these parking spaces.</para></description>
+                               </configOption>
+                               <configOption name="parkinghints" default="no">
+                                       <synopsis>If yes, this parking lot will add hints automatically for parking spaces.</synopsis>
+                               </configOption>
+                               <configOption name="parkingtime" default="45">
+                                       <synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
+                               </configOption>
+                               <configOption name="parkedmusicclass">
+                                       <synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
+                               </configOption>
+                               <configOption name="comebacktoorigin" default="yes">
+                                       <synopsis>Determines what should be done with the parked channel if no one picks it up before it times out.</synopsis>
+                                       <description><para>Valid Options:</para>
+                                               <enumlist>
+                                                       <enum name="yes">
+                                                               <para>Automatically have the parked channel dial the device that parked the call with dial
+                                                                       timeout set by the <literal>parkingtime</literal> option. When the call times out an extension
+                                                                       to dial the PARKER will automatically be created in the <literal>park-dial</literal> context with
+                                                                       an extension of the flattened parker device name. If the call is not answered, the parked channel
+                                                                       that is timing out will continue in the dial plan at that point if there are more priorities in
+                                                                       the extension (which won't be the case unless the dialplan deliberately includes such priorities
+                                                                       in the <literal>park-dial</literal> context through pattern matching or deliberately written
+                                                                       flattened peer extensions).</para>
+                                                       </enum>
+                                                       <enum name="no">
+                                                               <para>Place the call into the PBX at <literal>comebackcontext</literal> instead. The extension will
+                                                                       still be set as the flattened peer name. If an extension the flattened peer name isn't available
+                                                                       then it will fall back to the <literal>s</literal> extension. If that also is unavailable it will
+                                                                       attempt to fall back to <literal>s@default</literal>. The normal dial extension will still be
+                                                                       created in the <literal>park-dial</literal> context with the extension also being the flattened
+                                                                       peer name.</para>
+                                                       </enum>
+                                               </enumlist>
+                                               <note><para>Flattened Peer Names - Extensions can not include slash characters since those are used for pattern
+                                                       matching. When a peer name is flattened, slashes become underscores. For example if the parker of a call
+                                                       is called <literal>SIP/0004F2040001</literal> then flattened peer name and therefor the extensions created
+                                                       and used on timeouts will be <literal>SIP_0004F204001</literal>.</para></note>
+                                               <note><para>When parking times out and the channel returns to the dial plan, the following variables are set:
+                                               </para></note>
+                                               <variablelist>
+                                                       <variable name="PARKINGSLOT">
+                                                               <para>extension that the call was parked in prior to timing out.</para>
+                                                       </variable>
+                                                       <variable name="PARKEDLOT">
+                                                               <para>name of the lot that the call was parked in prior to timing out.</para>
+                                                       </variable>
+                                                       <variable name="PARKER">
+                                                               <para>The device that parked the call</para>
+                                                       </variable>
+                                               </variablelist>
+                                       </description>
+                               </configOption>
+                               <configOption name="comebackdialtime" default="30">
+                                       <synopsis>Timeout for the Dial extension created to call back the parker when a parked call times out.</synopsis>
+                               </configOption>
+                               <configOption name="comebackcontext" default="parkedcallstimeout">
+                                       <synopsis>Context where parked calls will enter the PBX on timeout when comebacktoorigin=no</synopsis>
+                                       <description><para>The extension the call enters will prioritize the flattened peer name in this context.
+                                               If the flattened peer name extension is unavailable, then the 's' extension in this context will be
+                                               used. If that also is unavailable, the 's' extension in the 'default' context will be used.</para>
+                                       </description>
+                               </configOption>
+                               <configOption name="courtesytone">
+                                       <synopsis>If the name of a sound file is provided, use this as the courtesy tone</synopsis>
+                                       <description><para>By default, this tone is only played to the caller of a parked call. Who receives the tone
+                                               can be changed using the <literal>parkedplay</literal> option.</para>
+                                       </description>
+                               </configOption>
+                               <configOption name="parkedplay" default="caller">
+                                       <synopsis>Who we should play the courtesytone to on the pickup of a parked call from this lot</synopsis>
+                                       <description>
+                                               <enumlist>
+                                                       <enum name="no"><para>Apply to neither side.</para></enum>
+                                                       <enum name="caller"><para>Apply to only to the caller picking up the parked call.</para></enum>
+                                                       <enum name="callee"><para>Apply to only to the parked call being picked up.</para></enum>
+                                                       <enum name="both"><para>Apply to both the caller and the callee.</para></enum>
+                                               </enumlist>
+                                               <note><para>If courtesy tone is not specified then this option will be ignored.</para></note>
+                                       </description>
+                               </configOption>
+                               <configOption name="parkedcalltransfers" default="no">
+                                       <synopsis>Apply the DTMF transfer features to the caller and/or callee when parked calls are picked up.</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="parkedcallreparking" default="no">
+                                       <synopsis>Apply the DTMF parking feature to the caller and/or callee when parked calls are picked up.</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="parkedcallhangup" default="no">
+                                       <synopsis>Apply the DTMF Hangup feature to the caller and/or callee when parked calls are picked up.</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="parkedcallrecording" default="no">
+                                       <synopsis>Apply the DTMF recording features to the caller and/or callee when parked calls are picked up</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_parking']/configFile[@name='res_parking.conf']/configObject[@name='parking_lot']/configOption[@name='parkedplay']/description/enumlist)" />
+                                       </description>
+                               </configOption>
+                               <configOption name="findslot" default="first">
+                                       <synopsis>Rule to use when trying to figure out which parking space a call should be parked with.</synopsis>
+                                       <description>
+                                               <enumlist>
+                                                       <enum name="first"><para>Always try to place in the lowest available space in the parking lot</para></enum>
+                                                       <enum name="next"><para>Track the last parking space used and always attempt to use the one immediately after.
+                                                       </para></enum>
+                                               </enumlist>
+                                       </description>
+                               </configOption>
+                               <configOption name="courtesytone">
+                                       <synopsis>If set, the sound set will be played to whomever is set by parkedplay</synopsis>
+                               </configOption>
+                       </configObject>
+               </configFile>
+       </configInfo>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "parking/res_parking.h"
+#include "asterisk/config.h"
+#include "asterisk/config_options.h"
+#include "asterisk/event.h"
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/features.h"
+#include "asterisk/manager.h"
+
+#define PARKED_CALL_APPLICATION "ParkedCall"
+#define PARK_AND_ANNOUNCE_APPLICATION "ParkAndAnnounce"
+
+/* TODO Add unit tests for parking */
+
+static int parking_lot_sort_fn(const void *obj_left, const void *obj_right, int flags)
+{
+       const struct parking_lot *left = obj_left;
+       const struct parking_lot *right = obj_right;
+       const char *right_key = obj_right;
+       int cmp;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       default:
+       case OBJ_POINTER:
+               right_key = right->name;
+               /* Fall through */
+       case OBJ_KEY:
+               cmp = strcmp(left->name, right_key);
+               break;
+       case OBJ_PARTIAL_KEY:
+               cmp = strncmp(left->name, right_key, strlen(right_key));
+       }
+       return cmp;
+}
+
+/*! All parking lots that are currently alive in some fashion can be obtained from here */
+static struct ao2_container *parking_lot_container;
+
+static void *parking_config_alloc(void);
+
+static void *parking_lot_cfg_alloc(const char *cat);
+static void *named_item_find(struct ao2_container *container, const char *name); /* XXX This is really just a generic string find. Move to astobj2.c? */
+
+static int config_parking_preapply(void);
+static void link_configured_disable_marked_lots(void);
+
+struct parking_global_config {
+       /* TODO Implement dynamic parking lots. Entirely. */
+       int parkeddynamic;
+};
+
+struct parking_config {
+       struct parking_global_config *global;
+       struct ao2_container *parking_lots;
+};
+
+static struct aco_type global_option = {
+       .type = ACO_GLOBAL,
+       .name = "globals",
+       .item_offset = offsetof(struct parking_config, global),
+       .category_match = ACO_WHITELIST,
+       .category = "^general$",
+};
+
+struct aco_type *global_options[] = ACO_TYPES(&global_option);
+
+static struct aco_type parking_lot_type = {
+       .type = ACO_ITEM,
+       .name = "parking_lot",
+       .category_match = ACO_BLACKLIST,
+       .category = "^(general)$",
+       .item_alloc = parking_lot_cfg_alloc,
+       .item_find = named_item_find,
+       .item_offset = offsetof(struct parking_config, parking_lots),
+};
+
+struct aco_type *parking_lot_types[] = ACO_TYPES(&parking_lot_type);
+
+struct aco_file parking_lot_conf = {
+       .filename = "res_parking.conf",
+       .types = ACO_TYPES(&global_option, &parking_lot_type),
+};
+
+static AO2_GLOBAL_OBJ_STATIC(globals);
+
+CONFIG_INFO_STANDARD(cfg_info, globals, parking_config_alloc,
+       .files = ACO_FILES(&parking_lot_conf),
+       .pre_apply_config = config_parking_preapply,
+       .post_apply_config = link_configured_disable_marked_lots,
+);
+
+static int parking_lot_cfg_hash_fn(const void *obj, const int flags)
+{
+       const struct parking_lot_cfg *entry;
+       const char *key;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       case OBJ_KEY:
+               key = obj;
+               return ast_str_hash(key);
+       case OBJ_PARTIAL_KEY:
+               ast_assert(0);
+               return 0;
+       default:
+               entry = obj;
+               return ast_str_hash(entry->name);
+       }
+}
+
+static int parking_lot_cfg_cmp_fn(void *obj, void *arg, const int flags)
+{
+       struct parking_lot_cfg *entry1 = obj;
+
+       char *key;
+       size_t key_size;
+       struct parking_lot_cfg *entry2;
+
+       switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+       case OBJ_KEY:
+               key = arg;
+               return (!strcmp(entry1->name, key)) ? CMP_MATCH : 0;
+       case OBJ_PARTIAL_KEY:
+               key = arg;
+               key_size = strlen(key);
+               return (!strncmp(entry1->name, key, key_size)) ? CMP_MATCH : 0;
+       case OBJ_POINTER:
+               entry2 = arg;
+               return (!strcmp(entry1->name, entry2->name)) ? CMP_MATCH : 0;
+       default:
+               return CMP_STOP;
+       }
+}
+
+/*! \brief destructor for parking_config */
+static void parking_config_destructor(void *obj)
+{
+       struct parking_config *cfg = obj;
+       ao2_cleanup(cfg->parking_lots);
+       ao2_cleanup(cfg->global);
+}
+
+/*! \brief destructor for parking_global_config */
+static void parking_global_config_destructor(void *obj)
+{
+       /* For now, do nothing. */
+}
+
+/*! \brief allocator callback for parking_config. Notice it returns void * since it is only used by the backend config code */
+static void *parking_config_alloc(void)
+{
+       RAII_VAR(struct parking_config *, cfg, NULL, ao2_cleanup);
+
+       if (!(cfg = ao2_alloc(sizeof(*cfg), parking_config_destructor))) {
+               return NULL;
+       }
+
+       if (!(cfg->parking_lots = ao2_container_alloc(37, parking_lot_cfg_hash_fn, parking_lot_cfg_cmp_fn))) {
+               return NULL;
+       }
+
+       if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), parking_global_config_destructor))) {
+               return NULL;
+       }
+
+       /* Bump the ref count since RAII_VAR is going to eat one */
+       ao2_ref(cfg, +1);
+       return cfg;
+}
+
+void parking_lot_remove_if_unused(struct parking_lot *lot)
+{
+
+       if (lot->mode != PARKINGLOT_DISABLED) {
+               return;
+       }
+
+
+       if (!ao2_container_count(lot->parked_users)) {
+               ao2_unlink(parking_lot_container, lot);
+       }
+}
+
+static void parking_lot_disable(struct parking_lot *lot)
+{
+       lot->mode = PARKINGLOT_DISABLED;
+       parking_lot_remove_if_unused(lot);
+}
+
+/*! \brief Destroy a parking lot cfg object */
+static void parking_lot_cfg_destructor(void *obj)
+{
+       struct parking_lot_cfg *lot_cfg = obj;
+
+       ast_string_field_free_memory(lot_cfg);
+}
+
+/* The arg just needs to have the parking space with it */
+static int parked_user_cmp_fn(void *obj, void *arg, int flags)
+{
+       int *search_space = arg;
+       struct parked_user *user = obj;
+       int object_space = user->parking_space;
+
+       if (*search_space == object_space) {
+               return CMP_MATCH;
+       }
+       return 0;
+}
+
+static int parked_user_sort_fn(const void *obj_left, const void *obj_right, int flags)
+{
+       const struct parked_user *left = obj_left;
+       const struct parked_user *right = obj_right;
+
+       return left->parking_space - right->parking_space;
+}
+
+/*!
+ * \brief create a parking lot structure
+ * \param cat name given to the parking lot
+ * \retval NULL failure
+ * \retval non-NULL successfully allocated parking lot
+ */
+static void *parking_lot_cfg_alloc(const char *cat)
+{
+       struct parking_lot_cfg *lot_cfg;
+
+       lot_cfg = ao2_alloc(sizeof(*lot_cfg), parking_lot_cfg_destructor);
+       if (!lot_cfg) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(lot_cfg, 32)) {
+               ao2_cleanup(lot_cfg);
+               return NULL;
+       }
+
+       ast_string_field_set(lot_cfg, name, cat);
+
+       return lot_cfg;
+}
+
+/*!
+ * XXX This is actually incredibly generic and might be better placed in something like astobj2 if there isn't already an equivalent
+ * \brief find an item in a container by its name
+ *
+ * \param container ao2container where we want the item from
+ * \param key name of the item wanted to be found
+ *
+ * \retval pointer to the parking lot if available. NULL if not found.
+ */
+static void *named_item_find(struct ao2_container *container, const char *name)
+{
+       return ao2_find(container, name, OBJ_KEY);
+}
+
+/*!
+ * \brief Custom field handler for parking positions
+ */
+static int option_handler_parkpos(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct parking_lot_cfg *lot_cfg = obj;
+       int low;
+       int high;
+
+       if (sscanf(var->value, "%30d-%30d", &low, &high) != 2) {
+               ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers\n");
+       } else if (high < low || low <= 0 || high <= 0) {
+               ast_log(LOG_WARNING, "Format for parking positions is a-b, where a <= b\n");
+       } else {
+               lot_cfg->parking_start = low;
+               lot_cfg->parking_stop = high;
+               return 0;
+       }
+       return -1;
+}
+
+/*!
+ * \brief Custom field handler for the findslot option
+ */
+static int option_handler_findslot(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct parking_lot_cfg *lot_cfg = obj;
+
+       if (!strcmp(var->value, "first")) {
+               lot_cfg->parkfindnext = 0;
+       } else if (!strcmp(var->value, "next")) {
+               lot_cfg->parkfindnext = 1;
+       } else {
+               ast_log(LOG_WARNING, "value '%s' is not valid for findslot option.\n", var->value);
+               return -1;
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Maps string values for option_handler_parkedfeature to their ENUM values
+ */
+static int parking_feature_flag_cfg(int *param, const char *var)
+{
+       if (ast_false(var)) {
+               *param = 0;
+       } else if (!strcasecmp(var, "both")) {
+               *param = AST_FEATURE_FLAG_BYBOTH;
+       } else if (!strcasecmp(var, "caller")) {
+               *param = AST_FEATURE_FLAG_BYCALLER;
+       } else if (!strcasecmp(var, "callee")) {
+               *param = AST_FEATURE_FLAG_BYCALLEE;
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Custom field handler for feature mapping on parked call pickup options
+ */
+static int option_handler_parkedfeature(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct parking_lot_cfg *cfg = obj;
+       enum parked_call_feature_options option = aco_option_get_flags(opt);
+       int *parameter = NULL;
+
+       switch (option) {
+       case OPT_PARKEDPLAY:
+               parameter = &cfg->parkedplay;
+               break;
+       case OPT_PARKEDTRANSFERS:
+               parameter = &cfg->parkedcalltransfers;
+               break;
+       case OPT_PARKEDREPARKING:
+               parameter = &cfg->parkedcallreparking;
+               break;
+       case OPT_PARKEDHANGUP:
+               parameter = &cfg->parkedcallhangup;
+               break;
+       case OPT_PARKEDRECORDING:
+               parameter = &cfg->parkedcallrecording;
+               break;
+       }
+
+       if (!parameter) {
+               ast_log(LOG_ERROR, "Unable to handle option '%s'\n", var->name);
+               return -1;
+       }
+
+       if (parking_feature_flag_cfg(parameter, var->value)) {
+               ast_log(LOG_ERROR, "'%s' is not a valid value for parking lot option '%s'\n", var->value, var->name);
+               return -1;
+       }
+
+       return 0;
+}
+
+struct ao2_container *get_parking_lot_container(void)
+{
+       return parking_lot_container;
+}
+
+struct parking_lot *parking_lot_find_by_name(const char *lot_name)
+{
+       struct parking_lot *lot = named_item_find(parking_lot_container, lot_name);
+       return lot;
+}
+
+const char *find_channel_parking_lot_name(struct ast_channel *chan)
+{
+       const char *name;
+
+       /* The channel variable overrides everything */
+       name = pbx_builtin_getvar_helper(chan, "PARKINGLOT");
+       if (ast_strlen_zero(name) && !ast_strlen_zero(ast_channel_parkinglot(chan))) {
+               /* Use the channel's parking lot. */
+               name = ast_channel_parkinglot(chan);
+       }
+
+       /* If the name couldn't be pulled from that either, use the default parking lot name. */
+       if (ast_strlen_zero(name)) {
+               name = DEFAULT_PARKING_LOT;
+       }
+
+       return name;
+}
+
+static void parking_lot_destructor(void *obj)
+{
+       struct parking_lot *lot = obj;
+
+       if (lot->parking_bridge) {
+               ast_bridge_destroy(lot->parking_bridge);
+       }
+       ao2_cleanup(lot->parked_users);
+       ao2_cleanup(lot->cfg);
+       ast_string_field_free_memory(lot);
+}
+
+static struct parking_lot *alloc_new_parking_lot(struct parking_lot_cfg *lot_cfg)
+{
+       struct parking_lot *lot;
+       if (!(lot = ao2_alloc(sizeof(*lot), parking_lot_destructor))) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(lot, 32)) {
+               return NULL;
+       }
+
+       /* Create parked user ordered list */
+       lot->parked_users = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
+               AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
+               parked_user_sort_fn,
+               parked_user_cmp_fn);
+
+       if (!lot->parked_users) {
+               ao2_cleanup(lot);
+               return NULL;
+       }
+
+       ast_string_field_set(lot, name, lot_cfg->name);
+       return lot;
+}
+
+struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *lot_cfg)
+{
+       struct parking_lot *lot;
+       struct parking_lot_cfg *replaced_cfg = NULL;
+       int found = 0;
+
+       /* Start by trying to find it. If that works we can skip the rest. */
+       lot = named_item_find(parking_lot_container, lot_cfg->name);
+       if (!lot) {
+               lot = alloc_new_parking_lot(lot_cfg);
+
+               /* If we still don't have a lot, we failed to alloc one. */
+               if (!lot) {
+                       return NULL;
+               }
+       } else {
+               found = 1;
+       }
+
+       /* Set the configuration reference. Unref the one currently in the lot if it's there. */
+       if (lot->cfg) {
+               replaced_cfg = lot->cfg;
+       }
+
+       ao2_ref(lot_cfg, +1);
+       lot->cfg = lot_cfg;
+
+       ao2_cleanup(replaced_cfg);
+
+       /* Set the operating mode to normal since the parking lot has a configuration. */
+       lot->disable_mark = 0;
+       lot->mode = PARKINGLOT_NORMAL;
+
+       if (!found) {
+               /* Link after configuration is set since a lot without configuration will cause all kinds of trouble. */
+               ao2_link(parking_lot_container, lot);
+       };
+
+       return lot;
+}
+
+static void generate_or_link_lots_to_configs(void)
+{
+       RAII_VAR(struct parking_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
+       struct parking_lot_cfg *lot_cfg;
+       struct ao2_iterator iter;
+
+       for (iter = ao2_iterator_init(cfg->parking_lots, 0); (lot_cfg = ao2_iterator_next(&iter)); ao2_ref(lot_cfg, -1)) {
+               RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
+               lot = parking_lot_build_or_update(lot_cfg);
+       }
+
+       ao2_iterator_destroy(&iter);
+}
+
+/* Preapply */
+
+static int verify_default_parking_lot(void)
+{
+       struct parking_config *cfg = aco_pending_config(&cfg_info);
+       RAII_VAR(struct parking_lot_cfg *, lot_cfg, NULL, ao2_cleanup);
+
+       if (!cfg) {
+               return 0;
+       }
+
+       lot_cfg = ao2_find(cfg->parking_lots, DEFAULT_PARKING_LOT, OBJ_KEY);
+       if (!lot_cfg) {
+               lot_cfg = parking_lot_cfg_alloc(DEFAULT_PARKING_LOT);
+               if (!lot_cfg) {
+                       return -1;
+               }
+               ast_log(AST_LOG_NOTICE, "Adding %s profile to res_parking\n", DEFAULT_PARKING_LOT);
+               aco_set_defaults(&parking_lot_type, DEFAULT_PARKING_LOT, lot_cfg);
+               ast_string_field_set(lot_cfg, parkext, DEFAULT_PARKING_EXTEN);
+               ao2_link(cfg->parking_lots, lot_cfg);
+       }
+
+       return 0;
+}
+
+static void mark_lots_as_disabled(void)
+{
+       struct ao2_iterator iter;
+       struct parking_lot *lot;
+
+       for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
+               /* We aren't concerned with dynamic lots */
+               if (lot->mode == PARKINGLOT_DYNAMIC) {
+                       continue;
+               }
+
+               lot->disable_mark = 1;
+       }
+
+       ao2_iterator_destroy(&iter);
+}
+
+static int config_parking_preapply(void)
+{
+       mark_lots_as_disabled();
+       return verify_default_parking_lot();
+}
+
+static void disable_marked_lots(void)
+{
+       struct ao2_iterator iter;
+       struct parking_lot *lot;
+
+       for (iter = ao2_iterator_init(parking_lot_container, 0); (lot = ao2_iterator_next(&iter)); ao2_ref(lot, -1)) {
+               if (lot->disable_mark) {
+                       parking_lot_disable(lot);
+               }
+       }
+
+       ao2_iterator_destroy(&iter);
+}
+
+static void link_configured_disable_marked_lots(void)
+{
+       generate_or_link_lots_to_configs();
+       disable_marked_lots();
+}
+
+static int load_module(void)
+{
+       if (aco_info_init(&cfg_info)) {
+               goto error;
+       }
+
+       parking_lot_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_RWLOCK,
+               AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
+               parking_lot_sort_fn,
+               NULL);
+
+       if (!parking_lot_container) {
+               goto error;
+       }
+
+       /* Global options */
+
+       /* Register the per parking lot options. */
+       aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext));
+       aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con));
+       aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime));
+       aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin));
+       aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext));
+       aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));
+       aco_option_register(&cfg_info, "parkedmusicclass", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, mohclass));
+       aco_option_register(&cfg_info, "parkext_exclusive", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkext_exclusive));
+       aco_option_register(&cfg_info, "parkinghints", ACO_EXACT, parking_lot_types, "no", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, parkaddhints));
+       aco_option_register(&cfg_info, "courtesytone", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, courtesytone));
+
+       /* More complicated parking lot options that require special handling */
+       aco_option_register_custom(&cfg_info, "parkpos", ACO_EXACT, parking_lot_types, "701-750", option_handler_parkpos, 0);
+       aco_option_register_custom(&cfg_info, "findslot", ACO_EXACT, parking_lot_types, "first", option_handler_findslot, 0);
+       aco_option_register_custom(&cfg_info, "parkedplay", ACO_EXACT, parking_lot_types, "caller", option_handler_parkedfeature, OPT_PARKEDPLAY);
+       aco_option_register_custom(&cfg_info, "parkedcalltransfers", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDTRANSFERS);
+       aco_option_register_custom(&cfg_info, "parkedcallreparking", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDREPARKING);
+       aco_option_register_custom(&cfg_info, "parkedcallhangup", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDHANGUP);
+       aco_option_register_custom(&cfg_info, "parkedcallrecording", ACO_EXACT, parking_lot_types, "no", option_handler_parkedfeature, OPT_PARKEDRECORDING);
+
+       if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
+               goto error;
+       }
+
+       if (ast_register_application_xml(PARK_APPLICATION, park_app_exec)) {
+               goto error;
+       }
+
+       if (ast_register_application_xml(PARKED_CALL_APPLICATION, parked_call_app_exec)) {
+               goto error;
+       }
+
+       if (ast_register_application_xml(PARK_AND_ANNOUNCE_APPLICATION, park_and_announce_app_exec)) {
+               goto error;
+       }
+
+       if (load_parking_ui()) {
+               goto error;
+       }
+
+       if (load_parking_manager()) {
+               goto error;
+       }
+
+       if (load_parking_bridge_features()) {
+               goto error;
+       }
+
+       /* TODO Dialplan generation for parking lots that set parkext */
+       /* TODO Generate hints for parking lots that set parkext and have hints enabled */
+
+       return AST_MODULE_LOAD_SUCCESS;
+
+error:
+       aco_info_destroy(&cfg_info);
+       return AST_MODULE_LOAD_DECLINE;
+}
+
+static int reload_module(void)
+{
+       if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       return 0;
+}
+
+static int unload_module(void)
+{
+       /* XXX Parking is currently unloadable due to the fact that it loads features which could cause
+        *     significant problems if they disappeared while a channel still had access to them.
+        */
+       return -1;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call Parking Resource",
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload_module,
+);
index f843310b62eb38b469b6f9ee0a276d8e3644b94d..10d36be424c49ba4a47fb902aee9b87135d69b71 100644 (file)
@@ -43,18 +43,32 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/json.h"
 #include "stasis_json/resource_events.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
 
-struct ast_json *stasis_json_event_channel_snapshot_create(
-       struct ast_channel_snapshot *channel_snapshot
+struct ast_json *stasis_json_event_channel_userevent_create(
+       struct ast_channel_snapshot *channel_snapshot,
+       struct ast_json *blob
        )
 {
        RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
        RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+       struct ast_json *validator;
        int ret;
 
        ast_assert(channel_snapshot != NULL);
+       ast_assert(blob != NULL);
+       ast_assert(ast_json_object_get(blob, "channel") == NULL);
+       ast_assert(ast_json_object_get(blob, "type") == NULL);
 
-       event = ast_json_object_create();
+       validator = ast_json_object_get(blob, "eventname");
+       if (validator) {
+               /* do validation? XXX */
+       } else {
+               /* fail message generation if the required parameter doesn't exist */
+               return NULL;
+       }
+
+       event = ast_json_deep_copy(blob);
        if (!event) {
                return NULL;
        }
@@ -65,7 +79,36 @@ struct ast_json *stasis_json_event_channel_snapshot_create(
                return NULL;
        }
 
-       message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event));
+       message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event));
+       if (!message) {
+               return NULL;
+       }
+
+       return ast_json_ref(message);
+}
+
+struct ast_json *stasis_json_event_bridge_created_create(
+       struct ast_bridge_snapshot *bridge_snapshot
+       )
+{
+       RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+       RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+       int ret;
+
+       ast_assert(bridge_snapshot != NULL);
+
+       event = ast_json_object_create();
+       if (!event) {
+               return NULL;
+       }
+
+       ret = ast_json_object_set(event,
+               "bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
+       if (ret) {
+               return NULL;
+       }
+
+       message = ast_json_pack("{s: o}", "bridge_created", ast_json_ref(event));
        if (!message) {
                return NULL;
        }
@@ -123,6 +166,35 @@ struct ast_json *stasis_json_event_channel_destroyed_create(
        return ast_json_ref(message);
 }
 
+struct ast_json *stasis_json_event_channel_snapshot_create(
+       struct ast_channel_snapshot *channel_snapshot
+       )
+{
+       RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+       RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+       int ret;
+
+       ast_assert(channel_snapshot != NULL);
+
+       event = ast_json_object_create();
+       if (!event) {
+               return NULL;
+       }
+
+       ret = ast_json_object_set(event,
+               "channel", ast_channel_snapshot_to_json(channel_snapshot));
+       if (ret) {
+               return NULL;
+       }
+
+       message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event));
+       if (!message) {
+               return NULL;
+       }
+
+       return ast_json_ref(message);
+}
+
 struct ast_json *stasis_json_event_channel_caller_id_create(
        struct ast_channel_snapshot *channel_snapshot,
        struct ast_json *blob
@@ -217,6 +289,35 @@ struct ast_json *stasis_json_event_channel_hangup_request_create(
        return ast_json_ref(message);
 }
 
+struct ast_json *stasis_json_event_bridge_destroyed_create(
+       struct ast_bridge_snapshot *bridge_snapshot
+       )
+{
+       RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+       RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+       int ret;
+
+       ast_assert(bridge_snapshot != NULL);
+
+       event = ast_json_object_create();
+       if (!event) {
+               return NULL;
+       }
+
+       ret = ast_json_object_set(event,
+               "bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
+       if (ret) {
+               return NULL;
+       }
+
+       message = ast_json_pack("{s: o}", "bridge_destroyed", ast_json_ref(event));
+       if (!message) {
+               return NULL;
+       }
+
+       return ast_json_ref(message);
+}
+
 struct ast_json *stasis_json_event_application_replaced_create(
        struct ast_json *blob
        )
@@ -299,41 +400,36 @@ struct ast_json *stasis_json_event_channel_varset_create(
        return ast_json_ref(message);
 }
 
-struct ast_json *stasis_json_event_channel_userevent_create(
-       struct ast_channel_snapshot *channel_snapshot,
-       struct ast_json *blob
+struct ast_json *stasis_json_event_channel_left_bridge_create(
+       struct ast_bridge_snapshot *bridge_snapshot,
+       struct ast_channel_snapshot *channel_snapshot
        )
 {
        RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
        RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
-       struct ast_json *validator;
        int ret;
 
        ast_assert(channel_snapshot != NULL);
-       ast_assert(blob != NULL);
-       ast_assert(ast_json_object_get(blob, "channel") == NULL);
-       ast_assert(ast_json_object_get(blob, "type") == NULL);
+       ast_assert(bridge_snapshot != NULL);
 
-       validator = ast_json_object_get(blob, "eventname");
-       if (validator) {
-               /* do validation? XXX */
-       } else {
-               /* fail message generation if the required parameter doesn't exist */
+       event = ast_json_object_create();
+       if (!event) {
                return NULL;
        }
 
-       event = ast_json_deep_copy(blob);
-       if (!event) {
+       ret = ast_json_object_set(event,
+               "channel", ast_channel_snapshot_to_json(channel_snapshot));
+       if (ret) {
                return NULL;
        }
 
        ret = ast_json_object_set(event,
-               "channel", ast_channel_snapshot_to_json(channel_snapshot));
+               "bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
        if (ret) {
                return NULL;
        }
 
-       message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event));
+       message = ast_json_pack("{s: o}", "channel_left_bridge", ast_json_ref(event));
        if (!message) {
                return NULL;
        }
@@ -491,6 +587,43 @@ struct ast_json *stasis_json_event_channel_state_change_create(
        return ast_json_ref(message);
 }
 
+struct ast_json *stasis_json_event_channel_entered_bridge_create(
+       struct ast_bridge_snapshot *bridge_snapshot,
+       struct ast_channel_snapshot *channel_snapshot
+       )
+{
+       RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
+       RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
+       int ret;
+
+       ast_assert(channel_snapshot != NULL);
+       ast_assert(bridge_snapshot != NULL);
+
+       event = ast_json_object_create();
+       if (!event) {
+               return NULL;
+       }
+
+       ret = ast_json_object_set(event,
+               "channel", ast_channel_snapshot_to_json(channel_snapshot));
+       if (ret) {
+               return NULL;
+       }
+
+       ret = ast_json_object_set(event,
+               "bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
+       if (ret) {
+               return NULL;
+       }
+
+       message = ast_json_pack("{s: o}", "channel_entered_bridge", ast_json_ref(event));
+       if (!message) {
+               return NULL;
+       }
+
+       return ast_json_ref(message);
+}
+
 struct ast_json *stasis_json_event_channel_dtmf_received_create(
        struct ast_channel_snapshot *channel_snapshot,
        struct ast_json *blob
index 5a260a5f3a24c717c6b0b2aec75c85c214dc1736..e3f59ca763f27111fe6cac1ceca0aa83d7cfe7a2 100644 (file)
@@ -1,16 +1,20 @@
 {
        global:
-               LINKER_SYMBOL_PREFIXstasis_json_event_channel_snapshot_create;
+               LINKER_SYMBOL_PREFIXstasis_json_event_channel_userevent_create;
+               LINKER_SYMBOL_PREFIXstasis_json_event_bridge_created_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_destroyed_create;
+               LINKER_SYMBOL_PREFIXstasis_json_event_channel_snapshot_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_caller_id_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_hangup_request_create;
+               LINKER_SYMBOL_PREFIXstasis_json_event_bridge_destroyed_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_application_replaced_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_varset_create;
-               LINKER_SYMBOL_PREFIXstasis_json_event_channel_userevent_create;
+               LINKER_SYMBOL_PREFIXstasis_json_event_channel_left_bridge_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_created_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_stasis_start_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_dialplan_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_state_change_create;
+               LINKER_SYMBOL_PREFIXstasis_json_event_channel_entered_bridge_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_channel_dtmf_received_create;
                LINKER_SYMBOL_PREFIXstasis_json_event_stasis_end_create;
        local:
index bd1c3263b369b465cdea7f3f3d7e16793b4af727..63abe0f85d8d61a22d982458b27f2cb3c181f475 100644 (file)
 #define _ASTERISK_RESOURCE_EVENTS_H
 
 struct ast_channel_snapshot;
+struct ast_bridge_snapshot;
 
 /*!
- * \brief Some part of channel state changed.
+ * \brief User-generated event with additional user-defined fields in the object.
  *
- * \param channel The channel to be used to generate this event
+ * \param channel The channel that signaled the user event.
+ * \param blob JSON blob containing the following parameters:
+ * - eventname: string - The name of the user event. (required)
  *
  * \retval NULL on error
  * \retval JSON (ast_json) describing the event
  */
-struct ast_json *stasis_json_event_channel_snapshot_create(
-       struct ast_channel_snapshot *channel_snapshot
+struct ast_json *stasis_json_event_channel_userevent_create(
+       struct ast_channel_snapshot *channel_snapshot,
+       struct ast_json *blob
+       );
+
+/*!
+ * \brief Notification that a bridge has been created.
+ *
+ * \param bridge The bridge to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_bridge_created_create(
+       struct ast_bridge_snapshot *bridge_snapshot
        );
 
 /*!
@@ -67,6 +83,18 @@ struct ast_json *stasis_json_event_channel_destroyed_create(
        struct ast_json *blob
        );
 
+/*!
+ * \brief Some part of channel state changed.
+ *
+ * \param channel The channel to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_channel_snapshot_create(
+       struct ast_channel_snapshot *channel_snapshot
+       );
+
 /*!
  * \brief Channel changed Caller ID.
  *
@@ -99,6 +127,18 @@ struct ast_json *stasis_json_event_channel_hangup_request_create(
        struct ast_json *blob
        );
 
+/*!
+ * \brief Notification that a bridge has been destroyed.
+ *
+ * \param bridge The bridge to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_bridge_destroyed_create(
+       struct ast_bridge_snapshot *bridge_snapshot
+       );
+
 /*!
  * \brief Notification that another WebSocket has taken over for an application.
  *
@@ -129,18 +169,17 @@ struct ast_json *stasis_json_event_channel_varset_create(
        );
 
 /*!
- * \brief User-generated event with additional user-defined fields in the object.
+ * \brief Notification that a channel has left a bridge.
  *
- * \param channel The channel that signaled the user event.
- * \param blob JSON blob containing the following parameters:
- * - eventname: string - The name of the user event. (required)
+ * \param channel The channel to be used to generate this event
+ * \param bridge The bridge to be used to generate this event
  *
  * \retval NULL on error
  * \retval JSON (ast_json) describing the event
  */
-struct ast_json *stasis_json_event_channel_userevent_create(
-       struct ast_channel_snapshot *channel_snapshot,
-       struct ast_json *blob
+struct ast_json *stasis_json_event_channel_left_bridge_create(
+       struct ast_bridge_snapshot *bridge_snapshot,
+       struct ast_channel_snapshot *channel_snapshot
        );
 
 /*!
@@ -198,6 +237,20 @@ struct ast_json *stasis_json_event_channel_state_change_create(
        struct ast_channel_snapshot *channel_snapshot
        );
 
+/*!
+ * \brief Notification that a channel has entered a bridge.
+ *
+ * \param channel The channel to be used to generate this event
+ * \param bridge The bridge to be used to generate this event
+ *
+ * \retval NULL on error
+ * \retval JSON (ast_json) describing the event
+ */
+struct ast_json *stasis_json_event_channel_entered_bridge_create(
+       struct ast_bridge_snapshot *bridge_snapshot,
+       struct ast_channel_snapshot *channel_snapshot
+       );
+
 /*!
  * \brief DTMF received on a channel.
  *
@@ -228,23 +281,26 @@ struct ast_json *stasis_json_event_stasis_end_create(
 /*
  * JSON models
  *
- * ChannelSnapshot
+ * ChannelUserevent
+ * - eventname: string (required)
+ * BridgeCreated
  * ChannelDestroyed
  * - cause: integer (required)
  * - cause_txt: string (required)
+ * ChannelSnapshot
  * ChannelCallerId
  * - caller_presentation_txt: string (required)
  * - caller_presentation: integer (required)
  * ChannelHangupRequest
  * - soft: boolean
  * - cause: integer
+ * BridgeDestroyed
  * ApplicationReplaced
  * - application: string (required)
  * ChannelVarset
  * - variable: string (required)
  * - value: string (required)
- * ChannelUserevent
- * - eventname: string (required)
+ * ChannelLeftBridge
  * ChannelCreated
  * StasisStart
  * - args: List[string] (required)
@@ -252,22 +308,27 @@ struct ast_json *stasis_json_event_stasis_end_create(
  * - application: string (required)
  * - application_data: string (required)
  * ChannelStateChange
+ * ChannelEnteredBridge
  * ChannelDtmfReceived
  * - digit: string (required)
  * Event
+ * - stasis_start: StasisStart
  * - channel_created: ChannelCreated
  * - channel_destroyed: ChannelDestroyed
+ * - channel_entered_bridge: ChannelEnteredBridge
+ * - channel_left_bridge: ChannelLeftBridge
  * - channel_dialplan: ChannelDialplan
  * - channel_varset: ChannelVarset
  * - application_replaced: ApplicationReplaced
  * - channel_state_change: ChannelStateChange
- * - stasis_start: StasisStart
+ * - bridge_created: BridgeCreated
  * - application: string (required)
  * - channel_hangup_request: ChannelHangupRequest
  * - channel_userevent: ChannelUserevent
  * - channel_snapshot: ChannelSnapshot
  * - channel_dtmf_received: ChannelDtmfReceived
  * - channel_caller_id: ChannelCallerId
+ * - bridge_destroyed: BridgeDestroyed
  * - stasis_end: StasisEnd
  * StasisEnd
  */
index 0398302702371d400237e835f230fdf65be3fe3c..a55389c070cf7cb106ffe1de2ee3f49e4db54fbc 100644 (file)
@@ -49,6 +49,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "stasis_json/resource_{{name}}.h"
 {{#has_events}}
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
 
 {{#events}}
 {{> event_function_decl}}
index f55e830bda0c1a416ae037f3b6ce66b08a0b9eb8..8cfd2c1f7e185b0a635d39ecaa99bd08dd06aff8 100644 (file)
@@ -38,6 +38,7 @@
 
 {{#has_events}}
 struct ast_channel_snapshot;
+struct ast_bridge_snapshot;
 
 {{#events}}
 /*!
index 5b6e0549dc58e7a6981aed00dfda910df8f611ec..4a36da3b8dcec03ec67af0199865c42222ea781f 100644 (file)
                                        "required": true
                                },
                                "application_replaced": { "type": "ApplicationReplaced" },
+                               "bridge_created": { "type": "BridgeCreated" },
+                               "bridge_destroyed": { "type": "BridgeDestroyed" },
                                "channel_created": { "type": "ChannelCreated" },
                                "channel_destroyed": { "type": "ChannelDestroyed" },
                                "channel_snapshot": { "type": "ChannelSnapshot" },
+                               "channel_entered_bridge": { "type": "ChannelEnteredBridge" },
+                               "channel_left_bridge": { "type": "ChannelLeftBridge" },
                                "channel_state_change": { "type": "ChannelStateChange" },
                                "channel_dtmf_received": { "type": "ChannelDtmfReceived" },
                                "channel_dialplan": { "type": "ChannelDialplan" },
                                }
                        }
                },
+               "BridgeCreated": {
+                       "id": "BridgeCreated",
+                       "description": "Notification that a bridge has been created.",
+                       "properties": {
+                               "bridge": {
+                                       "required": true,
+                                       "type": "Bridge"
+                               }
+                       }
+               },
+               "BridgeDestroyed": {
+                       "id": "BridgeDestroyed",
+                       "description": "Notification that a bridge has been destroyed.",
+                       "properties": {
+                               "bridge": {
+                                       "required": true,
+                                       "type": "Bridge"
+                               }
+                       }
+               },
                "ChannelCreated": {
                        "id": "ChannelCreated",
                        "description": "Notification that a channel has been created.",
                                }
                        }
                },
+               "ChannelEnteredBridge": {
+                       "id": "ChannelEnteredBridge",
+                       "description": "Notification that a channel has entered a bridge.",
+                       "properties": {
+                               "bridge": {
+                                       "required": true,
+                                       "type": "Bridge"
+                               },
+                               "channel": {
+                                       "type": "Channel"
+                               }
+                       }
+               },
+               "ChannelLeftBridge": {
+                       "id": "ChannelLeftBridge",
+                       "description": "Notification that a channel has left a bridge.",
+                       "properties": {
+                               "bridge": {
+                                       "required": true,
+                                       "type": "Bridge"
+                               },
+                               "channel": {
+                                       "required": true,
+                                       "type": "Channel"
+                               }
+                       }
+               },
                "ChannelStateChange": {
                        "id": "ChannelStateChange",
                        "description": "Notification of a channel's state change.",