summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
committerRichard Mudgett <rmudgett@digium.com>2013-05-21 18:00:22 +0000
commit3d63833bd6c869b7efa383e8dea14be1a6eff998 (patch)
tree34957dd051b8f67c7cc58a510e24ee3873a61ad4
parente1e1cc2deefb92f8b43825f1f34e619354737842 (diff)
Merge in the bridge_construction branch to make the system use the Bridging API.
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
-rw-r--r--CHANGES83
-rw-r--r--UPGRADE.txt15
-rw-r--r--addons/chan_ooh323.c1
-rw-r--r--apps/app_bridgewait.c245
-rw-r--r--apps/app_channelredirect.c4
-rw-r--r--apps/app_chanspy.c9
-rw-r--r--apps/app_confbridge.c501
-rw-r--r--apps/app_dial.c78
-rw-r--r--apps/app_dumpchan.c12
-rw-r--r--apps/app_followme.c2
-rw-r--r--apps/app_mixmonitor.c10
-rw-r--r--apps/app_parkandannounce.c249
-rw-r--r--apps/app_queue.c71
-rw-r--r--apps/confbridge/conf_chan_announce.c207
-rw-r--r--apps/confbridge/conf_chan_record.c94
-rw-r--r--apps/confbridge/conf_config_parser.c4
-rw-r--r--apps/confbridge/confbridge_manager.c339
-rw-r--r--apps/confbridge/include/confbridge.h63
-rw-r--r--bridges/bridge_builtin_features.c342
-rw-r--r--bridges/bridge_builtin_interval_features.c215
-rw-r--r--bridges/bridge_holding.c311
-rw-r--r--bridges/bridge_multiplexed.c513
-rw-r--r--bridges/bridge_native_rtp.c414
-rw-r--r--bridges/bridge_simple.c24
-rw-r--r--bridges/bridge_softmix.c593
-rw-r--r--channels/chan_agent.c4
-rw-r--r--channels/chan_bridge.c235
-rw-r--r--channels/chan_dahdi.c1
-rw-r--r--channels/chan_gulp.c1
-rw-r--r--channels/chan_h323.c1
-rw-r--r--channels/chan_iax2.c172
-rw-r--r--channels/chan_jingle.c1
-rw-r--r--channels/chan_local.c1452
-rw-r--r--channels/chan_mgcp.c102
-rw-r--r--channels/chan_misdn.c1
-rw-r--r--channels/chan_motif.c1
-rw-r--r--channels/chan_sip.c539
-rw-r--r--channels/chan_skinny.c1
-rw-r--r--channels/chan_unistim.c11
-rw-r--r--channels/chan_vpb.cc8
-rw-r--r--configs/features.conf.sample15
-rw-r--r--configs/res_parking.conf.sample48
-rw-r--r--funcs/func_channel.c7
-rw-r--r--funcs/func_frame_trace.c4
-rw-r--r--funcs/func_jitterbuffer.c280
-rw-r--r--include/asterisk/_private.h18
-rw-r--r--include/asterisk/abstract_jb.h28
-rw-r--r--include/asterisk/bridging.h1242
-rw-r--r--include/asterisk/bridging_basic.h107
-rw-r--r--include/asterisk/bridging_features.h476
-rw-r--r--include/asterisk/bridging_roles.h135
-rw-r--r--include/asterisk/bridging_technology.h116
-rw-r--r--include/asterisk/ccss.h10
-rw-r--r--include/asterisk/channel.h76
-rw-r--r--include/asterisk/config_options.h10
-rw-r--r--include/asterisk/core_local.h98
-rw-r--r--include/asterisk/core_unreal.h191
-rw-r--r--include/asterisk/frame.h2
-rw-r--r--include/asterisk/framehook.h2
-rw-r--r--include/asterisk/manager.h57
-rw-r--r--include/asterisk/parking.h184
-rw-r--r--include/asterisk/rtp_engine.h28
-rw-r--r--include/asterisk/stasis_bridging.h238
-rw-r--r--main/abstract_jb.c309
-rw-r--r--main/asterisk.c20
-rw-r--r--main/bridging.c5675
-rw-r--r--main/bridging_basic.c159
-rw-r--r--main/bridging_roles.c462
-rw-r--r--main/channel.c182
-rw-r--r--main/channel_internal_api.c13
-rw-r--r--main/cli.c45
-rw-r--r--main/config_options.c5
-rw-r--r--main/core_local.c775
-rw-r--r--main/core_unreal.c855
-rw-r--r--main/features.c1920
-rw-r--r--main/frame.c8
-rw-r--r--main/manager.c207
-rw-r--r--main/manager_bridging.c470
-rw-r--r--main/manager_channels.c93
-rw-r--r--main/parking.c191
-rw-r--r--main/pbx.c11
-rw-r--r--main/rtp_engine.c669
-rw-r--r--main/stasis_bridging.c358
-rw-r--r--main/strings.c1
-rw-r--r--res/Makefile4
-rw-r--r--res/parking/parking_applications.c801
-rw-r--r--res/parking/parking_bridge.c421
-rw-r--r--res/parking/parking_bridge_features.c479
-rw-r--r--res/parking/parking_controller.c292
-rw-r--r--res/parking/parking_manager.c610
-rw-r--r--res/parking/parking_ui.c199
-rw-r--r--res/parking/res_parking.h436
-rw-r--r--res/res_parking.c831
-rw-r--r--res/res_stasis_json_events.c173
-rw-r--r--res/res_stasis_json_events.exports.in8
-rw-r--r--res/stasis_json/resource_events.h91
-rw-r--r--rest-api-templates/res_stasis_json_resource.c.mustache1
-rw-r--r--rest-api-templates/stasis_json_resource.h.mustache1
-rw-r--r--rest-api/api-docs/events.json51
99 files changed, 19581 insertions, 7546 deletions
diff --git a/CHANGES b/CHANGES
index f5c705ed7..ec85f5ded 100644
--- 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
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 39f08049b..e2e709008 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -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:
diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c
index a393e8e0e..61d9245fe 100644
--- a/addons/chan_ooh323.c
+++ b/addons/chan_ooh323.c
@@ -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
index 000000000..ca12f0d30
--- /dev/null
+++ b/apps/app_bridgewait.c
@@ -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");
diff --git a/apps/app_channelredirect.c b/apps/app_channelredirect.c
index 8c98ed7b7..f636e0248 100644
--- a/apps/app_channelredirect.c
+++ b/apps/app_channelredirect.c
@@ -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);
diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c
index c5adb78ad..8e4590781 100644
--- a/apps/app_chanspy.c
+++ b/apps/app_chanspy.c
@@ -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;
}
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index d4cce4b46..5107bcd5c 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -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);
diff --git a/apps/app_dial.c b/apps/app_dial.c
index d3d37216a..35c9ad8bf 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -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) {
diff --git a/apps/app_dumpchan.c b/apps/app_dumpchan.c
index 4a80a3d13..722f15541 100644
--- a/apps/app_dumpchan.c
+++ b/apps/app_dumpchan.c
@@ -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;
}
diff --git a/apps/app_followme.c b/apps/app_followme.c
index 83f583bb3..43f196708 100644
--- a/apps/app_followme.c
+++ b/apps/app_followme.c
@@ -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:
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
index 6e7976ec9..e8d4903f0 100644
--- a/apps/app_mixmonitor.c
+++ b/apps/app_mixmonitor.c
@@ -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
index 6d6ccae26..000000000
--- a/apps/app_parkandannounce.c
+++ /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");
diff --git a/apps/app_queue.c b/apps/app_queue.c
index c63cd071e..8a96b64b2 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -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
*/
@@ -5100,6 +5105,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
*
* Here is the process of this function
@@ -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
index 000000000..46e074b20
--- /dev/null
+++ b/apps/confbridge/conf_chan_announce.c
@@ -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
index 000000000..18f971f35
--- /dev/null
+++ b/apps/confbridge/conf_chan_record.c
@@ -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;
+}
diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c
index 1bca2d5c2..6cec25522 100644
--- a/apps/confbridge/conf_config_parser.c
+++ b/apps/confbridge/conf_config_parser.c
@@ -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
index 000000000..56fedb98e
--- /dev/null
+++ b/apps/confbridge/confbridge_manager.c
@@ -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;
+}
diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h
index 245561376..f0620149a 100644
--- a/apps/confbridge/include/confbridge.h
+++ b/apps/confbridge/include/confbridge.h
@@ -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
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c
index 428d6deda..493b5c891 100644
--- a/bridges/bridge_builtin_features.c
+++ b/bridges/bridge_builtin_features.c
@@ -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
index 000000000..a0e767ed3
--- /dev/null
+++ b/bridges/bridge_builtin_interval_features.c
@@ -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
index 000000000..fe0a7303f
--- /dev/null
+++ b/bridges/bridge_holding.c
@@ -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
index 309ad47e3..000000000
--- a/bridges/bridge_multiplexed.c
+++ /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
index 000000000..1117e5aed
--- /dev/null
+++ b/bridges/bridge_native_rtp.c
@@ -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");
diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c
index 947983bae..3e53b31c0 100644
--- a/bridges/bridge_simple.c
+++ b/bridges/bridge_simple.c
@@ -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,
};
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 613601a1f..4583435a0 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -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)
diff --git a/channels/chan_agent.c b/channels/chan_agent.c
index 3fb6891cc..d72254ee7 100644
--- a/channels/chan_agent.c
+++ b/channels/chan_agent.c
@@ -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
index 8eac76a82..000000000
--- a/channels/chan_bridge.c
+++ /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,
-);
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 44fe3f4fb..1f9ee5a56 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -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,
diff --git a/channels/chan_gulp.c b/channels/chan_gulp.c
index 74859534b..4af137f28 100644
--- a/channels/chan_gulp.c
+++ b/channels/chan_gulp.c
@@ -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,
diff --git a/channels/chan_h323.c b/channels/chan_h323.c
index f6364a118..2476f5065 100644
--- a/channels/chan_h323.c
+++ b/channels/chan_h323.c
@@ -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)
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 112a99375..49ce66cb7 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -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;
diff --git a/channels/chan_jingle.c b/channels/chan_jingle.c
index 717afae67..42dccf666 100644
--- a/channels/chan_jingle.c
+++ b/channels/chan_jingle.c
@@ -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
index be4586fc8..000000000
--- a/channels/chan_local.c
+++ /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,
- );
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index e254823bb..1f0830762 100644
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -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;
diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c
index 2bc6f1e35..aaa7170b3 100644
--- a/channels/chan_misdn.c
+++ b/channels/chan_misdn.c
@@ -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)",
diff --git a/channels/chan_motif.c b/channels/chan_motif.c
index 0836972e1..56b06b145 100644
--- a/channels/chan_motif.c
+++ b/channels/chan_motif.c
@@ -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,
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 88965fc73..f7a528b68 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -176,7 +176,6 @@
/*** 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",
);
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index b3951d5e4..cd194d5a8 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -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);
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index dea796364..121e2f0b1 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -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);
diff --git a/channels/chan_vpb.cc b/channels/chan_vpb.cc
index 58fc73f69..d1747cb06 100644
--- a/channels/chan_vpb.cc
+++ b/channels/chan_vpb.cc
@@ -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) */
diff --git a/configs/features.conf.sample b/configs/features.conf.sample
index 701ccdf37..12fb3151c 100644
--- a/configs/features.conf.sample
+++ b/configs/features.conf.sample
@@ -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
index 000000000..b8308e6ab
--- /dev/null
+++ b/configs/res_parking.conf.sample
@@ -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.
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index db9434d83..937924400 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -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); \
diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c
index 6410d12b8..94a2c13d0 100644
--- a/funcs/func_frame_trace.c
+++ b/funcs/func_frame_trace.c
@@ -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);
diff --git a/funcs/func_jitterbuffer.c b/funcs/func_jitterbuffer.c
index 066d9d2f6..a00361043 100644
--- a/funcs/func_jitterbuffer.c
+++ b/funcs/func_jitterbuffer.c
@@ -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,
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index 7e1ef13ec..3fe35e58c 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -52,6 +52,24 @@ 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
*
diff --git a/include/asterisk/abstract_jb.h b/include/asterisk/abstract_jb.h
index 3e6bedd26..6a4d0610d 100644
--- a/include/asterisk/abstract_jb.h
+++ b/include/asterisk/abstract_jb.h
@@ -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
diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h
index 2890b1dfa..77fb54e8d 100644
--- a/include/asterisk/bridging.h
+++ b/include/asterisk/bridging.h
@@ -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
@@ -16,10 +17,16 @@
* 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 {
@@ -207,13 +276,151 @@ struct ast_bridge_video_mode {
};
/*!
+ * \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,11 +591,21 @@ 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
*
* \param bridge Bridge to join
@@ -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,18 +960,266 @@ 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
index 000000000..cc42354dc
--- /dev/null
+++ b/include/asterisk/bridging_basic.h
@@ -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 */
diff --git a/include/asterisk/bridging_features.h b/include/asterisk/bridging_features.h
index 1323a6da9..bb792a815 100644
--- a/include/asterisk/bridging_features.h
+++ b/include/asterisk/bridging_features.h
@@ -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
index 000000000..3acb67fae
--- /dev/null
+++ b/include/asterisk/bridging_roles.h
@@ -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 */
diff --git a/include/asterisk/bridging_technology.h b/include/asterisk/bridging_technology.h
index d09c4bec4..2bb2170f0 100644
--- a/include/asterisk/bridging_technology.h
+++ b/include/asterisk/bridging_technology.h
@@ -28,14 +28,17 @@
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;
@@ -126,27 +166,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.
*
* \note All DSP functionality on the bridge has been pushed down to the lowest possible
@@ -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
diff --git a/include/asterisk/ccss.h b/include/asterisk/ccss.h
index cf2f21996..d8101cddf 100644
--- a/include/asterisk/ccss.h
+++ b/include/asterisk/ccss.h
@@ -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
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 939c8db51..91373cfbd 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -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 */
diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h
index 64d8d5089..739d83130 100644
--- a/include/asterisk/config_options.h
+++ b/include/asterisk/config_options.h
@@ -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
index 000000000..693c93b46
--- /dev/null
+++ b/include/asterisk/core_local.h
@@ -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
index 000000000..7cb68f162
--- /dev/null
+++ b/include/asterisk/core_unreal.h
@@ -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 */
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index abb0c2e28..bedc3a25d 100644
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -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
diff --git a/include/asterisk/framehook.h b/include/asterisk/framehook.h
index 52993b55c..10d525ca7 100644
--- a/include/asterisk/framehook.h
+++ b/include/asterisk/framehook.h
@@ -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);
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index e0160c6b5..4e9b8d14a 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -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
index 000000000..176eddb04
--- /dev/null
+++ b/include/asterisk/parking.h
@@ -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);
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index c8a144b73..e2567f508 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -1541,24 +1541,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
*
* \param instance The RTP instance that we want
@@ -1579,6 +1561,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
*
* \param c0 First channel part of the bridge
diff --git a/include/asterisk/stasis_bridging.h b/include/asterisk/stasis_bridging.h
new file mode 100644
index 000000000..1b547a7d5
--- /dev/null
+++ b/include/asterisk/stasis_bridging.h
@@ -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 */
diff --git a/main/abstract_jb.c b/main/abstract_jb.c
index 88a9b8e91..6e20b86cb 100644
--- a/main/abstract_jb.c
+++ b/main/abstract_jb.c
@@ -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;
+}
diff --git a/main/asterisk.c b/main/asterisk.c
index 933aae63d..d8062d3b1 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -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);
diff --git a/main/bridging.c b/main/bridging.c
index 875e8503c..f332dfab2 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -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,241 +401,693 @@ 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,
+ };
+
+ if (bridge->dissolved) {
+ return;
+ }
+ bridge->dissolved = 1;
+
+ ast_debug(1, "Bridge %s: dissolving bridge\n", bridge->uniqueid);
+/* BUGBUG need a cause code on the bridge for the later ejected channels. */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
- 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;
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+ }
+
+ /* Must defer dissolving bridge because it is already locked. */
+ ast_bridge_queue_action(bridge, &action);
+}
+
+/*!
+ * \internal
+ * \brief Check if a bridge should dissolve and do it.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Channel causing the check.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_dissolve_check(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_bridge *bridge = bridge_channel->bridge;
+
+ if (bridge->dissolved) {
+ return;
+ }
+
+ if (!bridge->num_channels
+ && ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) {
+ /* Last channel leaving the bridge turns off the lights. */
+ bridge_dissolve(bridge);
+ return;
+ }
+
+ switch (bridge_channel->state) {
+ case AST_BRIDGE_CHANNEL_STATE_END:
+ /* Do we need to dissolve the bridge because this channel hung up? */
+ if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP)
+ || (bridge_channel->features->usable
+ && ast_test_flag(&bridge_channel->features->feature_flags,
+ AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP))) {
+ bridge_dissolve(bridge);
+ return;
}
- ao2_unlock(bridge_channel);
+ break;
+ default:
+ break;
}
+/* BUGBUG need to implement AST_BRIDGE_CHANNEL_FLAG_LONELY support here */
}
-/*! \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)
+/*!
+ * \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 (!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))) {
+ struct ast_bridge *bridge = bridge_channel->bridge;
+
+ if (!bridge_channel->in_bridge) {
return;
}
+ 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);
+ }
+ }
- ast_debug(1, "Dissolving bridge %p\n", bridge);
- bridge_force_out_all(bridge);
+ /* 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);
+
+ ast_bridge_channel_clear_roles(bridge_channel);
+
+ bridge_dissolve_check(bridge_channel);
+
+ bridge->reconfigured = 1;
+ ast_bridge_publish_leave(bridge, bridge_channel->chan);
}
-/*! \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 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)
{
- 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;
+ struct ast_bridge_channel *swap;
+
+ ast_assert(!bridge_channel->in_bridge);
+
+ swap = find_bridge_channel(bridge, bridge_channel->swap);
+ bridge_channel->swap = NULL;
- /* If the features structure we grabbed is not usable immediately return the frame */
- if (!features->usable) {
- return frame;
+ 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));
+ }
+
+ /* 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 Internal function to handle DTMF from a channel */
+static struct ast_frame *bridge_handle_dtmf(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
+{
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ char dtmf[2];
+/* BUGBUG the feature hook matching needs to be done here. Any matching feature hook needs to be queued onto the bridge_channel. Also the feature hook digit timeout needs to be handled. */
+/* BUGBUG the AMI atxfer action just sends DTMF end events to initiate DTMF atxfer and dial the extension. Another reason the DTMF hook matching needs rework. */
/* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */
- 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;
- }
+ 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);
}
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)
+/*!
+ * \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)
{
- switch (subclass) {
- case AST_CONTROL_ANSWER:
- case -1:
- return 1;
- default:
- return 0;
+ 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);
+
+ /* Default hangup action. */
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
}
-void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking)
+static int bridge_channel_interval_ready(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_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ int ready;
+
+ 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 ready;
}
-void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd)
+void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking)
{
- /* 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);
- }
+ struct ast_frame action = {
+ .frametype = AST_FRAME_BRIDGE_ACTION,
+ .subclass.integer = started_talking
+ ? AST_BRIDGE_ACTION_TALKING_START : AST_BRIDGE_ACTION_TALKING_STOP,
+ };
+
+ ast_bridge_channel_queue_frame(bridge_channel, &action);
+}
+
+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);
+}
- /* If a bridge channel with actual channel is present read a frame and handle it */
- if (chan && bridge_channel) {
- struct ast_frame *frame;
+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,
+ };
+
+ bridge_channel_write_frame(bridge_channel, &frame);
+}
- if (bridge->features.mute
- || (bridge_channel->features && bridge_channel->features->mute)) {
- frame = ast_read_noaudio(chan);
+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_frame frame = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = control,
+ .datalen = datalen,
+ .data.ptr = (void *) data,
+ };
+
+ bridge_channel_write_frame(bridge_channel, &frame);
+}
+
+static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args)
+{
+ int res = 0;
+
+ 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 {
- 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);
- }
+ res = pbx_exec(chan, app, app_args);
+ }
+ }
+ return res;
+}
- if (frame && dtmf_passthrough) {
- bridge->technology->write(bridge, bridge_channel, frame);
- }
+void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class)
+{
+ if (moh_class) {
+ if (ast_strlen_zero(moh_class)) {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD,
+ NULL, 0);
} else {
- /* Simply write the frame out to the bridge technology if it still exists */
- bridge->technology->write(bridge, bridge_channel, frame);
+ 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 (frame) {
- ast_frfree(frame);
+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];
+};
+
+/*!
+ * \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);
+}
+
+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 */
+ }
+ if (moh_class) {
+ strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */
+ }
+
+ post_it(bridge_channel, AST_BRIDGE_ACTION_RUN_APP, app_data, len_data);
+}
+
+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);
+}
+
+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);
+}
+
+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)
+{
+ 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);
}
- return;
+ }
+ 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 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;
+ /*
+ * 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);
}
+}
- /* If all else fails just poke the bridge */
- if (bridge->technology->poke && bridge_channel) {
- bridge->technology->poke(bridge, bridge_channel);
- return;
+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];
+};
+
+/*!
+ * \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);
+}
+
+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)
+{
+ 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 */
}
+
+ post_it(bridge_channel, AST_BRIDGE_ACTION_PLAY_FILE, payload, len_payload);
}
-/*! \brief Generic thread loop, TODO: Rethink this/improve it */
-static int generic_thread_loop(struct ast_bridge *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)
{
- while (!bridge->stop && !bridge->refresh && bridge->array_num) {
- struct ast_channel *winner;
- int to = -1;
+ payload_helper_playfile(ast_bridge_channel_write_action_data,
+ bridge_channel, custom_play, playfile, moh_class);
+}
- /* 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;
- }
+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);
+}
- /* 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);
+struct bridge_park {
+ int parker_uuid_offset;
+ int app_data_offset;
+ /* buffer used for holding those strings */
+ char parkee_uuid[0];
+};
+
+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);
+}
- /* Process whatever they did */
- ast_bridge_handle_trip(bridge, NULL, winner, -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);
}
-/*! \brief Bridge thread function */
-static void *bridge_thread(void *data)
+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_bridge *bridge = data;
- int res = 0;
+ payload_helper_park(ast_bridge_channel_write_action_data,
+ bridge_channel, parkee_uuid, parker_uuid, app_data);
+}
- ao2_lock(bridge);
+/*!
+ * \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;
- if (bridge->callid) {
- ast_callid_threadassoc_add(bridge->callid);
+ if (bridge_channel->features->mute) {
+ frame = ast_read_noaudio(bridge_channel->chan);
+ } else {
+ frame = ast_read(bridge_channel->chan);
}
- ast_debug(1, "Started bridge thread for %p\n", bridge);
+ 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;
+ }
+ break;
+ case AST_FRAME_DTMF_BEGIN:
+ frame = bridge_handle_dtmf(bridge_channel, frame);
+ if (!frame) {
+ return;
+ }
+ /* Fall through */
+ case AST_FRAME_DTMF_END:
+ if (!bridge_channel->features->dtmf_passthrough) {
+ ast_frfree(frame);
+ 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;
+ }
- /* 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;
+/* 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_debug(1, "Launching bridge thread function %p for bridge %p\n",
- bridge->technology->thread ? bridge->technology->thread : generic_thread_loop,
- bridge);
+ /* 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);
+}
+/*!
+ * \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) {
/*
- * Execute the appropriate thread function. If the technology
- * does not provide one we use the generic one.
+ * 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.
*/
- res = bridge->technology->thread
- ? bridge->technology->thread(bridge)
- : generic_thread_loop(bridge);
+ return;
}
- ast_debug(1, "Ending bridge thread for %p\n", bridge);
-
- /* Indicate the bridge thread is no longer active */
- bridge->thread = AST_PTHREADT_NULL;
- ao2_unlock(bridge);
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (!bridge_channel->just_joined) {
+ continue;
+ }
- ao2_ref(bridge, -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);
+ }
- return NULL;
+ bridge_channel->just_joined = 0;
+ }
}
-/*! \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)
+/*! \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)
{
- struct ast_bridge_technology *current = NULL, *best = NULL;
+ struct ast_bridge_technology *current;
+ struct ast_bridge_technology *best = NULL;
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);
+ 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);
+ ast_debug(1, "Bridge technology %s does not have any capabilities we want.\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);
+ 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;
+ }
+ 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;
@@ -505,127 +1104,348 @@ static struct ast_bridge_technology *find_best_technology(uint32_t capabilities)
return best;
}
+struct tech_deferred_destroy {
+ struct ast_bridge_technology *tech;
+ void *tech_pvt;
+};
+
+/*!
+ * \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);
+}
+
+/*!
+ * \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;
+ }
+}
+
+/*!
+ * \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;
+
+ 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);
+ }
+}
+
static void destroy_bridge(void *obj)
{
struct ast_bridge *bridge = obj;
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
- ast_debug(1, "Actually destroying bridge %p, nobody wants it anymore\n", bridge);
+ ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n",
+ bridge->uniqueid, bridge->v_table->name);
+
+ msg = stasis_cache_clear_create(ast_bridge_snapshot_type(), bridge->uniqueid);
+ if (msg) {
+ stasis_publish(ast_bridge_topic(bridge), msg);
+ }
+
+ /* 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->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 (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;
}
- 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);
+ if (bridge->callid) {
+ bridge->callid = ast_callid_unref(bridge->callid);
+ }
- /* Drop the array of channels */
- ast_free(bridge->array);
+ cleanup_video_mode(bridge);
}
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags)
+struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge)
{
- 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 (bridge) {
+ ast_bridge_publish_state(bridge);
+ if (!ao2_link(bridges, bridge)) {
+ ast_bridge_destroy(bridge);
+ bridge = NULL;
}
}
+ return bridge;
+}
- /*
- * 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 ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table)
+{
+ struct ast_bridge *bridge;
- /* If no bridge technology was found we can't possibly do bridging so fail creation of the bridge */
- if (!bridge_technology) {
+ /* 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;
}
- /* 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);
+ bridge = ao2_alloc(size, destroy_bridge);
+ if (bridge) {
+ bridge->v_table = v_table;
+ }
+ return bridge;
+}
+
+struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags)
+{
+ if (!self) {
return NULL;
}
- bridge->technology = bridge_technology;
- bridge->thread = AST_PTHREADT_NULL;
+ ast_uuid_generate_str(self->uniqueid, sizeof(self->uniqueid));
+ ast_set_flag(&self->feature_flags, flags);
+ self->allowed_capabilities = capabilities;
- /* 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);
+ /* 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;
}
- bridge->array_size = BRIDGE_ARRAY_START;
-
- ast_set_flag(&bridge->feature_flags, flags);
/* 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;
- }
+ 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;
}
- return bridge;
+ if (!ast_bridge_topic(self)) {
+ ao2_ref(self, -1);
+ return NULL;
+ }
+
+ return self;
}
-int ast_bridge_check(uint32_t capabilities)
+/*!
+ * \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_base_destroy(struct ast_bridge *self)
{
- struct ast_bridge_technology *bridge_technology;
-
- if (!(bridge_technology = find_best_technology(capabilities))) {
- return 0;
- }
+}
- ast_module_unref(bridge_technology->mod);
+/*!
+ * \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);
+}
- return 1;
+/*!
+ * \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;
}
-int ast_bridge_destroy(struct ast_bridge *bridge)
+/*!
+ * \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)
{
- ao2_lock(bridge);
+ bridge_features_remove_on_pull(bridge_channel->features);
+}
- if (bridge->callid) {
- bridge->callid = ast_callid_unref(bridge->callid);
- }
+/*!
+ * \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;
+}
- if (bridge->thread != AST_PTHREADT_NULL) {
- bridge_stop(bridge);
- }
+/*!
+ * \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;
+}
- ast_debug(1, "Telling all channels in bridge %p to leave the party\n", bridge);
+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;
- /* Drop every bridged channel, the last one will cause the bridge thread (if it exists) to exit */
- bridge_force_out_all(bridge);
+ 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;
+}
- ao2_unlock(bridge);
+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);
ao2_ref(bridge, -1);
@@ -634,266 +1454,439 @@ int ast_bridge_destroy(struct ast_bridge *bridge)
static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
- 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));
+ 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))) {
- struct ast_format best_format;
-
ast_best_codec(bridge->technology->format_capabilities, &best_format);
/* 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]));
- }
+ ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n",
+ bridge->technology->name,
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+ ast_getformatname(&read_format));
+
/* Switch read format to the best one chosen */
if (ast_set_read_format(bridge_channel->chan, &best_format)) {
- ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ 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 %p put channel %s into read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ 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 %p is happy that channel %s already has read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[0]));
+ ast_debug(1, "Bridge %s is happy that channel %s already has read format %s\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&read_format));
}
- if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &formats[1])) {
- struct ast_format best_format;
-
+ if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &write_format)) {
ast_best_codec(bridge->technology->format_capabilities, &best_format);
/* Write format is a no go... */
- 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]));
- }
+ ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n",
+ bridge->technology->name,
+ ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities),
+ ast_getformatname(&write_format));
+
/* Switch write format to the best one chosen */
if (ast_set_write_format(bridge_channel->chan, &best_format)) {
- ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ 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 %p put channel %s into write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format));
+ 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 %p is happy that channel %s already has write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[1]));
+ ast_debug(1, "Bridge %s is happy that channel %s already has write format %s\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan),
+ ast_getformatname(&write_format));
}
return 0;
}
-/*! \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)
+/*!
+ * \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)
{
- uint32_t new_capabilities = 0;
+ uint32_t new_capabilities;
struct ast_bridge_technology *new_technology;
struct ast_bridge_technology *old_technology = bridge->technology;
- struct ast_bridge temp_bridge = {
+ struct ast_bridge_channel *bridge_channel;
+ struct ast_frame *deferred_action;
+ struct ast_bridge dummy_bridge = {
.technology = bridge->technology,
- .bridge_pvt = bridge->bridge_pvt,
+ .tech_pvt = bridge->tech_pvt,
};
- struct ast_bridge_channel *bridge_channel2;
- /* 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;
- }
+ if (bridge->dissolved) {
+ ast_debug(1, "Bridge %s is dissolved, not performing smart bridge operation.\n",
+ bridge->uniqueid);
+ return 0;
+ }
+
+ /* Determine new bridge technology capabilities needed. */
+ if (2 < bridge->num_channels) {
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;
+ 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;
}
- new_capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX;
}
- if (!new_capabilities) {
- ast_debug(1, "Bridge '%p' has no new capabilities, not performing smart bridge operation.\n", bridge);
- return 0;
- }
+ /* 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;
+ }
- /* Attempt to find a new bridge technology to satisfy the capabilities */
- if (!(new_technology = find_best_technology(new_capabilities))) {
+ 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;
+ }
+
+ ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid));
- 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);
+ 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 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.
+ * We need to defer the bridge technology destroy callback
+ * because we have the bridge locked.
*/
- 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);
+ deferred_action = ast_frdup(&action);
+ if (!deferred_action) {
+ ast_module_unref(new_technology->mod);
+ return -1;
}
+ } else {
+ deferred_action = 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);
+
+ /*
* 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
+ * 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->bridge_pvt = NULL;
+ bridge->tech_pvt = NULL;
bridge->technology = new_technology;
- /* 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);
- }
+ /* 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;
}
- /* 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) {
+ /* 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) {
- 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);
- }
+ old_technology->leave(&dummy_bridge, bridge_channel);
}
/* Second we make them compatible again with the bridge */
- bridge_make_compatible(bridge, bridge_channel2);
+ bridge_make_compatible(bridge, bridge_channel);
/* 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: %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);
}
-
- /* Fourth we tell them to wake up so they become aware that the above has happened */
- bridge_channel_poke(bridge_channel2);
}
- /* 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 */
+ /*
+ * 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);
- }
+ 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);
}
- /* 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);
-
return 0;
}
-/*! \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)
+/*!
+ * \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)
{
- 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->reconfigured) {
+ return;
}
-
- ao2_unlock(bridge_channel->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;
+ 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);
+}
- ao2_lock(bridge_channel->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;
+ }
- if (!bridge_channel->suspended) {
- ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd);
+ /* Get technology bridge threads off of the channel. */
+ if (bridge_channel->bridge->technology->suspend) {
+ bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel);
}
+}
- return bridge_channel->state;
+/*!
+ * \internal
+ * \brief Suspend a channel from a bridge.
+ *
+ * \param bridge_channel Channel to suspend.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_suspend(struct ast_bridge_channel *bridge_channel)
+{
+ ast_bridge_channel_lock_bridge(bridge_channel);
+ bridge_channel_suspend_nolock(bridge_channel);
+ ast_bridge_unlock(bridge_channel->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)
+/*!
+ * \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)
{
- 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));
+ bridge_channel->suspended = 0;
+ if (bridge_channel->in_bridge) {
+ ++bridge_channel->bridge->num_active;
}
- ao2_unlock(bridge_channel);
- ao2_lock(bridge_channel->bridge);
- return bridge_channel->state;
+ /* 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);
}
-/*! \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)
+/*!
+ * \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)
{
- ao2_lock(bridge_channel);
- bridge_channel->suspended = 1;
- bridge_array_remove(bridge, bridge_channel->chan);
- ao2_unlock(bridge_channel);
-
- if (bridge->technology->suspend) {
- bridge->technology->suspend(bridge, 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 unsuspends a channel from a bridge */
-static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+/*! \brief Internal function that activates interval hooks on a bridge channel */
+static void bridge_channel_interval(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);
+ 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 (bridge->technology->unsuspend) {
- bridge->technology->unsuspend(bridge, bridge_channel);
+ 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);
}
/*!
@@ -901,64 +1894,72 @@ static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridg
* \note Neither the bridge nor the bridge_channel locks should be held when entering
* this function.
*/
-static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_feature(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 = NULL;
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook = NULL;
char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = "";
- int look_for_dtmf = 1, dtmf_len = 0;
+ 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. */
- while (look_for_dtmf) {
- int res = ast_waitfordigit(bridge_channel->chan, 3000);
+ 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 bridge channel %p timed out\n", bridge_channel);
+ ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n",
+ bridge_channel, ast_channel_name(bridge_channel->chan));
break;
- } else if (res < 0) {
- ast_debug(1, "DTMF feature string collection failed on bridge channel %p for some reason\n", bridge_channel);
+ }
+ 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 bridge channel %p is now '%s'\n", bridge_channel, dtmf);
-
- /* Assume that we do not want to look for DTMF any longer */
- look_for_dtmf = 0;
+ 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 */
- 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);
- }
+ 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 we have reached the maximum length of a DTMF feature string bail out */
- if (dtmf_len == MAXIMUM_DTMF_FEATURE_STRING) {
+ 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) {
- hook->callback(bridge, bridge_channel, hook->hook_pvt);
+ 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
@@ -966,205 +1967,551 @@ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_
* 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);
+ bridge_handle_hangup(bridge_channel);
}
- } else {
- ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan);
+ } else if (features->dtmf_passthrough) {
+ bridge_channel_write_dtmf_stream(bridge_channel, dtmf);
}
}
-static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking)
{
- struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
+ struct ast_bridge_features *features = bridge_channel->features;
- if (features && features->talker_cb) {
- features->talker_cb(bridge, bridge_channel, features->talker_pvt_data);
+ 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)
+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)
{
- char dtmf_q[8] = "";
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ struct ao2_iterator iter;
- ast_copy_string(dtmf_q, bridge_channel->dtmf_stream_q, sizeof(dtmf_q));
- bridge_channel->dtmf_stream_q[0] = '\0';
+ /* 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);
+}
- 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);
+/*!
+ * \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 enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_channel *bridge_channel)
+static void bridge_channel_join(struct ast_bridge_channel *bridge_channel)
{
- 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));
+ 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));
- /* Record the thread that will be the owner of us */
- bridge_channel->thread = pthread_self();
+ 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);
- ast_debug(1, "Joining bridge channel %p to bridge %p\n", bridge_channel, bridge_channel->bridge);
+ /* Add the jitterbuffer if the channel requires it */
+ ast_jb_enable_for_channel(bridge_channel->chan);
- ao2_lock(bridge_channel->bridge);
+ /*
+ * 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();
}
- /* 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);
-
- if (bridge_channel->swap) {
- struct ast_bridge_channel *bridge_channel2;
+ 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) {
/*
- * 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.
+ * 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.
*/
- 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;
- }
+ if (!(bridge_channel->bridge->technology->capabilities
+ & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
+ ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
}
- /* 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;
+
+ 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);
}
- ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
+ bridge_channel_pull(bridge_channel);
+ bridge_reconfigured(bridge_channel->bridge);
- /* 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);
- }
+ ast_bridge_unlock(bridge_channel->bridge);
- /* 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);
- }
+ /* 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");
}
- /* Remove channel from the bridge */
- bridge_channel->bridge->num--;
- AST_LIST_REMOVE(&bridge_channel->bridge->channels, bridge_channel, entry);
+ /*
+ * 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);
- bridge_array_remove(bridge_channel->bridge, bridge_channel->chan);
+ ast_bridge_channel_restore_formats(bridge_channel);
+}
- /* 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);
+/*!
+ * \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;
}
+}
- ao2_unlock(bridge_channel->bridge);
+/*!
+ * \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;
- /* 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);
- }
+ 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;
}
- 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);
- }
+ 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;
}
-
- return bridge_channel->state;
+ 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);
@@ -1174,7 +2521,13 @@ static void bridge_channel_destroy(void *obj)
ao2_ref(bridge_channel->bridge, -1);
bridge_channel->bridge = NULL;
}
- /* Destroy elements of the bridge channel structure and the bridge channel structure itself */
+
+ /* 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);
}
@@ -1187,94 +2540,667 @@ static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge
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)
+ 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) {
- return AST_BRIDGE_CHANNEL_STATE_HANGUP;
+ 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;
- state = bridge_channel_join(bridge_channel);
+ 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 */
-static void *bridge_channel_thread(void *data)
+/*! \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;
- enum ast_bridge_channel_state state;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- state = bridge_channel_join(bridge_channel);
+ 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;
+}
- /* 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);
+/*! \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 */
- ao2_lock(bridge_channel);
+ 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_unlock(bridge_channel);
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 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)
{
+ 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->allow_impart_hangup = allow_hangup;
+ bridge_channel->depart_wait = independent ? 0 : 1;
bridge_channel->callid = ast_read_threadstorage_callid();
/* Actually create the thread that will handle the channel */
- if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_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;
}
@@ -1282,26 +3208,46 @@ int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struc
return 0;
}
-int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan)
+int ast_bridge_depart(struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
- pthread_t thread;
-
- ao2_lock(bridge);
-
- /* Try to find the channel that we want to depart */
- if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ 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;
}
- ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART);
- thread = bridge_channel->thread;
+ /*
+ * We are claiming the reference held by the depart bridge
+ * channel thread.
+ */
+
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- ao2_unlock(bridge);
+ /* 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);
- pthread_join(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;
}
@@ -1309,115 +3255,816 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
/* Try to find the channel that we want to remove */
if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return -1;
}
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return 0;
}
-int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1)
+/*!
+ * \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;
+ }
- ao2_lock(bridge0);
- ao2_lock(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;
- /* 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);
+ /* 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;
}
- ast_debug(1, "Merging channels from bridge %p into bridge %p\n", bridge1, bridge0);
+ 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;
+ }
+ }
- /* 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);
+ 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 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;
+ 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;
+ }
}
- /* 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);
+ 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;
}
}
+ }
- /* Drop channel count and reference count on the bridge they are leaving */
- bridge1->num--;
- ao2_ref(bridge1, -1);
+ return other;
+}
- bridge_array_remove(bridge1, bridge_channel->chan);
+/*!
+ * \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;
- /* 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);
+ 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;
+}
- bridge_array_add(bridge0, bridge_channel->chan);
+/*!
+ * \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;
- /* Make the channel compatible with the new bridge it is joining or else formats would go amuck */
- bridge_make_compatible(bridge0, 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;
- /* 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);
+ 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;
+}
- /* 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);
+/*!
+ * \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, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0);
+ ast_debug(1, "Merge optimizing %s -- %s out.\n",
+ ast_channel_name(chan_bridge_channel->chan),
+ ast_channel_name(peer_bridge_channel->chan));
- ao2_unlock(bridge1);
- ao2_unlock(bridge0);
+ bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me));
+ res = -1;
+ }
- return 0;
+ 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. */
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return -1;
}
- bridge_channel_suspend(bridge, bridge_channel);
+ bridge_channel_suspend_nolock(bridge_channel);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return 0;
}
@@ -1425,17 +4072,18 @@ int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
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. */
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return -1;
}
- bridge_channel_unsuspend(bridge, bridge_channel);
+ bridge_channel_unsuspend_nolock(bridge_channel);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return 0;
}
@@ -1447,10 +4095,11 @@ void ast_bridge_technology_suspend(struct ast_bridge_technology *technology)
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_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)
{
if (ARRAY_LEN(builtin_features_handlers) <= feature
|| builtin_features_handlers[feature]) {
@@ -1478,30 +4127,175 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
return 0;
}
-int ast_bridge_features_hook(struct ast_bridge_features *features,
+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_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)
{
- struct ast_bridge_features_hook *hook;
+ struct ast_bridge_hook *hook;
+ int res;
- /* Allocate new memory and setup it's various variables */
- hook = ast_calloc(1, sizeof(*hook));
+ /* 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->dtmf, dtmf, sizeof(hook->dtmf));
- hook->callback = callback;
- hook->destructor = destructor;
- hook->hook_pvt = hook_pvt;
+ ast_copy_string(hook->parms.dtmf.code, dtmf, sizeof(hook->parms.dtmf.code));
- /* 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);
+ /* Once done we put it in the container. */
+ res = ao2_link(features->dtmf_hooks, hook) ? 0 : -1;
+ ao2_ref(hook, -1);
- features->usable = 1;
+ return res;
+}
- return 0;
+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,
@@ -1514,7 +4308,57 @@ void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
features->talker_pvt_data = pvt_data;
}
-int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config)
+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]) {
@@ -1526,81 +4370,323 @@ int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_br
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);
+ 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 callback and DTMF, easy as pie. */
- return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL);
+ /*
+ * 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);
}
-void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag)
+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 hooks list, just in case */
- AST_LIST_HEAD_INIT_NOLOCK(&features->hooks);
+ /* 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_features_hook *hook;
+ struct ast_bridge_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);
+ /* Destroy the interval hooks heap. */
+ if (features->interval_hooks) {
+ while ((hook = ast_heap_pop(features->interval_hooks))) {
+ ao2_ref(hook, -1);
}
- ast_free(hook);
+ 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;
}
-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)
{
- struct ast_bridge_channel *bridge_channel;
+ if (!features) {
+ return;
+ }
+ ast_bridge_features_cleanup(features);
+ ast_free(features);
+}
- ao2_lock(bridge);
+struct ast_bridge_features *ast_bridge_features_new(void)
+{
+ struct ast_bridge_features *features;
- AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
- if (bridge_channel->chan == chan) {
- continue;
+ features = ast_malloc(sizeof(*features));
+ if (features) {
+ if (ast_bridge_features_init(features)) {
+ ast_bridge_features_destroy(features);
+ features = NULL;
}
- 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);
}
- ao2_unlock(bridge);
-
- return 0;
+ return features;
}
void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
{
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
bridge->internal_mixing_interval = mixing_interval;
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
{
-
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
bridge->internal_sample_rate = sample_rate;
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
static void cleanup_video_mode(struct ast_bridge *bridge)
@@ -1626,22 +4712,22 @@ static void cleanup_video_mode(struct ast_bridge *bridge)
void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
{
- ao2_lock(bridge);
+ 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);
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
{
- ao2_lock(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);
- ao2_unlock(bridge);
+ 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)
@@ -1652,7 +4738,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
return;
}
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
data = &bridge->video_mode.mode_data.talker_src_data;
if (data->chan_vsrc == chan) {
@@ -1680,14 +4766,14 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
data->chan_old_vsrc = ast_channel_ref(chan);
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
}
int ast_bridge_number_video_src(struct ast_bridge *bridge)
{
int res = 0;
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
@@ -1704,7 +4790,7 @@ int ast_bridge_number_video_src(struct ast_bridge *bridge)
res++;
}
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return res;
}
@@ -1712,7 +4798,7 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
{
int res = 0;
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
@@ -1729,13 +4815,13 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
}
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
return res;
}
void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
{
- ao2_lock(bridge);
+ ast_bridge_lock(bridge);
switch (bridge->video_mode.mode) {
case AST_BRIDGE_VIDEO_MODE_NONE:
break;
@@ -1762,5 +4848,996 @@ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *
bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
}
}
- ao2_unlock(bridge);
+ ast_bridge_unlock(bridge);
+}
+
+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;
+ }
+ return hash;
+}
+
+static int channel_cmp(void *obj, void *arg, int flags)
+{
+ const struct ast_channel *left = obj;
+ const struct ast_channel *right = arg;
+ const char *right_name = arg;
+ int cmp;
+
+ 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 cmp ? 0 : CMP_MATCH;
+}
+
+struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge)
+{
+ struct ao2_container *channels;
+ struct ast_bridge_channel *iter;
+
+ channels = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ 13, channel_hash, channel_cmp);
+ if (!channels) {
+ return NULL;
+ }
+
+ AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
+ ao2_link(channels, iter->chan);
+ }
+
+ return channels;
+}
+
+struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge)
+{
+ struct ao2_container *channels;
+
+ ast_bridge_lock(bridge);
+ channels = ast_bridge_peers_nolock(bridge);
+ ast_bridge_unlock(bridge);
+
+ return channels;
+}
+
+struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+ 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;
+ }
+ }
+
+ return peer;
+}
+
+struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan)
+{
+ struct ast_channel *peer;
+
+ ast_bridge_lock(bridge);
+ peer = ast_bridge_peer_nolock(bridge, chan);
+ ast_bridge_unlock(bridge);
+
+ return peer;
+}
+
+/*!
+ * \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_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;
+ }
+
+ if (new_channel_cb) {
+ new_channel_cb(local, user_data);
+ }
+
+ 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;
+}
+
+/*!
+ * \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;
+ }
+ }
+
+ ao2_iterator_destroy(&channel_iter);
+ return transferee;
+}
+
+/*!
+ * \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)
+{
+ RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup);
+ struct blind_transfer_data blind_data;
+
+ ast_channel_lock(transferee);
+ transferee_bridge_channel = ast_channel_get_bridge_channel(transferee);
+ ast_channel_unlock(transferee);
+
+ if (!transferee_bridge_channel) {
+ return -1;
+ }
+
+ if (new_channel_cb) {
+ new_channel_cb(transferee, user_data);
+ }
+
+ ast_copy_string(blind_data.exten, exten, sizeof(blind_data.exten));
+ ast_copy_string(blind_data.context, context, sizeof(blind_data.context));
+
+/* 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;
+}
+
+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)
+{
+ /* 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
+ */
+
+#if 0
+ RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup);
+ struct ast_exten *parking_exten;
+
+ ast_channel_lock(transferer);
+ transfer_bridge_channel = ast_channel_get_bridge_channel(transferer);
+ ast_channel_unlock(transferer);
+
+ if (!transfer_bridge_channel) {
+ return PARKING_FAILURE;
+ }
+
+ 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 PARKING_NOT_APPLICABLE;
+}
+
+/*!
+ * \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 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_iterator_destroy(&iter);
+}
+
+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_channel_lock(transferer);
+ bridge = ast_channel_get_bridge(transferer);
+ ast_channel_unlock(transferer);
+
+ if (!bridge) {
+ return AST_BRIDGE_TRANSFER_INVALID;
+ }
+
+ 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;
+ }
+
+ {
+ 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;
+ }
+
+ if (transfer_prohibited) {
+ return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+ }
+
+ set_blind_transfer_variables(transferer, channels);
+
+ if (do_bridge_transfer) {
+ return blind_transfer_bridge(transferer, bridge, exten, context,
+ new_channel_cb, user_data);
+ }
+
+ /* Reaching this portion means that we're dealing with a two-party bridge */
+
+ transferee = get_transferee(channels, transferer);
+ if (!transferee) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ if (bridge_channel_queue_blind_transfer(transferee, exten, context,
+ new_channel_cb, user_data)) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ ast_bridge_remove(bridge, transferer);
+ return AST_BRIDGE_TRANSFER_SUCCESS;
+}
+
+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.
+ */
+
+ /* XXX STUB */
+ return AST_BRIDGE_TRANSFER_SUCCESS;
+}
+
+/*!
+ * \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)
+{
+ ast_bridge_lock(bridge);
+ if (bridge->callid) {
+ ast_callid_threadassoc_change(bridge->callid);
+ }
+
+ /* Do any pending bridge actions. */
+ bridge_handle_actions(bridge);
+ ast_bridge_unlock(bridge);
+}
+
+/*!
+ * \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);
+
+ /* Service the bridge. */
+ bridge_manager_service(request->bridge);
+ ao2_ref(request->bridge, -1);
+ ast_free(request);
+
+ ao2_lock(manager);
+ }
+ ao2_unlock(manager);
+
+ return NULL;
+}
+
+/*!
+ * \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 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);
+ }
+
+ /* Destroy the service request queue. */
+ while ((request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node))) {
+ ao2_ref(request->bridge, -1);
+ ast_free(request);
+ }
+
+ 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;
+
+ 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 manager;
+}
+
+/*!
+ * \internal
+ * \brief Bridge ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ * OBJ_POINTER - if set, 'obj_right', is an object.
+ * OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ * OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+ const struct ast_bridge *bridge_left = obj_left;
+ const struct ast_bridge *bridge_right = obj_right;
+ const char *right_key = obj_right;
+ int cmp;
+
+ switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+ default:
+ case OBJ_POINTER:
+ right_key = bridge_right->uniqueid;
+ /* Fall through */
+ case OBJ_KEY:
+ cmp = strcmp(bridge_left->uniqueid, right_key);
+ break;
+ case OBJ_PARTIAL_KEY:
+ cmp = strncmp(bridge_left->uniqueid, right_key, strlen(right_key));
+ break;
+ }
+ return cmp;
+}
+
+struct bridge_complete {
+ /*! Nth match to return. */
+ int state;
+ /*! Which match currently on. */
+ int which;
+};
+
+static int complete_bridge_search(void *obj, void *arg, void *data, int flags)
+{
+ struct bridge_complete *search = data;
+
+ if (++search->which > search->state) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
+static char *complete_bridge(const char *word, int state)
+{
+ char *ret;
+ struct ast_bridge *bridge;
+ struct bridge_complete search = {
+ .state = state,
+ };
+
+ bridge = ao2_callback_data(bridges, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+ complete_bridge_search, (char *) word, &search);
+ if (!bridge) {
+ return NULL;
+ }
+ ret = ast_strdup(bridge->uniqueid);
+ ao2_ref(bridge, -1);
+ return ret;
+}
+
+static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-36s %5s %-15s %s\n"
+#define FORMAT_ROW "%-36s %5u %-15s %s\n"
+
+ struct ao2_iterator iter;
+ struct ast_bridge *bridge;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge show all";
+ e->usage =
+ "Usage: bridge show all\n"
+ " List all bridges\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+ ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology");
+ iter = ao2_iterator_init(bridges, 0);
+ for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) {
+ ast_bridge_lock(bridge);
+ ast_cli(a->fd, FORMAT_ROW,
+ bridge->uniqueid,
+ bridge->num_channels,
+ bridge->v_table ? bridge->v_table->name : "<unknown>",
+ bridge->technology ? bridge->technology->name : "<unknown>");
+ ast_bridge_unlock(bridge);
+ }
+ ao2_iterator_destroy(&iter);
+ return CLI_SUCCESS;
+
+#undef FORMAT_HDR
+#undef FORMAT_ROW
+}
+
+static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_bridge *bridge;
+ struct ast_bridge_channel *bridge_channel;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge show";
+ e->usage =
+ "Usage: bridge show <bridge-id>\n"
+ " Show information about the <bridge-id> bridge\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_bridge(a->word, a->n);
+ }
+ return NULL;
+ }
+
+/* BUGBUG this command may need to be changed to look at the stasis cache. */
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+ if (!bridge) {
+ ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ ast_bridge_lock(bridge);
+ ast_cli(a->fd, "Id: %s\n", bridge->uniqueid);
+ ast_cli(a->fd, "Type: %s\n", bridge->v_table ? bridge->v_table->name : "<unknown>");
+ ast_cli(a->fd, "Technology: %s\n",
+ bridge->technology ? bridge->technology->name : "<unknown>");
+ ast_cli(a->fd, "Num-Channels: %u\n", bridge->num_channels);
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ ast_cli(a->fd, "Channel: %s\n", ast_channel_name(bridge_channel->chan));
+ }
+ ast_bridge_unlock(bridge);
+ ao2_ref(bridge, -1);
+
+ return CLI_SUCCESS;
+}
+
+static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_bridge *bridge;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge destroy";
+ e->usage =
+ "Usage: bridge destroy <bridge-id>\n"
+ " Destroy the <bridge-id> bridge\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_bridge(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+ if (!bridge) {
+ ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ ast_cli(a->fd, "Destroying bridge '%s'\n", a->argv[2]);
+ ast_bridge_destroy(bridge);
+
+ return CLI_SUCCESS;
+}
+
+static char *complete_bridge_participant(const char *bridge_name, const char *line, const char *word, int pos, int state)
+{
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
+ int which;
+ int wordlen;
+
+ bridge = ao2_find(bridges, bridge_name, OBJ_KEY);
+ if (!bridge) {
+ return NULL;
+ }
+
+ {
+ SCOPED_LOCK(bridge_lock, bridge, ast_bridge_lock, ast_bridge_unlock);
+
+ which = 0;
+ wordlen = strlen(word);
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen)
+ && ++which > state) {
+ return ast_strdup(ast_channel_name(bridge_channel->chan));
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge kick";
+ e->usage =
+ "Usage: bridge kick <bridge-id> <channel-name>\n"
+ " Kick the <channel-name> channel out of the <bridge-id> bridge\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_bridge(a->word, a->n);
+ }
+ if (a->pos == 3) {
+ return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ bridge = ao2_find(bridges, a->argv[2], OBJ_KEY);
+ if (!bridge) {
+ ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ chan = ast_channel_get_by_name_prefix(a->argv[3], strlen(a->argv[3]));
+ if (!chan) {
+ ast_cli(a->fd, "Channel '%s' not found\n", a->argv[3]);
+ return CLI_SUCCESS;
+ }
+
+/*
+ * BUGBUG the CLI kick needs to get the bridge to decide if it should dissolve.
+ *
+ * Likely the best way to do this is to add a kick method. The
+ * basic bridge class can then decide to dissolve the bridge if
+ * one of two channels is kicked.
+ *
+ * SIP/foo -- Local;1==Local;2 -- .... -- Local;1==Local;2 -- SIP/bar
+ * Kick a ;1 channel and the chain toward SIP/foo goes away.
+ * Kick a ;2 channel and the chain toward SIP/bar goes away.
+ *
+ * This can leave a local channel chain between the kicked ;1
+ * and ;2 channels that are orphaned until you manually request
+ * one of those channels to hangup or request the bridge to
+ * dissolve.
+ */
+ ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n",
+ ast_channel_name(chan), a->argv[2]);
+ ast_bridge_remove(bridge, chan);
+
+ return CLI_SUCCESS;
+}
+
+/*! Bridge technology capabilities to string. */
+static const char *tech_capability2str(uint32_t capabilities)
+{
+ const char *type;
+
+ if (capabilities & AST_BRIDGE_CAPABILITY_HOLDING) {
+ type = "Holding";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_EARLY) {
+ type = "Early";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_NATIVE) {
+ type = "Native";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) {
+ type = "1to1Mix";
+ } else if (capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+ type = "MultiMix";
+ } else {
+ type = "<Unknown>";
+ }
+ return type;
+}
+
+static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_HDR "%-20s %-20s %8s %s\n"
+#define FORMAT_ROW "%-20s %-20s %8d %s\n"
+
+ struct ast_bridge_technology *cur;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge technology show";
+ e->usage =
+ "Usage: bridge technology show\n"
+ " List registered bridge technologies\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ ast_cli(a->fd, FORMAT_HDR, "Name", "Type", "Priority", "Suspended");
+ AST_RWLIST_RDLOCK(&bridge_technologies);
+ AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+ const char *type;
+
+ /* Decode type for display */
+ type = tech_capability2str(cur->capabilities);
+
+ 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
+}
+
+static char *complete_bridge_technology(const char *word, int state)
+{
+ struct ast_bridge_technology *cur;
+ char *res;
+ int which;
+ int wordlen;
+
+ which = 0;
+ wordlen = strlen(word);
+ AST_RWLIST_RDLOCK(&bridge_technologies);
+ AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+ if (!strncasecmp(cur->name, word, wordlen) && ++which > state) {
+ res = ast_strdup(cur->name);
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+ return res;
+ }
+ }
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+ return NULL;
+}
+
+static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_bridge_technology *cur;
+ int suspend;
+ int successful;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "bridge technology {suspend|unsuspend}";
+ e->usage =
+ "Usage: bridge technology {suspend|unsuspend} <technology-name>\n"
+ " Suspend or unsuspend a bridge technology.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return complete_bridge_technology(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ suspend = !strcasecmp(a->argv[2], "suspend");
+ successful = 0;
+ AST_RWLIST_WRLOCK(&bridge_technologies);
+ AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) {
+ if (!strcasecmp(cur->name, a->argv[3])) {
+ successful = 1;
+ if (suspend) {
+ ast_bridge_technology_suspend(cur);
+ } else {
+ ast_bridge_technology_unsuspend(cur);
+ }
+ break;
+ }
+ }
+ AST_RWLIST_UNLOCK(&bridge_technologies);
+
+ if (successful) {
+ if (suspend) {
+ ast_cli(a->fd, "Suspended bridge technology '%s'\n", a->argv[3]);
+ } else {
+ ast_cli(a->fd, "Unsuspended bridge technology '%s'\n", a->argv[3]);
+ }
+ } else {
+ ast_cli(a->fd, "Bridge technology '%s' not found\n", a->argv[3]);
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry bridge_cli[] = {
+ AST_CLI_DEFINE(handle_bridge_show_all, "List all bridges"),
+ AST_CLI_DEFINE(handle_bridge_show_specific, "Show information about a bridge"),
+ AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"),
+ AST_CLI_DEFINE(handle_bridge_kick_channel, "Kick a channel from a bridge"),
+ AST_CLI_DEFINE(handle_bridge_technology_show, "List registered bridge technologies"),
+ AST_CLI_DEFINE(handle_bridge_technology_suspend, "Suspend/unsuspend a bridge technology"),
+};
+
+/*!
+ * \internal
+ * \brief Shutdown the bridging system.
+ * \since 12.0.0
+ *
+ * \return Nothing
+ */
+static void bridge_shutdown(void)
+{
+ ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+ ao2_cleanup(bridges);
+ bridges = NULL;
+ ao2_cleanup(bridge_manager);
+ bridge_manager = NULL;
+ ast_stasis_bridging_shutdown();
+}
+
+int ast_bridging_init(void)
+{
+ if (ast_stasis_bridging_init()) {
+ bridge_shutdown();
+ return -1;
+ }
+
+ bridge_manager = bridge_manager_create();
+ if (!bridge_manager) {
+ bridge_shutdown();
+ return -1;
+ }
+
+ bridges = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_sort_cmp, NULL);
+ if (!bridges) {
+ bridge_shutdown();
+ return -1;
+ }
+
+ ast_bridging_init_basic();
+
+/* BUGBUG need AMI action equivalents to the CLI commands. */
+ ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli));
+
+ ast_register_atexit(bridge_shutdown);
+ return 0;
}
diff --git a/main/bridging_basic.c b/main/bridging_basic.c
new file mode 100644
index 000000000..00daf7710
--- /dev/null
+++ b/main/bridging_basic.c
@@ -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
index 000000000..079cbdb33
--- /dev/null
+++ b/main/bridging_roles.c
@@ -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;
+ }
+}
diff --git a/main/channel.c b/main/channel.c
index 0b2dedd63..aec43edf7 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -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;
+}
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index f3293d59b..42aaada6d 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -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;
diff --git a/main/cli.c b/main/cli.c
index 7782b2279..22232acbc 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -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);
diff --git a/main/config_options.c b/main/config_options.c
index 06b452131..39a3fbe61 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -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
index 000000000..2e0bcc48a
--- /dev/null
+++ b/main/core_local.c
@@ -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
index 000000000..d5e588111
--- /dev/null
+++ b/main/core_unreal.c
@@ -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;
+}
diff --git a/main/features.c b/main/features.c
index b6cc19164..be872a80d 100644
--- a/main/features.c
+++ b/main/features.c
@@ -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");
@@ -2815,31 +2653,11 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
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;
}
}
}
@@ -4334,6 +4142,279 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st
/*!
* \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.
* \since 12.0.0
*
@@ -4375,6 +4456,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
*
* \param chan The bridge considers this channel the caller.
@@ -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];
+ /*
+ * 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_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);
+ 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_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);
+ /* 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;
}
- 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);
+ 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. */
- for (;;) {
- struct ast_channel *other; /* used later */
+ if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) {
+ ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n");
- res = ast_channel_bridge(chan, peer, config, &f, &who);
+ ast_bridge_features_destroy(peer_features);
+ ast_bridge_features_cleanup(&chan_features);
+ bridge_failed_peer_goto(chan, peer);
+ return -1;
+ }
- 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_peer)) {
+ ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n");
+ ast_bridge_features_limits_destroy(&call_duration_limits_chan);
- 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;
- }
+ bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer);
- 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);
- }
- }
+ 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;
}
- 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");
- }
+ /* 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);
- /* 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 (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;
+ }
}
- /* Wait for any dual redirect to complete. */
- while (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) {
- sched_yield();
+ /* 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;
}
- 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? */
- }
- return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */
+ /* 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;
}
- if (config->end_bridge_callback) {
- config->end_bridge_callback(config->end_bridge_callback_data);
- }
+ /* Join bridge */
+ ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 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 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 (!(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);
+ res = -1;
+ ast_channel_lock(chan);
+ if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+ res = 0;
}
+ ast_channel_unlock(chan);
- /* 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 */
+ ast_bridge_features_cleanup(&chan_features);
- /*
- * 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 (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 */
- }
+/* 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;
}
-
diff --git a/main/frame.c b/main/frame.c
index b559d552d..8822261f6 100644
--- a/main/frame.c
+++ b/main/frame.c
@@ -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;
diff --git a/main/manager.c b/main/manager.c
index c9b2fbe1e..c28e6169b 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -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
index 000000000..01ce68aaf
--- /dev/null
+++ b/main/manager_bridging.c
@@ -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;
+}
diff --git a/main/manager_channels.c b/main/manager_channels.c
index 0cab36562..fb579dd95 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -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
index 000000000..2a5b72e61
--- /dev/null
+++ b/main/parking.c
@@ -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);
+ }
+}
diff --git a/main/pbx.c b/main/pbx.c
index 6359c056c..8408048f2 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -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;
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index b25cb95b3..8e58f658d 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -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
index 000000000..2ee4fcfc1
--- /dev/null
+++ b/main/stasis_bridging.c
@@ -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;
+}
diff --git a/main/strings.c b/main/strings.c
index 47715e63e..d004da227 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -160,3 +160,4 @@ char *__ast_str_helper2(struct ast_str **buf, ssize_t maxlen, const char *src, s
return (*buf)->__AST_STR_STR;
}
+
diff --git a/res/Makefile b/res/Makefile
index 2ed719093..667e097e8 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -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
index 000000000..097329b93
--- /dev/null
+++ b/res/parking/parking_applications.c
@@ -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
index 000000000..b9ceb5203
--- /dev/null
+++ b/res/parking/parking_bridge.c
@@ -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
index 000000000..ddb5e7ff2
--- /dev/null
+++ b/res/parking/parking_bridge_features.c
@@ -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
index 000000000..03d7b8861
--- /dev/null
+++ b/res/parking/parking_controller.c
@@ -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
index 000000000..d6a05573f
--- /dev/null
+++ b/res/parking/parking_manager.c
@@ -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
index 000000000..5b432b689
--- /dev/null
+++ b/res/parking/parking_ui.c
@@ -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
index 000000000..7f66d6e8f
--- /dev/null
+++ b/res/parking/res_parking.h
@@ -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
index 000000000..72627f164
--- /dev/null
+++ b/res/res_parking.c
@@ -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,
+);
diff --git a/res/res_stasis_json_events.c b/res/res_stasis_json_events.c
index f843310b6..10d36be42 100644
--- a/res/res_stasis_json_events.c
+++ b/res/res_stasis_json_events.c
@@ -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
diff --git a/res/res_stasis_json_events.exports.in b/res/res_stasis_json_events.exports.in
index 5a260a5f3..e3f59ca76 100644
--- a/res/res_stasis_json_events.exports.in
+++ b/res/res_stasis_json_events.exports.in
@@ -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:
diff --git a/res/stasis_json/resource_events.h b/res/stasis_json/resource_events.h
index bd1c3263b..63abe0f85 100644
--- a/res/stasis_json/resource_events.h
+++ b/res/stasis_json/resource_events.h
@@ -38,17 +38,33 @@
#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
);
/*!
@@ -68,6 +84,18 @@ struct ast_json *stasis_json_event_channel_destroyed_create(
);
/*!
+ * \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.
*
* \param channel The channel that changed Caller ID.
@@ -100,6 +128,18 @@ struct ast_json *stasis_json_event_channel_hangup_request_create(
);
/*!
+ * \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.
*
* \param blob JSON blob containing the following parameters:
@@ -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
);
/*!
@@ -199,6 +238,20 @@ struct ast_json *stasis_json_event_channel_state_change_create(
);
/*!
+ * \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.
*
* \param channel The channel on which DTMF was received
@@ -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
*/
diff --git a/rest-api-templates/res_stasis_json_resource.c.mustache b/rest-api-templates/res_stasis_json_resource.c.mustache
index 039830270..a55389c07 100644
--- a/rest-api-templates/res_stasis_json_resource.c.mustache
+++ b/rest-api-templates/res_stasis_json_resource.c.mustache
@@ -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}}
diff --git a/rest-api-templates/stasis_json_resource.h.mustache b/rest-api-templates/stasis_json_resource.h.mustache
index f55e830bd..8cfd2c1f7 100644
--- a/rest-api-templates/stasis_json_resource.h.mustache
+++ b/rest-api-templates/stasis_json_resource.h.mustache
@@ -38,6 +38,7 @@
{{#has_events}}
struct ast_channel_snapshot;
+struct ast_bridge_snapshot;
{{#events}}
/*!
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 5b6e0549d..4a36da3b8 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -54,9 +54,13 @@
"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" },
@@ -79,6 +83,26 @@
}
}
},
+ "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.",
@@ -119,6 +143,33 @@
}
}
},
+ "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.",