diff --git a/CHANGES b/CHANGES index f5c705ed7f983ebbd7ec4a457500ba571f25fb9f..ec85f5ded464e1ecd950e051ff42861d2d68045f 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 39f08049b9606e273ac2a05d1c494cf0241df00f..e2e7090081685fe77441bac55cc6a37cb0c4ae2b 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 a393e8e0ef8ce4ff693937764af0b5faa867bf97..61d9245fe18866604ec60851398af04202942d48 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 0000000000000000000000000000000000000000..ca12f0d30f53200cc7927afebad841d0ddf913d4 --- /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 8c98ed7b723fa1ea057925f66f19cc34a49db1d4..f636e02480187e88b2751898630731de8d0e6816 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 c5adb78adf6e47ad229218c3093ae5b2c423c01a..8e45907819c1a8d54bd768ca00dee51bfd6025f2 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 d4cce4b46c4148cc4069165dc9135e1fa74ea461..5107bcd5cc10b9ee42443b79c0106b37dd23ee0d 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 d3d37216aa5ca47c689d0fb99345a048de3bc2b7..35c9ad8bfb9c76a6cf613be1d87c7ccbe085df7c 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 4a80a3d13ea269bebed442b03a7e8e12a824dde3..722f15541757cd9545900d0cfa11a61b8eeaba6c 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 83f583bb3777339268f41a9f5a4ae176eac967a1..43f1967087bf246abf4a0255fa5b188ade1cb935 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 6e7976ec9fd4ba4d269339d8b626754b3ee02f51..e8d4903f0b1d9d4513a43811a9aba0d8ff6d0158 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 6d6ccae261d2ea94424f04821ab883084b74221d..0000000000000000000000000000000000000000 --- 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 c63cd071ed0191c55ed99d5d95aa19d4766438c9..8a96b64b21b5afe5d900d97ee19c647ec93d958d 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 */ @@ -5098,6 +5103,35 @@ static void end_bridge_callback(void *data) } } +/*! + * \internal + * \brief Setup the after bridge goto location on the peer. + * \since 12.0.0 + * + * \param chan Calling channel for bridge. + * \param peer Peer channel for bridge. + * \param opts Dialing option flags. + * \param opt_args Dialing option argument strings. + * + * \return Nothing + */ +static void setup_peer_after_bridge_goto(struct ast_channel *chan, struct ast_channel *peer, struct ast_flags *opts, char *opt_args[]) +{ + const char *context; + const char *extension; + int priority; + + if (ast_test_flag(opts, OPT_CALLEE_GO_ON)) { + ast_channel_lock(chan); + context = ast_strdupa(ast_channel_context(chan)); + extension = ast_strdupa(ast_channel_exten(chan)); + priority = ast_channel_priority(chan); + ast_channel_unlock(chan); + ast_after_bridge_set_go_on(peer, context, extension, priority, + opt_args[OPT_ARG_CALLEE_GO_ON]); + } +} + /*! * \internal * \brief A large function which calls members, updates statistics, and bridges the caller and a member @@ -5128,7 +5162,7 @@ static void end_bridge_callback(void *data) * \param[in] gosub the gosub passed as the seventh parameter to the Queue() application * \param[in] ringing 1 if the 'r' option is set, otherwise 0 */ -static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) +static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) { struct member *cur; struct callattempt *outgoing = NULL; /* the list of calls we are building */ @@ -5200,9 +5234,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * if (ast_test_flag(&opts, OPT_CALLER_AUTOMON)) { ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON); } - if (ast_test_flag(&opts, OPT_GO_ON)) { - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_NO_H_EXTEN); - } if (ast_test_flag(&opts, OPT_DATA_QUALITY)) { nondataquality = 0; } @@ -5244,7 +5275,7 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * } /* if the calling channel has AST_CAUSE_ANSWERED_ELSEWHERE set, make sure this is inherited. - (this is mainly to support chan_local) + (this is mainly to support unreal/local channels) */ if (ast_channel_hangupcause(qe->chan) == AST_CAUSE_ANSWERED_ELSEWHERE) { qe->cancel_answered_elsewhere = 1; @@ -5437,11 +5468,6 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * } } } else { /* peer is valid */ - /* These variables are used with the F option without arguments (callee jumps to next priority after queue) */ - char *caller_context; - char *caller_extension; - int caller_priority; - /* Ah ha! Someone answered within the desired timeframe. Of course after this we will always return with -1 so that it is hung up properly after the conversation. */ @@ -5595,11 +5621,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * set_queue_variables(qe->parent, qe->chan); set_queue_variables(qe->parent, peer); + setup_peer_after_bridge_goto(qe->chan, peer, &opts, opt_args); ast_channel_lock(qe->chan); - /* Copy next destination data for 'F' option (no args) */ - caller_context = ast_strdupa(ast_channel_context(qe->chan)); - caller_extension = ast_strdupa(ast_channel_exten(qe->chan)); - caller_priority = ast_channel_priority(qe->chan); if ((monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"))) { monitorfilename = ast_strdupa(monitorfilename); } @@ -5883,6 +5906,8 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl); bridge = ast_bridge_call(qe->chan, peer, &bridge_config); +/* BUGBUG need to do this queue logging a different way because we cannot reference peer anymore. Likely needs to be made a subscriber of stasis transfer events. */ +#if 0 // BUGBUG /* If the queue member did an attended transfer, then the TRANSFER already was logged in the queue_log * when the masquerade occurred. These other "ending" queue_log messages are unnecessary, except for * the AgentComplete manager event @@ -5917,28 +5942,12 @@ static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char * /* We already logged the TRANSFER on the queue_log, but we still need to send the AgentComplete event */ send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER); } +#endif // BUGBUG if (transfer_ds) { ast_datastore_free(transfer_ds); } - if (!ast_check_hangup(peer) && ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { - int goto_res; - - if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) { - ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]); - goto_res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]); - } else { /* F() */ - goto_res = ast_goto_if_exists(peer, caller_context, caller_extension, - caller_priority + 1); - } - if (goto_res || ast_pbx_start(peer)) { - ast_autoservice_chan_hangup_peer(qe->chan, peer); - } - } else { - ast_autoservice_chan_hangup_peer(qe->chan, peer); - } - res = bridge ? bridge : 1; ao2_ref(member, -1); } diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c new file mode 100644 index 0000000000000000000000000000000000000000..46e074b2064be9d1eafce9373f596607451670e0 --- /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 0000000000000000000000000000000000000000..18f971f35d7fb9d0845ff4997b1a11bcacd36523 --- /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 1bca2d5c2f3c4fab4b3e56ce06c15eb80596470c..6cec255221be14ad64162747f6c15dbdfbe2b9fb 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 0000000000000000000000000000000000000000..56fedb98e834d30f3e6fc0fec2efcbf8f09ebe61 --- /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 2455613769f34077175c7c97f33c9f0feec4e6fb..f0620149a0c7a9be28f78c8cf7c435ecb4f0c92d 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 428d6deda24a48087f30b8bf610ede50d6cba2fc..493b5c89136d34d3697f062b3168b8057609ae46 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 0000000000000000000000000000000000000000..a0e767ed3bf05f1e89ea26e81e62b9b89ee22e31 --- /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 0000000000000000000000000000000000000000..fe0a7303fc2a478a15c571bb7280a4f0a10c38e1 --- /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 309ad47e3ec8fc79a476848c217c97252b7377c9..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..1117e5aedb5505028888190e9bbe17bbed398b27 --- /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 947983bae495bc2ff8dc6d24de1049dba6fcbea4..3e53b31c0e1849b7f361927228954c54e6e8d921 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 613601a1f154e7828f7f6aed3a86afb42cd7670a..4583435a08d471d7dbab34ac9d862da445779aa6 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 3fb6891ccb4898e4658fc515855593a39d18baad..d72254ee75d914504ef411d4345caf985ba12898 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 8eac76a829abd63a291e06c7816764a48517d6de..0000000000000000000000000000000000000000 --- 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 44fe3f4fb90d10006fac980ec8aeade54f2a3529..1f9ee5a56fda98ec21ed556e040de3482c054470 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 74859534b1100389dd1b93938d4f843d584295bf..4af137f282f9fc758aca7a174cd2168b7226fd7f 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 f6364a1186ca524e225a42a1b44221a23b3bd105..2476f5065f8dddcecd67b50b4bb7812d74112bb0 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 112a99375366b9e2bdf954437a610d3c0a5a46ce..49ce66cb7921d1045c899d614c146bb94c2aa865 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 717afae6750632bcb39d2b62ccb050ef928a95b1..42dccf666a8083bfc4b3238b75b8f81ac4847e89 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 be4586fc89ace9f29d276006afae1862ba5b7b1a..0000000000000000000000000000000000000000 --- 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 e254823bbabf2b618f4171f1eeed4d939ad51f4c..1f0830762b18ae487f28ff8759b210bc4098d872 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 2bc6f1e35e2b696eeae9763450aefb8d8f997469..aaa7170b3a9a28c8bb7dd26f338e97c9da3c22b7 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 0836972e167581261096b3d104aaacb359f35cb3..56b06b14571172af9aa05b6d88fb2eeddeea01fb 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 88965fc73a94f01de15f60c534d55ce3023973f4..f7a528b681df4cbdae5b8dea9fcce15f23a67bba 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, ¤t, 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 b3951d5e48294913b89553e018703d9a77222139..cd194d5a8e0f43d9bbcd71128fc0fbf42abae715 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 dea796364564947c042e94330e8fb3435c0d8aa9..121e2f0b1530368502d3b8c19c6ea8603cda2b61 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 58fc73f699ce4c1ebccf9b24112ab78d9906396d..d1747cb066f9905dfc1bebb59980152d98b078b0 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 701ccdf37f6d26a1da5d9e345dd06d91d35938d8..12fb3151c9c00c85a065ed4381e679db729d23f7 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 0000000000000000000000000000000000000000..b8308e6ab4fedd4e4b1f44e594700f96910e3eaa --- /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 db9434d834a433cc7c602ee125ea53c84dca225f..93792440074295aebeb9082f5348d69ecd1a89d5 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 6410d12b80211d56e025706abae1bb270e0cc70e..94a2c13d0829d64005c86de3157abfd3f17e1945 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 066d9d2f626ec9c8b1e6dd0c98409eb11b5a3f7b..a00361043c2ac6210f0a8b9975189d582e8c36bc 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 7e1ef13ec490ec8336e4f7c08fbd3cef2e6210f6..3fe35e58ceecd697b25a6c683167fc97a80aaa1d 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -51,6 +51,24 @@ int ast_msg_init(void); /*!< Provided by message.c */ void ast_msg_shutdown(void); /*!< Provided by message.c */ int aco_init(void); /*!< Provided by config_options.c */ +/*! + * \brief Initialize the bridging system. + * \since 12.0.0 + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ast_bridging_init(void); + +/*! + * \brief Initialize the local proxy channel. + * \since 12.0.0 + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ast_local_init(void); + /*! * \brief Reload asterisk modules. * \param name the name of the module to reload diff --git a/include/asterisk/abstract_jb.h b/include/asterisk/abstract_jb.h index 3e6bedd269cc36cd3d974f40a052418115a6c621..6a4d0610d126729fd3bb82884687d654ed6b7bd9 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 2890b1dfa7d90446350d01eff299c374699458b5..77fb54e8ddbe7e4e5d0619679431f7339183b44c 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 { @@ -206,14 +275,152 @@ struct ast_bridge_video_mode { } mode_data; }; +/*! + * \brief Destroy the bridge. + * + * \param self Bridge to operate upon. + * + * \return Nothing + */ +typedef void (*ast_bridge_destructor_fn)(struct ast_bridge *self); + +/*! + * \brief The bridge is being dissolved. + * + * \param self Bridge to operate upon. + * + * \details + * The bridge is being dissolved. Remove any external + * references to the bridge so it can be destroyed. + * + * \note On entry, self must NOT be locked. + * + * \return Nothing + */ +typedef void (*ast_bridge_dissolving_fn)(struct ast_bridge *self); + +/*! + * \brief Push this channel into the bridge. + * + * \param self Bridge to operate upon. + * \param bridge_channel Bridge channel to push. + * \param swap Bridge channel to swap places with if not NULL. + * + * \details + * Setup any channel hooks controlled by the bridge. Allocate + * bridge_channel->bridge_pvt and initialize any resources put + * in bridge_channel->bridge_pvt if needed. If there is a swap + * channel, use it as a guide to setting up the bridge_channel. + * + * \note On entry, self is already locked. + * + * \retval 0 on success. + * \retval -1 on failure. The channel did not get pushed. + */ +typedef int (*ast_bridge_push_channel_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap); + +/*! + * \brief Pull this channel from the bridge. + * + * \param self Bridge to operate upon. + * \param bridge_channel Bridge channel to pull. + * + * \details + * Remove any channel hooks controlled by the bridge. Release + * any resources held by bridge_channel->bridge_pvt and release + * bridge_channel->bridge_pvt. + * + * \note On entry, self is already locked. + * + * \return Nothing + */ +typedef void (*ast_bridge_pull_channel_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel); + +/*! + * \brief Notify the bridge that this channel was just masqueraded. + * + * \param self Bridge to operate upon. + * \param bridge_channel Bridge channel that was masqueraded. + * + * \details + * A masquerade just happened to this channel. The bridge needs + * to re-evaluate this a channel in the bridge. + * + * \note On entry, self is already locked. + * + * \return Nothing + */ +typedef void (*ast_bridge_notify_masquerade_fn)(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel); + +/*! + * \brief Get the merge priority of this bridge. + * + * \param self Bridge to operate upon. + * + * \note On entry, self is already locked. + * + * \return Merge priority + */ +typedef int (*ast_bridge_merge_priority_fn)(struct ast_bridge *self); + +/*! + * \brief Bridge virtual methods table definition. + * + * \note Any changes to this struct must be reflected in + * ast_bridge_alloc() validity checking. + */ +struct ast_bridge_methods { + /*! Bridge class name for log messages. */ + const char *name; + /*! Destroy the bridge. */ + ast_bridge_destructor_fn destroy; + /*! The bridge is being dissolved. Remove any references to the bridge. */ + ast_bridge_dissolving_fn dissolving; + /*! Push the bridge channel into the bridge. */ + ast_bridge_push_channel_fn push; + /*! Pull the bridge channel from the bridge. */ + ast_bridge_pull_channel_fn pull; + /*! Notify the bridge of a masquerade with the channel. */ + ast_bridge_notify_masquerade_fn notify_masquerade; + /*! Get the bridge merge priority. */ + ast_bridge_merge_priority_fn get_merge_priority; +}; + /*! * \brief Structure that contains information about a bridge */ struct ast_bridge { - /*! Number of channels participating in the bridge */ - int num; + /*! Bridge virtual method table. */ + const struct ast_bridge_methods *v_table; + /*! Immutable bridge UUID. */ + char uniqueid[AST_UUID_STR_LEN]; + /*! Bridge technology that is handling the bridge */ + struct ast_bridge_technology *technology; + /*! Private information unique to the bridge technology */ + void *tech_pvt; + /*! Call ID associated with the bridge */ + struct ast_callid *callid; + /*! Linked list of channels participating in the bridge */ + AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels; + /*! Queue of actions to perform on the bridge. */ + AST_LIST_HEAD_NOLOCK(, ast_frame) action_queue; /*! The video mode this bridge is using */ struct ast_bridge_video_mode video_mode; + /*! Bridge flags to tweak behavior */ + struct ast_flags feature_flags; + /*! Allowed bridge technology capabilities when AST_BRIDGE_FLAG_SMART enabled. */ + uint32_t allowed_capabilities; + /*! Number of channels participating in the bridge */ + unsigned int num_channels; + /*! Number of active channels in the bridge. */ + unsigned int num_active; + /*! + * \brief Count of the active temporary requests to inhibit bridge merges. + * Zero if merges are allowed. + * + * \note Temporary as in try again in a moment. + */ + unsigned int inhibit_merge; /*! The internal sample rate this bridge is mixed at when multiple channels are being mixed. * If this value is 0, the bridge technology may auto adjust the internal mixing rate. */ unsigned int internal_sample_rate; @@ -221,36 +428,83 @@ struct ast_bridge { * for bridge technologies that mix audio. When set to 0, the bridge tech must choose a * default interval for itself. */ unsigned int internal_mixing_interval; - /*! Bit to indicate that the bridge thread is waiting on channels in the bridge array */ - unsigned int waiting:1; - /*! Bit to indicate the bridge thread should stop */ - unsigned int stop:1; - /*! Bit to indicate the bridge thread should refresh itself */ - unsigned int refresh:1; - /*! Bridge flags to tweak behavior */ - struct ast_flags feature_flags; - /*! Bridge technology that is handling the bridge */ - struct ast_bridge_technology *technology; - /*! Private information unique to the bridge technology */ - void *bridge_pvt; - /*! Thread running the bridge */ - pthread_t thread; - /*! Enabled features information */ - struct ast_bridge_features features; - /*! Array of channels that the bridge thread is currently handling */ - struct ast_channel **array; - /*! Number of channels in the above array */ - size_t array_num; - /*! Number of channels the array can handle */ - size_t array_size; - /*! Call ID associated with the bridge */ - struct ast_callid *callid; - /*! Linked list of channels participating in the bridge */ - AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels; - }; + /*! TRUE if the bridge was reconfigured. */ + unsigned int reconfigured:1; + /*! TRUE if the bridge has been dissolved. Any channel that now tries to join is immediately ejected. */ + unsigned int dissolved:1; +}; + +/*! + * \brief Register the new bridge with the system. + * \since 12.0.0 + * + * \param bridge What to register. (Tolerates a NULL pointer) + * + * \code + * struct ast_bridge *ast_bridge_basic_new(uint32_t capabilities, int flags, uint32 dtmf_features) + * { + * void *bridge; + * + * bridge = ast_bridge_alloc(sizeof(struct ast_bridge_basic), &ast_bridge_basic_v_table); + * bridge = ast_bridge_base_init(bridge, capabilities, flags); + * bridge = ast_bridge_basic_init(bridge, dtmf_features); + * bridge = ast_bridge_register(bridge); + * return bridge; + * } + * \endcode + * + * \note This must be done after a bridge constructor has + * completed setting up the new bridge but before it returns. + * + * \note After a bridge is registered, ast_bridge_destroy() must + * eventually be called to get rid of the bridge. + * + * \retval bridge on success. + * \retval NULL on error. + */ +struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge); + +/*! + * \internal + * \brief Allocate the bridge class object memory. + * \since 12.0.0 + * + * \param size Size of the bridge class structure to allocate. + * \param v_table Bridge class virtual method table. + * + * \retval bridge on success. + * \retval NULL on error. + */ +struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table); + +/*! \brief Bridge base class virtual method table. */ +extern struct ast_bridge_methods ast_bridge_base_v_table; /*! - * \brief Create a new bridge + * \brief Initialize the base class of the bridge. + * + * \param self Bridge to operate upon. (Tolerates a NULL pointer) + * \param capabilities The capabilities that we require to be used on the bridge + * \param flags Flags that will alter the behavior of the bridge + * + * \retval self on success + * \retval NULL on failure, self is already destroyed + * + * Example usage: + * + * \code + * struct ast_bridge *bridge; + * bridge = ast_bridge_alloc(sizeof(*bridge), &ast_bridge_base_v_table); + * bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE_HANGUP); + * \endcode + * + * This creates a no frills two party bridge that will be + * destroyed once one of the channels hangs up. + */ +struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags); + +/*! + * \brief Create a new base class bridge * * \param capabilities The capabilities that we require to be used on the bridge * \param flags Flags that will alter the behavior of the bridge @@ -262,13 +516,27 @@ struct ast_bridge { * * \code * struct ast_bridge *bridge; - * bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE); + * bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE_HANGUP); * \endcode * - * This creates a simple two party bridge that will be destroyed once one of - * the channels hangs up. + * This creates a no frills two party bridge that will be + * destroyed once one of the channels hangs up. */ -struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags); +struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags); + +/*! + * \brief Try locking the bridge. + * + * \param bridge Bridge to try locking + * + * \retval 0 on success. + * \retval non-zero on error. + */ +#define ast_bridge_trylock(bridge) _ast_bridge_trylock(bridge, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge) +static inline int _ast_bridge_trylock(struct ast_bridge *bridge, const char *file, const char *function, int line, const char *var) +{ + return __ao2_trylock(bridge, AO2_LOCK_REQ_MUTEX, file, function, line, var); +} /*! * \brief Lock the bridge. @@ -296,24 +564,18 @@ static inline void _ast_bridge_unlock(struct ast_bridge *bridge, const char *fil __ao2_unlock(bridge, file, function, line, var); } -/*! - * \brief See if it is possible to create a bridge - * - * \param capabilities The capabilities that the bridge will use - * - * \retval 1 if possible - * \retval 0 if not possible - * - * Example usage: - * - * \code - * int possible = ast_bridge_check(AST_BRIDGE_CAPABILITY_1TO1MIX); - * \endcode - * - * This sees if it is possible to create a bridge capable of bridging two channels - * together. - */ -int ast_bridge_check(uint32_t capabilities); +/*! \brief Lock two bridges. */ +#define ast_bridge_lock_both(bridge1, bridge2) \ + do { \ + for (;;) { \ + ast_bridge_lock(bridge1); \ + if (!ast_bridge_trylock(bridge2)) { \ + break; \ + } \ + ast_bridge_unlock(bridge1); \ + sched_yield(); \ + } \ + } while (0) /*! * \brief Destroy a bridge @@ -329,10 +591,20 @@ int ast_bridge_check(uint32_t capabilities); * ast_bridge_destroy(bridge); * \endcode * - * This destroys a bridge that was previously created using ast_bridge_new. + * This destroys a bridge that was previously created. */ int ast_bridge_destroy(struct ast_bridge *bridge); +/*! + * \brief Notify bridging that this channel was just masqueraded. + * \since 12.0.0 + * + * \param chan Channel just involved in a masquerade + * + * \return Nothing + */ +void ast_bridge_notify_masquerade(struct ast_channel *chan); + /*! * \brief Join (blocking) a channel to a bridge * @@ -341,13 +613,17 @@ int ast_bridge_destroy(struct ast_bridge *bridge); * \param swap Channel to swap out if swapping * \param features Bridge features structure * \param tech_args Optional Bridging tech optimization parameters for this channel. + * \param pass_reference TRUE if the bridge reference is being passed by the caller. + * + * \note Absolutely _NO_ locks should be held before calling + * this function since it blocks. * * \retval state that channel exited the bridge with * * Example usage: * * \code - * ast_bridge_join(bridge, chan, NULL, NULL); + * ast_bridge_join(bridge, chan, NULL, NULL, NULL, 0); * \endcode * * This adds a channel pointed to by the chan pointer to the bridge pointed to by @@ -365,16 +641,23 @@ enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, - struct ast_bridge_tech_optimizations *tech_args); + struct ast_bridge_tech_optimizations *tech_args, + int pass_reference); /*! * \brief Impart (non-blocking) a channel onto a bridge * * \param bridge Bridge to impart on * \param chan Channel to impart - * \param swap Channel to swap out if swapping - * \param features Bridge features structure - * \param allow_hangup Indicates if the bridge thread should manage hanging up of the channel or not. + * \param swap Channel to swap out if swapping. NULL if not swapping. + * \param features Bridge features structure. + * \param independent TRUE if caller does not want to reclaim the channel using ast_bridge_depart(). + * + * \note The features parameter must be NULL or obtained by + * ast_bridge_features_new(). You must not dereference features + * after calling even if the call fails. + * + * \note chan is locked by this function. * * \retval 0 on success * \retval -1 on failure @@ -385,42 +668,60 @@ enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, * ast_bridge_impart(bridge, chan, NULL, NULL, 0); * \endcode * - * This adds a channel pointed to by the chan pointer to the bridge pointed to by - * the bridge pointer. This function will return immediately and will not wait - * until the channel is no longer part of the bridge. - * - * If this channel will be replacing another channel the other channel can be specified - * in the swap parameter. The other channel will be thrown out of the bridge in an - * atomic fashion. - * - * If channel specific features are enabled a pointer to the features structure - * can be specified in the features parameter. + * \details + * This adds a channel pointed to by the chan pointer to the + * bridge pointed to by the bridge pointer. This function will + * return immediately and will not wait until the channel is no + * longer part of the bridge. + * + * If this channel will be replacing another channel the other + * channel can be specified in the swap parameter. The other + * channel will be thrown out of the bridge in an atomic + * fashion. + * + * If channel specific features are enabled, a pointer to the + * features structure can be specified in the features + * parameter. + * + * \note If you impart a channel as not independent you MUST + * ast_bridge_depart() the channel if this call succeeds. The + * bridge channel thread is created join-able. The implication + * is that the channel is special and will not behave like a + * normal channel. + * + * \note If you impart a channel as independent you must not + * ast_bridge_depart() the channel. The bridge channel thread + * is created non-join-able. The channel must be treated as if + * it were placed into the bridge by ast_bridge_join(). + * Channels placed into a bridge by ast_bridge_join() are + * removed by a third party using ast_bridge_remove(). */ -int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup); +int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent); /*! * \brief Depart a channel from a bridge * - * \param bridge Bridge to depart from * \param chan Channel to depart * + * \note chan is locked by this function. + * * \retval 0 on success * \retval -1 on failure * * Example usage: * * \code - * ast_bridge_depart(bridge, chan); + * ast_bridge_depart(chan); * \endcode * - * This removes the channel pointed to by the chan pointer from the bridge - * pointed to by the bridge pointer and gives control to the calling thread. + * This removes the channel pointed to by the chan pointer from any bridge + * it may be in and gives control to the calling thread. * This does not hang up the channel. * * \note This API call can only be used on channels that were added to the bridge - * using the ast_bridge_impart API call. + * using the ast_bridge_impart API call with the independent flag FALSE. */ -int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan); +int ast_bridge_depart(struct ast_channel *chan); /*! * \brief Remove a channel from a bridge @@ -449,8 +750,14 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan); /*! * \brief Merge two bridges together * - * \param bridge0 First bridge - * \param bridge1 Second bridge + * \param dst_bridge Destination bridge of merge. + * \param src_bridge Source bridge of merge. + * \param merge_best_direction TRUE if don't care about which bridge merges into the other. + * \param kick_me Array of channels to kick from the bridges. + * \param num_kick Number of channels in the kick_me array. + * + * \note Absolutely _NO_ bridge or channel locks should be held + * before calling this function. * * \retval 0 on success * \retval -1 on failure @@ -458,16 +765,57 @@ int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan); * Example usage: * * \code - * ast_bridge_merge(bridge0, bridge1); + * ast_bridge_merge(dst_bridge, src_bridge, 0, NULL, 0); * \endcode * - * This merges the bridge pointed to by bridge1 with the bridge pointed to by bridge0. - * In reality all of the channels in bridge1 are simply moved to bridge0. + * This moves the channels in src_bridge into the bridge pointed + * to by dst_bridge. + */ +int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick); + +/*! + * \brief Move a channel from one bridge to another. + * \since 12.0.0 + * + * \param dst_bridge Destination bridge of bridge channel move. + * \param src_bridge Source bridge of bridge channel move. + * \param chan Channel to move. + * \param swap Channel to replace in dst_bridge. + * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge. + * + * \note Absolutely _NO_ bridge or channel locks should be held + * before calling this function. * - * \note The second bridge specified is not destroyed when this operation is - * completed. + * \retval 0 on success. + * \retval -1 on failure. */ -int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1); +int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery); + +/*! + * \brief Adjust the bridge merge inhibit request count. + * \since 12.0.0 + * + * \param bridge What to operate on. + * \param request Inhibit request increment. + * (Positive to add requests. Negative to remove requests.) + * + * \return Nothing + */ +void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request); + +/*! + * \brief Adjust the bridge_channel's bridge merge inhibit request count. + * \since 12.0.0 + * + * \param bridge_channel What to operate on. + * \param request Inhibit request increment. + * (Positive to add requests. Negative to remove requests.) + * + * \note This API call is meant for internal bridging operations. + * + * \retval bridge adjusted merge inhibit with reference count. + */ +struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request); /*! * \brief Suspend a channel temporarily from a bridge @@ -517,7 +865,94 @@ int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan); int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan); /*! - * \brief Change the state of a bridged channel + * \brief Check and optimize out the unreal channels between bridges. + * \since 12.0.0 + * + * \param chan Unreal channel writing a frame into the channel driver. + * \param peer Other unreal channel in the pair. + * + * \note It is assumed that chan is already locked. + * + * \retval 0 if unreal channels were not optimized out. + * \retval non-zero if unreal channels were optimized out. + */ +int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer); + +/*! + * \brief Try locking the bridge_channel. + * + * \param bridge_channel What to try locking + * + * \retval 0 on success. + * \retval non-zero on error. + */ +#define ast_bridge_channel_trylock(bridge_channel) _ast_bridge_channel_trylock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel) +static inline int _ast_bridge_channel_trylock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var) +{ + return __ao2_trylock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var); +} + +/*! + * \brief Lock the bridge_channel. + * + * \param bridge_channel What to lock + * + * \return Nothing + */ +#define ast_bridge_channel_lock(bridge_channel) _ast_bridge_channel_lock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel) +static inline void _ast_bridge_channel_lock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var) +{ + __ao2_lock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var); +} + +/*! + * \brief Unlock the bridge_channel. + * + * \param bridge_channel What to unlock + * + * \return Nothing + */ +#define ast_bridge_channel_unlock(bridge_channel) _ast_bridge_channel_unlock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel) +static inline void _ast_bridge_channel_unlock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var) +{ + __ao2_unlock(bridge_channel, file, function, line, var); +} + +/*! + * \brief Lock the bridge associated with the bridge channel. + * \since 12.0.0 + * + * \param bridge_channel Channel that wants to lock the bridge. + * + * \details + * This is an upstream lock operation. The defined locking + * order is bridge then bridge_channel. + * + * \note On entry, neither the bridge nor bridge_channel is locked. + * + * \note The bridge_channel->bridge pointer changes because of a + * bridge-merge/channel-move operation between bridges. + * + * \return Nothing + */ +void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel); + +/*! + * \brief Set bridge channel state to leave bridge (if not leaving already) with no lock. + * + * \param bridge_channel Channel to change the state on + * \param new_state The new state to place the channel into + * + * \note This API call is only meant to be used within the + * bridging module and hook callbacks to request the channel + * exit the bridge. + * + * \note This function assumes the bridge_channel is locked. + */ +void ast_bridge_change_state_nolock(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state); + +/*! + * \brief Set bridge channel state to leave bridge (if not leaving already). * * \param bridge_channel Channel to change the state on * \param new_state The new state to place the channel into @@ -525,17 +960,265 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan); * Example usage: * * \code - * ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT); + * ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); * \endcode * - * This places the channel pointed to by bridge_channel into the state - * AST_BRIDGE_CHANNEL_STATE_WAIT. + * This places the channel pointed to by bridge_channel into the + * state AST_BRIDGE_CHANNEL_STATE_HANGUP if it was + * AST_BRIDGE_CHANNEL_STATE_WAIT before. * - * \note This API call is only meant to be used in feature hook callbacks to - * make sure the channel either hangs up or returns to the bridge. + * \note This API call is only meant to be used within the + * bridging module and hook callbacks to request the channel + * exit the bridge. */ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state); +/*! + * \brief Put an action onto the specified bridge. + * \since 12.0.0 + * + * \param bridge What to queue the action on. + * \param action What to do. + * + * \retval 0 on success. + * \retval -1 on error. + * + * \note This API call is meant for internal bridging operations. + * \note BUGBUG This may get moved. + */ +int ast_bridge_queue_action(struct ast_bridge *bridge, struct ast_frame *action); + +/*! + * \brief Write a frame to the specified bridge_channel. + * \since 12.0.0 + * + * \param bridge_channel Channel to queue the frame. + * \param fr Frame to write. + * + * \retval 0 on success. + * \retval -1 on error. + * + * \note This API call is meant for internal bridging operations. + * \note BUGBUG This may get moved. + */ +int ast_bridge_channel_queue_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr); + +/*! + * \brief Used to queue an action frame onto a bridge channel and write an action frame into a bridge. + * \since 12.0.0 + * + * \param bridge_channel Which channel work with. + * \param action Type of bridge action frame. + * \param data Frame payload data to pass. + * \param datalen Frame payload data length to pass. + * + * \return Nothing + */ +typedef void (*ast_bridge_channel_post_action_data)(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen); + +/*! + * \brief Queue an action frame onto the bridge channel with data. + * \since 12.0.0 + * + * \param bridge_channel Which channel to queue the frame onto. + * \param action Type of bridge action frame. + * \param data Frame payload data to pass. + * \param datalen Frame payload data length to pass. + * + * \return Nothing + */ +void ast_bridge_channel_queue_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen); + +/*! + * \brief Write an action frame into the bridge with data. + * \since 12.0.0 + * + * \param bridge_channel Which channel is putting the frame into the bridge. + * \param action Type of bridge action frame. + * \param data Frame payload data to pass. + * \param datalen Frame payload data length to pass. + * + * \return Nothing + */ +void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen); + +/*! + * \brief Queue a control frame onto the bridge channel with data. + * \since 12.0.0 + * + * \param bridge_channel Which channel to queue the frame onto. + * \param control Type of control frame. + * \param data Frame payload data to pass. + * \param datalen Frame payload data length to pass. + * + * \return Nothing + */ +void ast_bridge_channel_queue_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen); + +/*! + * \brief Write a control frame into the bridge with data. + * \since 12.0.0 + * + * \param bridge_channel Which channel is putting the frame into the bridge. + * \param control Type of control frame. + * \param data Frame payload data to pass. + * \param datalen Frame payload data length to pass. + * + * \return Nothing + */ +void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen); + +/*! + * \brief Run an application on the bridge channel. + * \since 12.0.0 + * + * \param bridge_channel Which channel to run the application on. + * \param app_name Dialplan application name. + * \param app_args Arguments for the application. (NULL tolerant) + * \param moh_class MOH class to request bridge peers to hear while application is running. + * NULL if no MOH. + * Empty if default MOH class. + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class); + +/*! + * \brief Write a bridge action run application frame into the bridge. + * \since 12.0.0 + * + * \param bridge_channel Which channel is putting the frame into the bridge + * \param app_name Dialplan application name. + * \param app_args Arguments for the application. (NULL or empty for no arguments) + * \param moh_class MOH class to request bridge peers to hear while application is running. + * NULL if no MOH. + * Empty if default MOH class. + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class); + +/*! + * \brief Queue a bridge action run application frame onto the bridge channel. + * \since 12.0.0 + * + * \param bridge_channel Which channel to put the frame onto + * \param app_name Dialplan application name. + * \param app_args Arguments for the application. (NULL or empty for no arguments) + * \param moh_class MOH class to request bridge peers to hear while application is running. + * NULL if no MOH. + * Empty if default MOH class. + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class); + +/*! + * \brief Custom interpretation of the playfile name. + * + * \param bridge_channel Which channel to play the file on + * \param playfile Sound filename to play. + * + * \return Nothing + */ +typedef void (*ast_bridge_custom_play_fn)(struct ast_bridge_channel *bridge_channel, const char *playfile); + +/*! + * \brief Play a file on the bridge channel. + * \since 12.0.0 + * + * \param bridge_channel Which channel to play the file on + * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play) + * \param playfile Sound filename to play. + * \param moh_class MOH class to request bridge peers to hear while file is played. + * NULL if no MOH. + * Empty if default MOH class. + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class); + +/*! + * \brief Have a bridge channel park a channel in the bridge + * \since 12.0.0 + * + * \param bridge_channel Bridge channel performing the parking + * \param parkee_uuid Unique id of the channel we want to park + * \param parker_uuid Unique id of the channel parking the call + * \param app_data string indicating data used for park application (NULL allowed) + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, + const char *parker_uuid, const char *app_data); + +/*! + * \brief Write a bridge action play file frame into the bridge. + * \since 12.0.0 + * + * \param bridge_channel Which channel is putting the frame into the bridge + * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play) + * \param playfile Sound filename to play. + * \param moh_class MOH class to request bridge peers to hear while file is played. + * NULL if no MOH. + * Empty if default MOH class. + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class); + +/*! + * \brief Queue a bridge action play file frame onto the bridge channel. + * \since 12.0.0 + * + * \param bridge_channel Which channel to put the frame onto. + * \param custom_play Call this function to play the playfile. (NULL if normal sound file to play) + * \param playfile Sound filename to play. + * \param moh_class MOH class to request bridge peers to hear while file is played. + * NULL if no MOH. + * Empty if default MOH class. + * + * \note This is intended to be called by bridge hooks. + * + * \return Nothing + */ +void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class); + +/*! + * \brief Restore the formats of a bridge channel's channel to how they were before bridge_channel_join + * \since 12.0.0 + * + * \param bridge_channel Channel to restore + */ +void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channel); + +/*! + * \brief Get the peer bridge channel of a two party bridge. + * \since 12.0.0 + * + * \param bridge_channel What to get the peer of. + * + * \note On entry, bridge_channel->bridge is already locked. + * + * \note This is an internal bridge function. + * + * \retval peer on success. + * \retval NULL no peer channel. + */ +struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel); + /*! * \brief Adjust the internal mixing sample rate of a bridge * used during multimix mode. @@ -594,8 +1277,297 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan) */ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan); +enum ast_transfer_result { + /*! The transfer completed successfully */ + AST_BRIDGE_TRANSFER_SUCCESS, + /*! A bridge involved does not permit transferring */ + AST_BRIDGE_TRANSFER_NOT_PERMITTED, + /*! The current bridge setup makes transferring an invalid operation */ + AST_BRIDGE_TRANSFER_INVALID, + /*! The transfer operation failed for a miscellaneous reason */ + AST_BRIDGE_TRANSFER_FAIL, +}; + +typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data); + +/*! + * \brief Blind transfer target to the extension and context provided + * + * The channel given is bridged to one or multiple channels. Depending on + * the bridge and the number of participants, the entire bridge could be + * transferred to the given destination, or a single channel may be redirected. + * + * Callers may also provide a callback to be called on the channel that will + * be running dialplan. The user data passed into ast_bridge_transfer_blind + * will be given as the argument to the callback to be interpreted as desired. + * This callback is guaranteed to be called in the same thread as + * ast_bridge_transfer_blind() and before ast_bridge_transfer_blind() returns. + * + * \note Absolutely _NO_ channel locks should be held before + * calling this function. + * + * \param transferer The channel performing the blind transfer + * \param exten The dialplan extension to send the call to + * \param context The dialplan context to send the call to + * \param new_channel_cb A callback to be called on the channel that will + * be executing dialplan + * \param user_data Argument for new_channel_cb + * \return The success or failure result of the blind transfer + */ +enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer, + const char *exten, const char *context, + transfer_channel_cb new_channel_cb, void *user_data); + +/*! + * \brief Attended transfer + * + * The two channels are both transferer channels. The first is the channel + * that is bridged to the transferee (or if unbridged, the 'first' call of + * the transfer). The second is the channel that is bridged to the transfer + * target (or if unbridged, the 'second' call of the transfer). + * + * Like with a blind transfer, a frame hook can be provided to monitor the + * resulting call after the transfer completes. If the transfer fails, the + * hook will not be attached to any call. + * + * \note Absolutely _NO_ channel locks should be held before + * calling this function. + * + * \param to_transferee Transferer channel on initial call (presumably bridged to transferee) + * \param to_transfer_target Transferer channel on consultation call (presumably bridged to transfer target) + * \param hook A frame hook to attach to the resultant call + * \return The success or failure of the attended transfer + */ +enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee, + struct ast_channel *to_transfer_target, struct ast_framehook *hook); +/*! + * \brief Set channel to goto specific location after the bridge. + * \since 12.0.0 + * + * \param chan Channel to setup after bridge goto location. + * \param context Context to goto after bridge. + * \param exten Exten to goto after bridge. + * \param priority Priority to goto after bridge. + * + * \note chan is locked by this function. + * + * \details Add a channel datastore to setup the goto location + * when the channel leaves the bridge and run a PBX from there. + * + * \return Nothing + */ +void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority); + +/*! + * \brief Set channel to run the h exten after the bridge. + * \since 12.0.0 + * + * \param chan Channel to setup after bridge goto location. + * \param context Context to goto after bridge. + * + * \note chan is locked by this function. + * + * \details Add a channel datastore to setup the goto location + * when the channel leaves the bridge and run a PBX from there. + * + * \return Nothing + */ +void ast_after_bridge_set_h(struct ast_channel *chan, const char *context); + +/*! + * \brief Set channel to go on in the dialplan after the bridge. + * \since 12.0.0 + * + * \param chan Channel to setup after bridge goto location. + * \param context Current context of the caller channel. + * \param exten Current exten of the caller channel. + * \param priority Current priority of the caller channel + * \param parseable_goto User specified goto string from dialplan. + * + * \note chan is locked by this function. + * + * \details Add a channel datastore to setup the goto location + * when the channel leaves the bridge and run a PBX from there. + * + * If parseable_goto then use the given context/exten/priority + * as the relative position for the parseable_goto. + * Else goto the given context/exten/priority+1. + * + * \return Nothing + */ +void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto); + +/*! + * \brief Setup any after bridge goto location to begin execution. + * \since 12.0.0 + * + * \param chan Channel to setup after bridge goto location. + * + * \note chan is locked by this function. + * + * \details Pull off any after bridge goto location datastore and + * setup for dialplan execution there. + * + * \retval 0 on success. The goto location is set for a PBX to run it. + * \retval non-zero on error or no goto location. + * + * \note If the after bridge goto is set to run an h exten it is + * run here immediately. + */ +int ast_after_bridge_goto_setup(struct ast_channel *chan); + +/*! + * \brief Run a PBX on any after bridge goto location. + * \since 12.0.0 + * + * \param chan Channel to execute after bridge goto location. + * + * \note chan is locked by this function. + * + * \details Pull off any after bridge goto location datastore + * and run a PBX at that location. + * + * \note On return, the chan pointer is no longer valid because + * the channel has hung up. + * + * \return Nothing + */ +void ast_after_bridge_goto_run(struct ast_channel *chan); + +/*! + * \brief Discard channel after bridge goto location. + * \since 12.0.0 + * + * \param chan Channel to discard after bridge goto location. + * + * \note chan is locked by this function. + * + * \return Nothing + */ +void ast_after_bridge_goto_discard(struct ast_channel *chan); + +/*! Reason the the after bridge callback will not be called. */ +enum ast_after_bridge_cb_reason { + /*! The datastore is being destroyed. Likely due to hangup. */ + AST_AFTER_BRIDGE_CB_REASON_DESTROY, + /*! Something else replaced the callback with another. */ + AST_AFTER_BRIDGE_CB_REASON_REPLACED, + /*! The callback was removed because of a masquerade. (fixup) */ + AST_AFTER_BRIDGE_CB_REASON_MASQUERADE, + /*! The channel departed bridge. */ + AST_AFTER_BRIDGE_CB_REASON_DEPART, + /*! Was explicitly removed by external code. */ + AST_AFTER_BRIDGE_CB_REASON_REMOVED, +}; + +/*! + * \brief After bridge callback failed. + * \since 12.0.0 + * + * \param reason Reason callback is failing. + * \param data Extra data what setup the callback wanted to pass. + * + * \return Nothing + */ +typedef void (*ast_after_bridge_cb_failed)(enum ast_after_bridge_cb_reason reason, void *data); + +/*! + * \brief After bridge callback function. + * \since 12.0.0 + * + * \param chan Channel just leaving bridging system. + * \param data Extra data what setup the callback wanted to pass. + * + * \return Nothing + */ +typedef void (*ast_after_bridge_cb)(struct ast_channel *chan, void *data); + +/*! + * \brief Discard channel after bridge callback. + * \since 12.0.0 + * + * \param chan Channel to discard after bridge callback. + * \param reason Why are we doing this. + * + * \note chan is locked by this function. + * + * \return Nothing + */ +void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason); + +/*! + * \brief Setup an after bridge callback for when the channel leaves the bridging system. + * \since 12.0.0 + * + * \param chan Channel to setup an after bridge callback on. + * \param callback Function to call when the channel leaves the bridging system. + * \param failed Function to call when it will not be calling the callback. + * \param data Extra data to pass with the callback. + * + * \note chan is locked by this function. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data); + +/*! + * \brief Get a container of all channels in the bridge + * \since 12.0.0 + * + * \param bridge The bridge which is already locked. + * + * \retval NULL Failed to create container + * \retval non-NULL Container of channels in the bridge + */ +struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge); + +/*! + * \brief Get a container of all channels in the bridge + * \since 12.0.0 + * + * \param bridge The bridge + * + * \note The returned container is a snapshot of channels in the + * bridge when called. + * + * \retval NULL Failed to create container + * \retval non-NULL Container of channels in the bridge + */ +struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge); + +/*! + * \brief Get the channel's bridge peer only if the bridge is two-party. + * \since 12.0.0 + * + * \param bridge The bridge which is already locked. + * \param chan Channel desiring the bridge peer channel. + * + * \note The returned peer channel is the current peer in the + * bridge when called. + * + * \retval NULL Channel not in a bridge or the bridge is not two-party. + * \retval non-NULL Reffed peer channel at time of calling. + */ +struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan); + +/*! + * \brief Get the channel's bridge peer only if the bridge is two-party. + * \since 12.0.0 + * + * \param bridge The bridge + * \param chan Channel desiring the bridge peer channel. + * + * \note The returned peer channel is the current peer in the + * bridge when called. + * + * \retval NULL Channel not in a bridge or the bridge is not two-party. + * \retval non-NULL Reffed peer channel at time of calling. + */ +struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan); + #if defined(__cplusplus) || defined(c_plusplus) } #endif -#endif /* _ASTERISK_BRIDGING_H */ +#endif /* _ASTERISK_BRIDGING_H */ diff --git a/include/asterisk/bridging_basic.h b/include/asterisk/bridging_basic.h new file mode 100644 index 0000000000000000000000000000000000000000..cc42354dc9bc2a5e37bdf13b1c433b24e1dded34 --- /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 1323a6da98f1167c4fe689889e5dfd3e2c5759c1..bb792a81556c337af963cf5d00e6038202263dea 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 0000000000000000000000000000000000000000..3acb67faefb79ff1094a4ee1d5f04733c725d940 --- /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 d09c4bec45384ad28ce4ef03bf46f84efe60aa53..2bb2170f0146b2799bf52a33293a6aedaa8bb01e 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; @@ -125,27 +165,6 @@ int __ast_bridge_technology_register(struct ast_bridge_technology *technology, s */ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology); -/*! - * \brief Feed notification that a frame is waiting on a channel into the bridging core - * - * \param bridge The bridge that the notification should influence - * \param bridge_channel Bridge channel the notification was received on (if known) - * \param chan Channel the notification was received on (if known) - * \param outfd File descriptor that the notification was received on (if known) - * - * Example usage: - * - * \code - * ast_bridge_handle_trip(bridge, NULL, chan, -1); - * \endcode - * - * This tells the bridging core that a frame has been received on - * the channel pointed to by chan and that it should be read and handled. - * - * \note This should only be used by bridging technologies. - */ -void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd); - /*! * \brief Lets the bridging indicate when a bridge channel has stopped or started talking. * @@ -155,12 +174,11 @@ void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel * application, this function has been created to allow the bridging technology to communicate * that information with the bridging core. * - * \param bridge The bridge that the channel is a part of. * \param bridge_channel The bridge channel that has either started or stopped talking. * \param started_talking set to 1 when this indicates the channel has started talking set to 0 * when this indicates the channel has stopped talking. */ -void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking); +void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking); /*! * \brief Suspend a bridge technology from consideration diff --git a/include/asterisk/ccss.h b/include/asterisk/ccss.h index cf2f2199669b0f7dd2e5abaffcf76ee798fa51de..d8101cddf93a39d9a05fa6f54a1d2ca4fc29dba7 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 939c8db513dc766a8be8d272570a5d4be24569a4..91373cfbda3250196fb4de41a164749d674797e2 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 64d8d5089326ad55a78d8a104ffd1329567a6ec7..739d83130032bacd53fa10b836aae3382f3a6786 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 0000000000000000000000000000000000000000..693c93b4679ea936a4e300560269c157e3cd2f82 --- /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 0000000000000000000000000000000000000000..7cb68f162307607d84df9f4d7c8c64ba575a9e62 --- /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 abb0c2e28d4b853cde6496e712ffdab78f492a25..bedc3a25d9c022135c5954aca0b6a79e5a4560b8 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 52993b55c9785cff426ab1d0dbda684786b86b05..10d525ca731541fa2b6d1a25a676fe2dd62b5ba4 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 e0160c6b576accd66635cb32828816fe83f455db..4e9b8d14a7c2074059a0e35575ccf5cad00217c5 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 0000000000000000000000000000000000000000..176eddb04202820f3f0ad49aac505c9bc2a1fb87 --- /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 c8a144b73f8578aefea5183b60d9290ea9ae4009..e2567f50816116d40b27d447971502ed4231f2fa 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -1540,24 +1540,6 @@ int ast_rtp_instance_fd(struct ast_rtp_instance *instance, int rtcp); */ struct ast_rtp_glue *ast_rtp_instance_get_glue(const char *type); -/*! - * \brief Bridge two channels that use RTP instances - * - * \param c0 First channel part of the bridge - * \param c1 Second channel part of the bridge - * \param flags Bridging flags - * \param fo If a frame needs to be passed up it is stored here - * \param rc Channel that passed the above frame up - * \param timeoutms How long the channels should be bridged for - * - * \retval Bridge result - * - * \note This should only be used by channel drivers in their technology declaration. - * - * \since 1.8 - */ -enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms); - /*! * \brief Get the other RTP instance that an instance is bridged to * @@ -1578,6 +1560,16 @@ enum ast_bridge_result ast_rtp_instance_bridge(struct ast_channel *c0, struct as */ struct ast_rtp_instance *ast_rtp_instance_get_bridged(struct ast_rtp_instance *instance); +/*! + * \brief Set the other RTP instance that an instance is bridged to + * + * \param instance The RTP instance that we want to set the bridged value on + * \param bridged The RTP instance they are bridged to + * + * \since 12 + */ +void ast_rtp_instance_set_bridged(struct ast_rtp_instance *instance, struct ast_rtp_instance *bridged); + /*! * \brief Make two channels compatible for early bridging * diff --git a/include/asterisk/stasis_bridging.h b/include/asterisk/stasis_bridging.h new file mode 100644 index 0000000000000000000000000000000000000000..1b547a7d537af233a78a39dd23110cc0ef79eedb --- /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 88a9b8e9171dd4e443ca7af292a2eec37ca63d0c..6e20b86cb93271ad0b5a1a60bdb27dcd13ba4eb6 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 933aae63d3cd8e35338a4ff6d708d7cbad68683a..d8062d3b1d2df22cd30042c04dbe8fbef867d702 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 875e8503c396926b3e233f369b58d24bc215c6c5..f332dfab21f5c24ef35c7e45d6e269514b42a738 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,1513 +401,5443 @@ static struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, /*! * \internal - * \brief Force out all channels that are not already going out of the bridge. + * \brief Dissolve the bridge. * \since 12.0.0 * * \param bridge Bridge to eject all channels * + * \details + * Force out all channels that are not already going out of the + * bridge. Any new channels joining will leave immediately. + * * \note On entry, bridge is already locked. * * \return Nothing */ -static void bridge_force_out_all(struct ast_bridge *bridge) +static void bridge_dissolve(struct ast_bridge *bridge) { struct ast_bridge_channel *bridge_channel; + struct ast_frame action = { + .frametype = AST_FRAME_BRIDGE_ACTION, + .subclass.integer = AST_BRIDGE_ACTION_DEFERRED_DISSOLVING, + }; - AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { - ao2_lock(bridge_channel); - switch (bridge_channel->state) { - case AST_BRIDGE_CHANNEL_STATE_END: - case AST_BRIDGE_CHANNEL_STATE_HANGUP: - case AST_BRIDGE_CHANNEL_STATE_DEPART: - break; - default: - ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); - break; - } - ao2_unlock(bridge_channel); + if (bridge->dissolved) { + return; } -} + bridge->dissolved = 1; -/*! \brief Internal function to see whether a bridge should dissolve, and if so do it */ -static void bridge_check_dissolve(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) -{ - if (!ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE) - && (!bridge_channel->features - || !bridge_channel->features->usable - || !ast_test_flag(&bridge_channel->features->feature_flags, AST_BRIDGE_FLAG_DISSOLVE))) { - return; + ast_debug(1, "Bridge %s: dissolving bridge\n", bridge->uniqueid); + +/* BUGBUG need a cause code on the bridge for the later ejected channels. */ + AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); } - ast_debug(1, "Dissolving bridge %p\n", bridge); - bridge_force_out_all(bridge); + /* Must defer dissolving bridge because it is already locked. */ + ast_bridge_queue_action(bridge, &action); } -/*! \brief Internal function to handle DTMF from a channel */ -static struct ast_frame *bridge_handle_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) +/*! + * \internal + * \brief Check if a bridge should dissolve and do it. + * \since 12.0.0 + * + * \param bridge_channel Channel causing the check. + * + * \note On entry, bridge_channel->bridge is already locked. + * + * \return Nothing + */ +static void bridge_dissolve_check(struct ast_bridge_channel *bridge_channel) { - struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features); - struct ast_bridge_features_hook *hook; + struct ast_bridge *bridge = bridge_channel->bridge; - /* If the features structure we grabbed is not usable immediately return the frame */ - if (!features->usable) { - return frame; + if (bridge->dissolved) { + return; } - /* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */ - AST_LIST_TRAVERSE(&features->hooks, hook, entry) { - if (hook->dtmf[0] == frame->subclass.integer) { - ast_frfree(frame); - frame = NULL; - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_FEATURE); - break; - } + if (!bridge->num_channels + && ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY)) { + /* Last channel leaving the bridge turns off the lights. */ + bridge_dissolve(bridge); + return; } - return frame; -} - -/*! \brief Internal function used to determine whether a control frame should be dropped or not */ -static int bridge_drop_control_frame(int subclass) -{ - switch (subclass) { - case AST_CONTROL_ANSWER: - case -1: - return 1; + switch (bridge_channel->state) { + case AST_BRIDGE_CHANNEL_STATE_END: + /* Do we need to dissolve the bridge because this channel hung up? */ + if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_HANGUP) + || (bridge_channel->features->usable + && ast_test_flag(&bridge_channel->features->feature_flags, + AST_BRIDGE_CHANNEL_FLAG_DISSOLVE_HANGUP))) { + bridge_dissolve(bridge); + return; + } + break; default: - return 0; + break; } +/* BUGBUG need to implement AST_BRIDGE_CHANNEL_FLAG_LONELY support here */ } -void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking) +/*! + * \internal + * \brief Pull the bridge channel out of its current bridge. + * \since 12.0.0 + * + * \param bridge_channel Channel to pull. + * + * \note On entry, bridge_channel->bridge is already locked. + * + * \return Nothing + */ +static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel) { - if (started_talking) { - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_START_TALKING); - } else { - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_STOP_TALKING); - } -} + struct ast_bridge *bridge = bridge_channel->bridge; -void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd) -{ - /* If no bridge channel has been provided and the actual channel has been provided find it */ - if (chan && !bridge_channel) { - bridge_channel = find_bridge_channel(bridge, chan); + if (!bridge_channel->in_bridge) { + return; } - - /* If a bridge channel with actual channel is present read a frame and handle it */ - if (chan && bridge_channel) { - struct ast_frame *frame; - - if (bridge->features.mute - || (bridge_channel->features && bridge_channel->features->mute)) { - frame = ast_read_noaudio(chan); - } else { - frame = ast_read(chan); - } - /* This is pretty simple... see if they hung up */ - if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) { - /* Signal the thread that is handling the bridged channel that it should be ended */ - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END); - } else if (frame->frametype == AST_FRAME_CONTROL && bridge_drop_control_frame(frame->subclass.integer)) { - ast_debug(1, "Dropping control frame %d from bridge channel %p\n", - frame->subclass.integer, bridge_channel); - } else if (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) { - int dtmf_passthrough = bridge_channel->features ? - bridge_channel->features->dtmf_passthrough : - bridge->features.dtmf_passthrough; - - if (frame->frametype == AST_FRAME_DTMF_BEGIN) { - frame = bridge_handle_dtmf(bridge, bridge_channel, frame); - } - - if (frame && dtmf_passthrough) { - bridge->technology->write(bridge, bridge_channel, frame); - } - } else { - /* Simply write the frame out to the bridge technology if it still exists */ - bridge->technology->write(bridge, bridge_channel, frame); - } - - if (frame) { - ast_frfree(frame); + bridge_channel->in_bridge = 0; + + ast_debug(1, "Bridge %s: pulling %p(%s)\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan)); + +/* BUGBUG This is where incoming HOLD/UNHOLD memory should write UNHOLD into bridge. (if not local optimizing) */ +/* BUGBUG This is where incoming DTMF begin/end memory should write DTMF end into bridge. (if not local optimizing) */ + if (!bridge_channel->just_joined) { + /* Tell the bridge technology we are leaving so they tear us down */ + ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + bridge->technology->name); + if (bridge->technology->leave) { + bridge->technology->leave(bridge, bridge_channel); } - return; } - /* If a file descriptor actually tripped pass it off to the bridge technology */ - if (outfd > -1 && bridge->technology->fd) { - bridge->technology->fd(bridge, bridge_channel, outfd); - return; + /* Remove channel from the bridge */ + if (!bridge_channel->suspended) { + --bridge->num_active; } + --bridge->num_channels; + AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry); + bridge->v_table->pull(bridge, bridge_channel); - /* If all else fails just poke the bridge */ - if (bridge->technology->poke && bridge_channel) { - bridge->technology->poke(bridge, bridge_channel); - return; - } + ast_bridge_channel_clear_roles(bridge_channel); + + bridge_dissolve_check(bridge_channel); + + bridge->reconfigured = 1; + ast_bridge_publish_leave(bridge, bridge_channel->chan); } -/*! \brief Generic thread loop, TODO: Rethink this/improve it */ -static int generic_thread_loop(struct ast_bridge *bridge) +/*! + * \internal + * \brief Push the bridge channel into its specified bridge. + * \since 12.0.0 + * + * \param bridge_channel Channel to push. + * + * \note On entry, bridge_channel->bridge is already locked. + * + * \retval 0 on success. + * \retval -1 on failure. The channel did not get pushed. + */ +static int bridge_channel_push(struct ast_bridge_channel *bridge_channel) { - while (!bridge->stop && !bridge->refresh && bridge->array_num) { - struct ast_channel *winner; - int to = -1; + struct ast_bridge *bridge = bridge_channel->bridge; + struct ast_bridge_channel *swap; - /* Move channels around for priority reasons if we have more than one channel in our array */ - if (bridge->array_num > 1) { - struct ast_channel *first = bridge->array[0]; - memmove(bridge->array, bridge->array + 1, sizeof(struct ast_channel *) * (bridge->array_num - 1)); - bridge->array[(bridge->array_num - 1)] = first; - } + ast_assert(!bridge_channel->in_bridge); + + swap = find_bridge_channel(bridge, bridge_channel->swap); + bridge_channel->swap = NULL; - /* Wait on the channels */ - bridge->waiting = 1; - ao2_unlock(bridge); - winner = ast_waitfor_n(bridge->array, (int) bridge->array_num, &to); - bridge->waiting = 0; - ao2_lock(bridge); + if (swap) { + ast_debug(1, "Bridge %s: pushing %p(%s) by swapping with %p(%s)\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + swap, ast_channel_name(swap->chan)); + } else { + ast_debug(1, "Bridge %s: pushing %p(%s)\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan)); + } - /* Process whatever they did */ - ast_bridge_handle_trip(bridge, NULL, winner, -1); + /* Add channel to the bridge */ + if (bridge->dissolved + || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT + || (swap && swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT) + || bridge->v_table->push(bridge, bridge_channel, swap) + || ast_bridge_channel_establish_roles(bridge_channel)) { + ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan)); + return -1; + } + bridge_channel->in_bridge = 1; + bridge_channel->just_joined = 1; + AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry); + ++bridge->num_channels; + if (!bridge_channel->suspended) { + ++bridge->num_active; + } + if (swap) { + ast_bridge_change_state(swap, AST_BRIDGE_CHANNEL_STATE_HANGUP); + bridge_channel_pull(swap); } + bridge->reconfigured = 1; + ast_bridge_publish_enter(bridge, bridge_channel->chan); return 0; } -/*! \brief Bridge thread function */ -static void *bridge_thread(void *data) +/*! \brief Internal function to handle DTMF from a channel */ +static struct ast_frame *bridge_handle_dtmf(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) { - struct ast_bridge *bridge = data; - int res = 0; + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook; + char dtmf[2]; - ao2_lock(bridge); - - if (bridge->callid) { - ast_callid_threadassoc_add(bridge->callid); +/* BUGBUG the feature hook matching needs to be done here. Any matching feature hook needs to be queued onto the bridge_channel. Also the feature hook digit timeout needs to be handled. */ +/* BUGBUG the AMI atxfer action just sends DTMF end events to initiate DTMF atxfer and dial the extension. Another reason the DTMF hook matching needs rework. */ + /* See if this DTMF matches the beginnings of any feature hooks, if so we switch to the feature state to either execute the feature or collect more DTMF */ + dtmf[0] = frame->subclass.integer; + dtmf[1] = '\0'; + hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY); + if (hook) { + struct ast_frame action = { + .frametype = AST_FRAME_BRIDGE_ACTION, + .subclass.integer = AST_BRIDGE_ACTION_FEATURE, + }; + + ast_frfree(frame); + frame = NULL; + ast_bridge_channel_queue_frame(bridge_channel, &action); + ao2_ref(hook, -1); } - ast_debug(1, "Started bridge thread for %p\n", bridge); - - /* Loop around until we are told to stop */ - while (!bridge->stop && bridge->array_num && !res) { - /* In case the refresh bit was set simply set it back to off */ - bridge->refresh = 0; - - ast_debug(1, "Launching bridge thread function %p for bridge %p\n", - bridge->technology->thread ? bridge->technology->thread : generic_thread_loop, - bridge); + return frame; +} - /* - * Execute the appropriate thread function. If the technology - * does not provide one we use the generic one. - */ - res = bridge->technology->thread - ? bridge->technology->thread(bridge) - : generic_thread_loop(bridge); +/*! + * \internal + * \brief Handle bridge hangup event. + * \since 12.0.0 + * + * \param bridge_channel Which channel is hanging up. + * + * \return Nothing + */ +static void bridge_handle_hangup(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook; + struct ao2_iterator iter; + + /* Run any hangup hooks. */ + iter = ao2_iterator_init(features->hangup_hooks, 0); + for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) { + int failed; + + failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt); + if (failed) { + ast_debug(1, "Hangup hook %p is being removed from %p(%s)\n", + hook, bridge_channel, ast_channel_name(bridge_channel->chan)); + ao2_unlink(features->hangup_hooks, hook); + } } + ao2_iterator_destroy(&iter); - ast_debug(1, "Ending bridge thread for %p\n", bridge); + /* Default hangup action. */ + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END); +} - /* Indicate the bridge thread is no longer active */ - bridge->thread = AST_PTHREADT_NULL; - ao2_unlock(bridge); +static int bridge_channel_interval_ready(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook; + int ready; - ao2_ref(bridge, -1); + ast_heap_wrlock(features->interval_hooks); + hook = ast_heap_peek(features->interval_hooks, 1); + ready = hook && ast_tvdiff_ms(hook->parms.timer.trip_time, ast_tvnow()) <= 0; + ast_heap_unlock(features->interval_hooks); - return NULL; + return ready; } -/*! \brief Helper function used to find the "best" bridge technology given a specified capabilities */ -static struct ast_bridge_technology *find_best_technology(uint32_t capabilities) +void ast_bridge_notify_talking(struct ast_bridge_channel *bridge_channel, int started_talking) { - struct ast_bridge_technology *current = NULL, *best = NULL; + struct ast_frame action = { + .frametype = AST_FRAME_BRIDGE_ACTION, + .subclass.integer = started_talking + ? AST_BRIDGE_ACTION_TALKING_START : AST_BRIDGE_ACTION_TALKING_STOP, + }; - AST_RWLIST_RDLOCK(&bridge_technologies); - AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) { - if (current->suspended) { - ast_debug(1, "Bridge technology %s is suspended. Skipping.\n", current->name); - continue; - } - if (!(current->capabilities & capabilities)) { - ast_debug(1, "Bridge technology %s does not have the capabilities we need.\n", current->name); - continue; - } - if (best && best->preference < current->preference) { - ast_debug(1, "Bridge technology %s has preference %d while %s has preference %d. Skipping.\n", current->name, current->preference, best->name, best->preference); - continue; - } - best = current; - } + ast_bridge_channel_queue_frame(bridge_channel, &action); +} - if (best) { - /* Increment it's module reference count if present so it does not get unloaded while in use */ - ast_module_ref(best->mod); - ast_debug(1, "Chose bridge technology %s\n", best->name); - } +static void bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) +{ + ast_bridge_channel_lock_bridge(bridge_channel); +/* + * BUGBUG need to implement a deferred write queue for when there is no peer channel in the bridge (yet or it was kicked). + * + * The tech decides if a frame needs to be pushed back for deferral. + * simple_bridge/native_bridge are likely the only techs that will do this. + */ + bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame); + ast_bridge_unlock(bridge_channel->bridge); +} - AST_RWLIST_UNLOCK(&bridge_technologies); +void ast_bridge_channel_write_action_data(struct ast_bridge_channel *bridge_channel, enum ast_bridge_action_type action, const void *data, size_t datalen) +{ + struct ast_frame frame = { + .frametype = AST_FRAME_BRIDGE_ACTION, + .subclass.integer = action, + .datalen = datalen, + .data.ptr = (void *) data, + }; - return best; + bridge_channel_write_frame(bridge_channel, &frame); } -static void destroy_bridge(void *obj) +void ast_bridge_channel_write_control_data(struct ast_bridge_channel *bridge_channel, enum ast_control_frame_type control, const void *data, size_t datalen) { - struct ast_bridge *bridge = obj; + struct ast_frame frame = { + .frametype = AST_FRAME_CONTROL, + .subclass.integer = control, + .datalen = datalen, + .data.ptr = (void *) data, + }; - ast_debug(1, "Actually destroying bridge %p, nobody wants it anymore\n", bridge); + bridge_channel_write_frame(bridge_channel, &frame); +} - /* There should not be any channels left in the bridge. */ - ast_assert(AST_LIST_EMPTY(&bridge->channels)); +static int run_app_helper(struct ast_channel *chan, const char *app_name, const char *app_args) +{ + int res = 0; - /* Pass off the bridge to the technology to destroy if needed */ - if (bridge->technology->destroy) { - ast_debug(1, "Giving bridge technology %s the bridge structure %p to destroy\n", bridge->technology->name, bridge); - if (bridge->technology->destroy(bridge)) { - ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p... some memory may have leaked\n", - bridge->technology->name, bridge); + if (!strcasecmp("Gosub", app_name)) { + ast_app_exec_sub(NULL, chan, app_args, 0); + } else if (!strcasecmp("Macro", app_name)) { + ast_app_exec_macro(NULL, chan, app_args); + } else { + struct ast_app *app; + + app = pbx_findapp(app_name); + if (!app) { + ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name); + } else { + res = pbx_exec(chan, app, app_args); } } - - cleanup_video_mode(bridge); - - /* Clean up the features configuration */ - ast_bridge_features_cleanup(&bridge->features); - - /* We are no longer using the bridge technology so decrement the module reference count on it */ - ast_module_unref(bridge->technology->mod); - - /* Drop the array of channels */ - ast_free(bridge->array); + return res; } -struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags) +void ast_bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class) { - struct ast_bridge *bridge; - struct ast_bridge_technology *bridge_technology; - - /* If we need to be a smart bridge see if we can move between 1to1 and multimix bridges */ - if (flags & AST_BRIDGE_FLAG_SMART) { - if (!ast_bridge_check((capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) - ? AST_BRIDGE_CAPABILITY_MULTIMIX : AST_BRIDGE_CAPABILITY_1TO1MIX)) { - return NULL; + if (moh_class) { + if (ast_strlen_zero(moh_class)) { + ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD, + NULL, 0); + } else { + ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD, + moh_class, strlen(moh_class) + 1); } } + if (run_app_helper(bridge_channel->chan, app_name, S_OR(app_args, ""))) { + /* Break the bridge if the app returns non-zero. */ + bridge_handle_hangup(bridge_channel); + } + if (moh_class) { + ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, + NULL, 0); + } +} - /* - * If capabilities were provided use our helper function to find - * the "best" bridge technology, otherwise we can just look for - * the most basic capability needed, single 1to1 mixing. - */ - bridge_technology = capabilities - ? find_best_technology(capabilities) - : find_best_technology(AST_BRIDGE_CAPABILITY_1TO1MIX); +struct bridge_run_app { + /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */ + int moh_offset; + /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */ + int app_args_offset; + /*! Application name to run. */ + char app_name[0]; +}; - /* If no bridge technology was found we can't possibly do bridging so fail creation of the bridge */ - if (!bridge_technology) { - return NULL; - } +/*! + * \internal + * \brief Handle the run application bridge action. + * \since 12.0.0 + * + * \param bridge_channel Which channel to run the application on. + * \param data Action frame data to run the application. + * + * \return Nothing + */ +static void bridge_channel_run_app(struct ast_bridge_channel *bridge_channel, struct bridge_run_app *data) +{ + ast_bridge_channel_run_app(bridge_channel, data->app_name, + data->app_args_offset ? &data->app_name[data->app_args_offset] : NULL, + data->moh_offset ? &data->app_name[data->moh_offset] : NULL); +} - /* We have everything we need to create this bridge... so allocate the memory, link things together, and fire her up! */ - bridge = ao2_alloc(sizeof(*bridge), destroy_bridge); - if (!bridge) { - ast_module_unref(bridge_technology->mod); - return NULL; +static void payload_helper_app(ast_bridge_channel_post_action_data post_it, + struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class) +{ + struct bridge_run_app *app_data; + size_t len_name = strlen(app_name) + 1; + size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1; + size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1; + size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh; + + /* Fill in application run frame data. */ + app_data = alloca(len_data); + app_data->app_args_offset = len_args ? len_name : 0; + app_data->moh_offset = len_moh ? len_name + len_args : 0; + strcpy(app_data->app_name, app_name);/* Safe */ + if (len_args) { + strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */ } - - bridge->technology = bridge_technology; - bridge->thread = AST_PTHREADT_NULL; - - /* Create an array of pointers for the channels that will be joining us */ - bridge->array = ast_malloc(BRIDGE_ARRAY_START * sizeof(*bridge->array)); - if (!bridge->array) { - ao2_ref(bridge, -1); - return NULL; + if (moh_class) { + strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */ } - bridge->array_size = BRIDGE_ARRAY_START; - ast_set_flag(&bridge->feature_flags, flags); + post_it(bridge_channel, AST_BRIDGE_ACTION_RUN_APP, app_data, len_data); +} - /* Pass off the bridge to the technology to manipulate if needed */ - if (bridge->technology->create) { - ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n", bridge->technology->name, bridge); - if (bridge->technology->create(bridge)) { - ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n", bridge->technology->name, bridge); - ao2_ref(bridge, -1); - return NULL; - } - } +void ast_bridge_channel_write_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class) +{ + payload_helper_app(ast_bridge_channel_write_action_data, + bridge_channel, app_name, app_args, moh_class); +} - return bridge; +void ast_bridge_channel_queue_app(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class) +{ + payload_helper_app(ast_bridge_channel_queue_action_data, + bridge_channel, app_name, app_args, moh_class); } -int ast_bridge_check(uint32_t capabilities) +void ast_bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class) { - struct ast_bridge_technology *bridge_technology; + if (moh_class) { + if (ast_strlen_zero(moh_class)) { + ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD, + NULL, 0); + } else { + ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_HOLD, + moh_class, strlen(moh_class) + 1); + } + } + if (custom_play) { + custom_play(bridge_channel, playfile); + } else { + ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE); + } + if (moh_class) { + ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, + NULL, 0); + } - if (!(bridge_technology = find_best_technology(capabilities))) { - return 0; + /* + * It may be necessary to resume music on hold after we finish + * playing the announcment. + * + * XXX We have no idea what MOH class was in use before playing + * the file. + */ + if (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_MOH)) { + ast_moh_start(bridge_channel->chan, NULL, NULL); } +} - ast_module_unref(bridge_technology->mod); +struct bridge_playfile { + /*! Call this function to play the playfile. (NULL if normal sound file to play) */ + ast_bridge_custom_play_fn custom_play; + /*! Offset into playfile[] where the MOH class name starts. (zero if no MOH)*/ + int moh_offset; + /*! Filename to play. */ + char playfile[0]; +}; - return 1; +/*! + * \internal + * \brief Handle the playfile bridge action. + * \since 12.0.0 + * + * \param bridge_channel Which channel to play a file on. + * \param payload Action frame payload to play a file. + * + * \return Nothing + */ +static void bridge_channel_playfile(struct ast_bridge_channel *bridge_channel, struct bridge_playfile *payload) +{ + ast_bridge_channel_playfile(bridge_channel, payload->custom_play, payload->playfile, + payload->moh_offset ? &payload->playfile[payload->moh_offset] : NULL); } -int ast_bridge_destroy(struct ast_bridge *bridge) +static void payload_helper_playfile(ast_bridge_channel_post_action_data post_it, + struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class) { - ao2_lock(bridge); - - if (bridge->callid) { - bridge->callid = ast_callid_unref(bridge->callid); + struct bridge_playfile *payload; + size_t len_name = strlen(playfile) + 1; + size_t len_moh = !moh_class ? 0 : strlen(moh_class) + 1; + size_t len_payload = sizeof(*payload) + len_name + len_moh; + + /* Fill in play file frame data. */ + payload = alloca(len_payload); + payload->custom_play = custom_play; + payload->moh_offset = len_moh ? len_name : 0; + strcpy(payload->playfile, playfile);/* Safe */ + if (moh_class) { + strcpy(&payload->playfile[payload->moh_offset], moh_class);/* Safe */ } - if (bridge->thread != AST_PTHREADT_NULL) { - bridge_stop(bridge); - } + post_it(bridge_channel, AST_BRIDGE_ACTION_PLAY_FILE, payload, len_payload); +} - ast_debug(1, "Telling all channels in bridge %p to leave the party\n", bridge); +void ast_bridge_channel_write_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class) +{ + payload_helper_playfile(ast_bridge_channel_write_action_data, + bridge_channel, custom_play, playfile, moh_class); +} + +void ast_bridge_channel_queue_playfile(struct ast_bridge_channel *bridge_channel, ast_bridge_custom_play_fn custom_play, const char *playfile, const char *moh_class) +{ + payload_helper_playfile(ast_bridge_channel_queue_action_data, + bridge_channel, custom_play, playfile, moh_class); +} - /* Drop every bridged channel, the last one will cause the bridge thread (if it exists) to exit */ - bridge_force_out_all(bridge); +struct bridge_park { + int parker_uuid_offset; + int app_data_offset; + /* buffer used for holding those strings */ + char parkee_uuid[0]; +}; - ao2_unlock(bridge); +static void bridge_channel_park(struct ast_bridge_channel *bridge_channel, struct bridge_park *payload) +{ + ast_bridge_channel_park(bridge_channel, payload->parkee_uuid, + &payload->parkee_uuid[payload->parker_uuid_offset], + payload->app_data_offset ? &payload->parkee_uuid[payload->app_data_offset] : NULL); +} - ao2_ref(bridge, -1); +static void payload_helper_park(ast_bridge_channel_post_action_data post_it, + struct ast_bridge_channel *bridge_channel, + const char *parkee_uuid, + const char *parker_uuid, + const char *app_data) +{ + struct bridge_park *payload; + size_t len_parkee_uuid = strlen(parkee_uuid) + 1; + size_t len_parker_uuid = strlen(parker_uuid) + 1; + size_t len_app_data = !app_data ? 0 : strlen(app_data) + 1; + size_t len_payload = sizeof(*payload) + len_parker_uuid + len_parkee_uuid + len_app_data; + + payload = alloca(len_payload); + payload->app_data_offset = len_app_data ? len_parkee_uuid + len_parker_uuid : 0; + payload->parker_uuid_offset = len_parkee_uuid; + strcpy(payload->parkee_uuid, parkee_uuid); + strcpy(&payload->parkee_uuid[payload->parker_uuid_offset], parker_uuid); + if (app_data) { + strcpy(&payload->parkee_uuid[payload->app_data_offset], app_data); + } - return 0; + post_it(bridge_channel, AST_BRIDGE_ACTION_PARK, payload, len_payload); } -static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) +void ast_bridge_channel_write_park(struct ast_bridge_channel *bridge_channel, const char *parkee_uuid, const char *parker_uuid, const char *app_data) { - struct ast_format formats[2]; - ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan)); - ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan)); + payload_helper_park(ast_bridge_channel_write_action_data, + bridge_channel, parkee_uuid, parker_uuid, app_data); +} - /* Are the formats currently in use something this bridge can handle? */ - if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) { - struct ast_format best_format; +/*! + * \internal + * \brief Feed notification that a frame is waiting on a channel into the bridging core + * + * \param bridge_channel Bridge channel the notification was received on + */ +static void bridge_handle_trip(struct ast_bridge_channel *bridge_channel) +{ + struct ast_frame *frame; - ast_best_codec(bridge->technology->format_capabilities, &best_format); + if (bridge_channel->features->mute) { + frame = ast_read_noaudio(bridge_channel->chan); + } else { + frame = ast_read(bridge_channel->chan); + } - /* Read format is a no go... */ - if (option_debug) { - char codec_buf[512]; - ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n", bridge->technology->name, - ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities), - ast_getformatname(&formats[0])); + if (!frame) { + bridge_handle_hangup(bridge_channel); + return; + } + switch (frame->frametype) { + case AST_FRAME_NULL: + /* Just discard it. */ + ast_frfree(frame); + return; + case AST_FRAME_CONTROL: + switch (frame->subclass.integer) { + case AST_CONTROL_HANGUP: + bridge_handle_hangup(bridge_channel); + ast_frfree(frame); + return; +/* BUGBUG This is where incoming HOLD/UNHOLD memory should register. Write UNHOLD into bridge when this channel is pulled. */ + default: + break; } - /* Switch read format to the best one chosen */ - if (ast_set_read_format(bridge_channel->chan, &best_format)) { - ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format)); - return -1; + break; + case AST_FRAME_DTMF_BEGIN: + frame = bridge_handle_dtmf(bridge_channel, frame); + if (!frame) { + return; } - ast_debug(1, "Bridge %p put channel %s into read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format)); - } else { - ast_debug(1, "Bridge %p is happy that channel %s already has read format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[0])); + /* Fall through */ + case AST_FRAME_DTMF_END: + if (!bridge_channel->features->dtmf_passthrough) { + ast_frfree(frame); + return; + } +/* BUGBUG This is where incoming DTMF begin/end memory should register. Write DTMF end into bridge when this channel is pulled. */ + break; + default: + break; } - if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &formats[1])) { - struct ast_format best_format; +/* BUGBUG bridge join or impart needs to do CONNECTED_LINE updates if the channels are being swapped and it is a 1-1 bridge. */ - ast_best_codec(bridge->technology->format_capabilities, &best_format); + /* Simply write the frame out to the bridge technology. */ +/* BUGBUG The tech is where AST_CONTROL_ANSWER hook should go. (early bridge) */ +/* BUGBUG The tech is where incoming BUSY/CONGESTION hangup should happen? (early bridge) */ + bridge_channel_write_frame(bridge_channel, frame); + ast_frfree(frame); +} - /* Write format is a no go... */ - if (option_debug) { - char codec_buf[512]; - ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n", bridge->technology->name, - ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities), - ast_getformatname(&formats[1])); +/*! + * \internal + * \brief Complete joining new channels to the bridge. + * \since 12.0.0 + * + * \param bridge Check for new channels on this bridge. + * + * \note On entry, bridge is already locked. + * + * \return Nothing + */ +static void bridge_complete_join(struct ast_bridge *bridge) +{ + struct ast_bridge_channel *bridge_channel; + + if (bridge->dissolved) { + /* + * No sense in completing the join on channels for a dissolved + * bridge. They are just going to be removed soon anyway. + * However, we do have reason to abort here because the bridge + * technology may not be able to handle the number of channels + * still in the bridge. + */ + return; + } + + AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { + if (!bridge_channel->just_joined) { + continue; } - /* Switch write format to the best one chosen */ - if (ast_set_write_format(bridge_channel->chan, &best_format)) { - ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n", ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format)); - return -1; + + /* Make the channel compatible with the bridge */ + bridge_make_compatible(bridge, bridge_channel); + + /* Tell the bridge technology we are joining so they set us up */ + ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + bridge->technology->name); + if (bridge->technology->join + && bridge->technology->join(bridge, bridge_channel)) { + ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + bridge->technology->name); } - ast_debug(1, "Bridge %p put channel %s into write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format)); - } else { - ast_debug(1, "Bridge %p is happy that channel %s already has write format %s\n", bridge, ast_channel_name(bridge_channel->chan), ast_getformatname(&formats[1])); - } - return 0; + bridge_channel->just_joined = 0; + } } -/*! \brief Perform the smart bridge operation. Basically sees if a new bridge technology should be used instead of the current one. */ -static int smart_bridge_operation(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int count) +/*! \brief Helper function used to find the "best" bridge technology given specified capabilities */ +static struct ast_bridge_technology *find_best_technology(uint32_t capabilities, struct ast_bridge *bridge) { - uint32_t new_capabilities = 0; - struct ast_bridge_technology *new_technology; - struct ast_bridge_technology *old_technology = bridge->technology; - struct ast_bridge temp_bridge = { - .technology = bridge->technology, - .bridge_pvt = bridge->bridge_pvt, - }; - struct ast_bridge_channel *bridge_channel2; + struct ast_bridge_technology *current; + struct ast_bridge_technology *best = NULL; - /* Based on current feature determine whether we want to change bridge technologies or not */ - if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) { - if (count <= 2) { - ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n", - bridge, count, bridge->technology->name); - return 0; + AST_RWLIST_RDLOCK(&bridge_technologies); + AST_RWLIST_TRAVERSE(&bridge_technologies, current, entry) { + if (current->suspended) { + ast_debug(1, "Bridge technology %s is suspended. Skipping.\n", + current->name); + continue; } - new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX; - } else if (bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) { - if (count > 2) { - ast_debug(1, "Bridge %p channel count (%d) is within limits for bridge technology %s, not performing smart bridge operation.\n", - bridge, count, bridge->technology->name); - return 0; + if (!(current->capabilities & capabilities)) { + ast_debug(1, "Bridge technology %s does not have any capabilities we want.\n", + current->name); + continue; + } + if (best && current->preference <= best->preference) { + ast_debug(1, "Bridge technology %s has less preference than %s (%d <= %d). Skipping.\n", + current->name, best->name, current->preference, best->preference); + continue; } - new_capabilities = AST_BRIDGE_CAPABILITY_1TO1MIX; + if (current->compatible && !current->compatible(bridge)) { + ast_debug(1, "Bridge technology %s is not compatible with properties of existing bridge.\n", + current->name); + continue; + } + best = current; } - if (!new_capabilities) { - ast_debug(1, "Bridge '%p' has no new capabilities, not performing smart bridge operation.\n", bridge); - return 0; + if (best) { + /* Increment it's module reference count if present so it does not get unloaded while in use */ + ast_module_ref(best->mod); + ast_debug(1, "Chose bridge technology %s\n", best->name); } - /* Attempt to find a new bridge technology to satisfy the capabilities */ - if (!(new_technology = find_best_technology(new_capabilities))) { - return -1; - } + AST_RWLIST_UNLOCK(&bridge_technologies); - ast_debug(1, "Performing smart bridge operation on bridge %p, moving from bridge technology %s to %s\n", - bridge, old_technology->name, new_technology->name); + return best; +} - /* If a thread is currently executing for the current technology tell it to stop */ - if (bridge->thread != AST_PTHREADT_NULL) { - /* - * If the new bridge technology also needs a thread simply tell - * the bridge thread to refresh itself. This has the benefit of - * not incurring the cost/time of tearing down and bringing up a - * new thread. - */ - if (new_technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD) { - ast_debug(1, "Telling current bridge thread for bridge %p to refresh\n", bridge); - bridge->refresh = 1; - bridge_poke(bridge); - } else { - ast_debug(1, "Telling current bridge thread for bridge %p to stop\n", bridge); - bridge_stop(bridge); - } - } +struct tech_deferred_destroy { + struct ast_bridge_technology *tech; + void *tech_pvt; +}; - /* - * Since we are soon going to pass this bridge to a new - * technology we need to NULL out the bridge_pvt pointer but - * don't worry as it still exists in temp_bridge, ditto for the - * old technology. - */ - bridge->bridge_pvt = NULL; - bridge->technology = new_technology; +/*! + * \internal + * \brief Deferred destruction of bridge tech private structure. + * \since 12.0.0 + * + * \param bridge What to execute the action on. + * \param action Deferred bridge tech destruction. + * + * \note On entry, bridge must not be locked. + * + * \return Nothing + */ +static void bridge_tech_deferred_destroy(struct ast_bridge *bridge, struct ast_frame *action) +{ + struct tech_deferred_destroy *deferred = action->data.ptr; + struct ast_bridge dummy_bridge = { + .technology = deferred->tech, + .tech_pvt = deferred->tech_pvt, + }; + + ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid)); + ast_debug(1, "Bridge %s: calling %s technology destructor (deferred, dummy)\n", + dummy_bridge.uniqueid, dummy_bridge.technology->name); + dummy_bridge.technology->destroy(&dummy_bridge); + ast_module_unref(dummy_bridge.technology->mod); +} - /* Pass the bridge to the new bridge technology so it can set it up */ - if (new_technology->create) { - ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n", - new_technology->name, bridge); - if (new_technology->create(bridge)) { - ast_debug(1, "Bridge technology %s failed to setup bridge structure %p\n", - new_technology->name, bridge); - } +/*! + * \internal + * \brief Handle bridge action frame. + * \since 12.0.0 + * + * \param bridge What to execute the action on. + * \param action What to do. + * + * \note On entry, bridge is already locked. + * \note Can be called by the bridge destructor. + * + * \return Nothing + */ +static void bridge_action_bridge(struct ast_bridge *bridge, struct ast_frame *action) +{ +#if 0 /* In case we need to know when the destructor is calling us. */ + int in_destructor = !ao2_ref(bridge, 0); +#endif + + switch (action->subclass.integer) { + case AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY: + ast_bridge_unlock(bridge); + bridge_tech_deferred_destroy(bridge, action); + ast_bridge_lock(bridge); + break; + case AST_BRIDGE_ACTION_DEFERRED_DISSOLVING: + ast_bridge_unlock(bridge); + bridge->v_table->dissolving(bridge); + ast_bridge_lock(bridge); + break; + default: + /* Unexpected deferred action type. Should never happen. */ + ast_assert(0); + break; } +} - /* Move existing channels over to the new technology, while taking them away from the old one */ - AST_LIST_TRAVERSE(&bridge->channels, bridge_channel2, entry) { - /* Skip over channel that initiated the smart bridge operation */ - if (bridge_channel == bridge_channel2) { - continue; - } +/*! + * \internal + * \brief Do any pending bridge actions. + * \since 12.0.0 + * + * \param bridge What to do actions on. + * + * \note On entry, bridge is already locked. + * \note Can be called by the bridge destructor. + * + * \return Nothing + */ +static void bridge_handle_actions(struct ast_bridge *bridge) +{ + struct ast_frame *action; - /* First we part them from the old technology */ - if (old_technology->leave) { - ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p (really %p)\n", - old_technology->name, bridge_channel2, &temp_bridge, bridge); - if (old_technology->leave(&temp_bridge, bridge_channel2)) { - ast_debug(1, "Bridge technology %s failed to allow %p (really %p) to leave bridge %p\n", - old_technology->name, bridge_channel2, &temp_bridge, bridge); - } + while ((action = AST_LIST_REMOVE_HEAD(&bridge->action_queue, frame_list))) { + switch (action->frametype) { + case AST_FRAME_BRIDGE_ACTION: + bridge_action_bridge(bridge, action); + break; + default: + /* Unexpected deferred frame type. Should never happen. */ + ast_assert(0); + break; } + ast_frfree(action); + } +} - /* Second we make them compatible again with the bridge */ - bridge_make_compatible(bridge, bridge_channel2); +static void destroy_bridge(void *obj) +{ + struct ast_bridge *bridge = obj; + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); - /* Third we join them to the new technology */ - if (new_technology->join) { - ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", - new_technology->name, bridge_channel2, bridge); - if (new_technology->join(bridge, bridge_channel2)) { - ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", - new_technology->name, bridge_channel2, bridge); - } - } + ast_debug(1, "Bridge %s: actually destroying %s bridge, nobody wants it anymore\n", + bridge->uniqueid, bridge->v_table->name); - /* Fourth we tell them to wake up so they become aware that the above has happened */ - bridge_channel_poke(bridge_channel2); + msg = stasis_cache_clear_create(ast_bridge_snapshot_type(), bridge->uniqueid); + if (msg) { + stasis_publish(ast_bridge_topic(bridge), msg); } - /* Now that all the channels have been moved over we need to get rid of all the information the old technology may have left around */ - if (old_technology->destroy) { - ast_debug(1, "Giving bridge technology %s the bridge structure %p (really %p) to destroy\n", - old_technology->name, &temp_bridge, bridge); - if (old_technology->destroy(&temp_bridge)) { - ast_debug(1, "Bridge technology %s failed to destroy bridge structure %p (really %p)... some memory may have leaked\n", - old_technology->name, &temp_bridge, bridge); + /* Do any pending actions in the context of destruction. */ + ast_bridge_lock(bridge); + bridge_handle_actions(bridge); + ast_bridge_unlock(bridge); + + /* There should not be any channels left in the bridge. */ + ast_assert(AST_LIST_EMPTY(&bridge->channels)); + + ast_debug(1, "Bridge %s: calling %s bridge destructor\n", + bridge->uniqueid, bridge->v_table->name); + bridge->v_table->destroy(bridge); + + /* Pass off the bridge to the technology to destroy if needed */ + if (bridge->technology) { + ast_debug(1, "Bridge %s: calling %s technology destructor\n", + bridge->uniqueid, bridge->technology->name); + if (bridge->technology->destroy) { + bridge->technology->destroy(bridge); } + ast_module_unref(bridge->technology->mod); + bridge->technology = NULL; } - /* Finally if the old technology has module referencing remove our reference, we are no longer going to use it */ - ast_module_unref(old_technology->mod); + if (bridge->callid) { + bridge->callid = ast_callid_unref(bridge->callid); + } - return 0; + cleanup_video_mode(bridge); } -/*! \brief Run in a multithreaded model. Each joined channel does writing/reading in their own thread. TODO: Improve */ -static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct ast_bridge_channel *bridge_channel) +struct ast_bridge *ast_bridge_register(struct ast_bridge *bridge) { - int fds[4] = { -1, }, nfds = 0, i = 0, outfd = -1, ms = -1; - struct ast_channel *chan; - - /* Add any file descriptors we may want to monitor */ - if (bridge_channel->bridge->technology->fd) { - for (i = 0; i < 4; i ++) { - if (bridge_channel->fds[i] >= 0) { - fds[nfds++] = bridge_channel->fds[i]; - } + if (bridge) { + ast_bridge_publish_state(bridge); + if (!ao2_link(bridges, bridge)) { + ast_bridge_destroy(bridge); + bridge = NULL; } } + return bridge; +} - ao2_unlock(bridge_channel->bridge); +struct ast_bridge *ast_bridge_alloc(size_t size, const struct ast_bridge_methods *v_table) +{ + struct ast_bridge *bridge; - ao2_lock(bridge_channel); - /* Wait for data to either come from the channel or us to be signalled */ - if (!bridge_channel->suspended) { - ao2_unlock(bridge_channel); - ast_debug(10, "Going into a multithreaded waitfor for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge); - chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, fds, nfds, NULL, &outfd, &ms); - } else { - ast_debug(10, "Going into a multithreaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge); - ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel)); - ao2_unlock(bridge_channel); - chan = NULL; + /* Check v_table that all methods are present. */ + if (!v_table + || !v_table->name + || !v_table->destroy + || !v_table->dissolving + || !v_table->push + || !v_table->pull + || !v_table->notify_masquerade + || !v_table->get_merge_priority) { + ast_log(LOG_ERROR, "Virtual method table for bridge class %s not complete.\n", + v_table && v_table->name ? v_table->name : "<unknown>"); + ast_assert(0); + return NULL; } - ao2_lock(bridge_channel->bridge); - - if (!bridge_channel->suspended) { - ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd); + bridge = ao2_alloc(size, destroy_bridge); + if (bridge) { + bridge->v_table = v_table; } - - return bridge_channel->state; + return bridge; } -/*! \brief Run in a singlethreaded model. Each joined channel yields itself to the main bridge thread. TODO: Improve */ -static enum ast_bridge_channel_state bridge_channel_join_singlethreaded(struct ast_bridge_channel *bridge_channel) +struct ast_bridge *ast_bridge_base_init(struct ast_bridge *self, uint32_t capabilities, unsigned int flags) { - ao2_unlock(bridge_channel->bridge); - ao2_lock(bridge_channel); - if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { - ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge); - ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel)); + if (!self) { + return NULL; } - ao2_unlock(bridge_channel); - ao2_lock(bridge_channel->bridge); - return bridge_channel->state; -} + ast_uuid_generate_str(self->uniqueid, sizeof(self->uniqueid)); + ast_set_flag(&self->feature_flags, flags); + self->allowed_capabilities = capabilities; -/*! \brief Internal function that suspends a channel from a bridge */ -static void bridge_channel_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) -{ - ao2_lock(bridge_channel); - bridge_channel->suspended = 1; - bridge_array_remove(bridge, bridge_channel->chan); - ao2_unlock(bridge_channel); + /* Use our helper function to find the "best" bridge technology. */ + self->technology = find_best_technology(capabilities, self); + if (!self->technology) { + ast_debug(1, "Bridge %s: Could not create. No technology available to support it.\n", + self->uniqueid); + ao2_ref(self, -1); + return NULL; + } - if (bridge->technology->suspend) { - bridge->technology->suspend(bridge, bridge_channel); + /* Pass off the bridge to the technology to manipulate if needed */ + ast_debug(1, "Bridge %s: calling %s technology constructor\n", + self->uniqueid, self->technology->name); + if (self->technology->create && self->technology->create(self)) { + ast_debug(1, "Bridge %s: failed to setup %s technology\n", + self->uniqueid, self->technology->name); + ao2_ref(self, -1); + return NULL; } -} -/*! \brief Internal function that unsuspends a channel from a bridge */ -static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) -{ - ao2_lock(bridge_channel); - bridge_channel->suspended = 0; - bridge_array_add(bridge, bridge_channel->chan); - ast_cond_signal(&bridge_channel->cond); - ao2_unlock(bridge_channel); - - if (bridge->technology->unsuspend) { - bridge->technology->unsuspend(bridge, bridge_channel); + if (!ast_bridge_topic(self)) { + ao2_ref(self, -1); + return NULL; } + + return self; } /*! - * \brief Internal function that executes a feature on a bridge channel - * \note Neither the bridge nor the bridge_channel locks should be held when entering - * this function. + * \internal + * \brief ast_bridge base class destructor. + * \since 12.0.0 + * + * \param self Bridge to operate upon. + * + * \note Stub because of nothing to do. + * + * \return Nothing */ -static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) +static void bridge_base_destroy(struct ast_bridge *self) { - struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features); - struct ast_bridge_features_hook *hook = NULL; - char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = ""; - int look_for_dtmf = 1, dtmf_len = 0; +} - /* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */ - ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY); +/*! + * \internal + * \brief The bridge is being dissolved. + * \since 12.0.0 + * + * \param self Bridge to operate upon. + * + * \return Nothing + */ +static void bridge_base_dissolving(struct ast_bridge *self) +{ + ao2_unlink(bridges, self); +} - /* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */ - while (look_for_dtmf) { - int res = ast_waitfordigit(bridge_channel->chan, 3000); +/*! + * \internal + * \brief ast_bridge base push method. + * \since 12.0.0 + * + * \param self Bridge to operate upon. + * \param bridge_channel Bridge channel to push. + * \param swap Bridge channel to swap places with if not NULL. + * + * \note On entry, self is already locked. + * \note Stub because of nothing to do. + * + * \retval 0 on success + * \retval -1 on failure + */ +static int bridge_base_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap) +{ + return 0; +} - /* If the above timed out simply exit */ - if (!res) { - ast_debug(1, "DTMF feature string collection on bridge channel %p timed out\n", bridge_channel); - break; - } else if (res < 0) { - ast_debug(1, "DTMF feature string collection failed on bridge channel %p for some reason\n", bridge_channel); - break; - } +/*! + * \internal + * \brief ast_bridge base pull method. + * \since 12.0.0 + * + * \param self Bridge to operate upon. + * \param bridge_channel Bridge channel to pull. + * + * \note On entry, self is already locked. + * + * \return Nothing + */ +static void bridge_base_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel) +{ + bridge_features_remove_on_pull(bridge_channel->features); +} - /* Add the above DTMF into the DTMF string so we can do our matching */ - dtmf[dtmf_len++] = res; +/*! + * \internal + * \brief ast_bridge base notify_masquerade method. + * \since 12.0.0 + * + * \param self Bridge to operate upon. + * \param bridge_channel Bridge channel that was masqueraded. + * + * \note On entry, self is already locked. + * + * \return Nothing + */ +static void bridge_base_notify_masquerade(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel) +{ + self->reconfigured = 1; +} - ast_debug(1, "DTMF feature string on bridge channel %p is now '%s'\n", bridge_channel, dtmf); +/*! + * \internal + * \brief Get the merge priority of this bridge. + * \since 12.0.0 + * + * \param self Bridge to operate upon. + * + * \note On entry, self is already locked. + * + * \return Merge priority + */ +static int bridge_base_get_merge_priority(struct ast_bridge *self) +{ + return 0; +} - /* Assume that we do not want to look for DTMF any longer */ - look_for_dtmf = 0; +struct ast_bridge_methods ast_bridge_base_v_table = { + .name = "base", + .destroy = bridge_base_destroy, + .dissolving = bridge_base_dissolving, + .push = bridge_base_push, + .pull = bridge_base_pull, + .notify_masquerade = bridge_base_notify_masquerade, + .get_merge_priority = bridge_base_get_merge_priority, +}; + +struct ast_bridge *ast_bridge_base_new(uint32_t capabilities, unsigned int flags) +{ + void *bridge; - /* See if a DTMF feature hook matches or can match */ - AST_LIST_TRAVERSE(&features->hooks, hook, entry) { - /* If this hook matches just break out now */ - if (!strcmp(hook->dtmf, dtmf)) { - ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on bridge channel %p\n", hook, dtmf, bridge_channel); - look_for_dtmf = 0; - break; - } else if (!strncmp(hook->dtmf, dtmf, dtmf_len)) { - ast_debug(1, "DTMF feature hook %p can match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel); - look_for_dtmf = 1; - } else { - ast_debug(1, "DTMF feature hook %p does not match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel); - } - } + bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_base_v_table); + bridge = ast_bridge_base_init(bridge, capabilities, flags); + bridge = ast_bridge_register(bridge); + return bridge; +} - /* If we have reached the maximum length of a DTMF feature string bail out */ - if (dtmf_len == MAXIMUM_DTMF_FEATURE_STRING) { - break; - } - } +int ast_bridge_destroy(struct ast_bridge *bridge) +{ + ast_debug(1, "Bridge %s: telling all channels to leave the party\n", bridge->uniqueid); + ast_bridge_lock(bridge); + bridge_dissolve(bridge); + ast_bridge_unlock(bridge); - /* Since we are done bringing DTMF in return to using both begin and end frames */ - ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY); + ao2_ref(bridge, -1); - /* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */ - if (hook) { - hook->callback(bridge, bridge_channel, hook->hook_pvt); - /* - * If we are handing the channel off to an external hook for - * ownership, we are not guaranteed what kind of state it will - * come back in. If the channel hungup, we need to detect that - * here if the hook did not already change the state. - */ - if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) { - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END); - } - } else { - ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan); - } + return 0; } -static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) +static int bridge_make_compatible(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) { - struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features); + struct ast_format read_format; + struct ast_format write_format; + struct ast_format best_format; + char codec_buf[512]; + + ast_format_copy(&read_format, ast_channel_readformat(bridge_channel->chan)); + ast_format_copy(&write_format, ast_channel_writeformat(bridge_channel->chan)); + + /* Are the formats currently in use something this bridge can handle? */ + if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, ast_channel_readformat(bridge_channel->chan))) { + ast_best_codec(bridge->technology->format_capabilities, &best_format); + + /* Read format is a no go... */ + ast_debug(1, "Bridge technology %s wants to read any of formats %s but channel has %s\n", + bridge->technology->name, + ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities), + ast_getformatname(&read_format)); - if (features && features->talker_cb) { - features->talker_cb(bridge, bridge_channel, features->talker_pvt_data); + /* Switch read format to the best one chosen */ + if (ast_set_read_format(bridge_channel->chan, &best_format)) { + ast_log(LOG_WARNING, "Failed to set channel %s to read format %s\n", + ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format)); + return -1; + } + ast_debug(1, "Bridge %s put channel %s into read format %s\n", + bridge->uniqueid, ast_channel_name(bridge_channel->chan), + ast_getformatname(&best_format)); + } else { + ast_debug(1, "Bridge %s is happy that channel %s already has read format %s\n", + bridge->uniqueid, ast_channel_name(bridge_channel->chan), + ast_getformatname(&read_format)); } -} -/*! \brief Internal function that plays back DTMF on a bridge channel */ -static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel) -{ - char dtmf_q[8] = ""; + if (!ast_format_cap_iscompatible(bridge->technology->format_capabilities, &write_format)) { + ast_best_codec(bridge->technology->format_capabilities, &best_format); + + /* Write format is a no go... */ + ast_debug(1, "Bridge technology %s wants to write any of formats %s but channel has %s\n", + bridge->technology->name, + ast_getformatname_multiple(codec_buf, sizeof(codec_buf), bridge->technology->format_capabilities), + ast_getformatname(&write_format)); - ast_copy_string(dtmf_q, bridge_channel->dtmf_stream_q, sizeof(dtmf_q)); - bridge_channel->dtmf_stream_q[0] = '\0'; + /* Switch write format to the best one chosen */ + if (ast_set_write_format(bridge_channel->chan, &best_format)) { + ast_log(LOG_WARNING, "Failed to set channel %s to write format %s\n", + ast_channel_name(bridge_channel->chan), ast_getformatname(&best_format)); + return -1; + } + ast_debug(1, "Bridge %s put channel %s into write format %s\n", + bridge->uniqueid, ast_channel_name(bridge_channel->chan), + ast_getformatname(&best_format)); + } else { + ast_debug(1, "Bridge %s is happy that channel %s already has write format %s\n", + bridge->uniqueid, ast_channel_name(bridge_channel->chan), + ast_getformatname(&write_format)); + } - ast_debug(1, "Playing DTMF stream '%s' out to bridge channel %p\n", dtmf_q, bridge_channel); - ast_dtmf_stream(bridge_channel->chan, NULL, dtmf_q, 250, 0); + return 0; } -/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */ -static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_channel *bridge_channel) +/*! + * \internal + * \brief Perform the smart bridge operation. + * \since 12.0.0 + * + * \param bridge Work on this bridge. + * + * \details + * Basically see if a new bridge technology should be used instead + * of the current one. + * + * \note On entry, bridge is already locked. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int smart_bridge_operation(struct ast_bridge *bridge) { - struct ast_format formats[2]; - enum ast_bridge_channel_state state; - ast_format_copy(&formats[0], ast_channel_readformat(bridge_channel->chan)); - ast_format_copy(&formats[1], ast_channel_writeformat(bridge_channel->chan)); + uint32_t new_capabilities; + struct ast_bridge_technology *new_technology; + struct ast_bridge_technology *old_technology = bridge->technology; + struct ast_bridge_channel *bridge_channel; + struct ast_frame *deferred_action; + struct ast_bridge dummy_bridge = { + .technology = bridge->technology, + .tech_pvt = bridge->tech_pvt, + }; - /* Record the thread that will be the owner of us */ - bridge_channel->thread = pthread_self(); + if (bridge->dissolved) { + ast_debug(1, "Bridge %s is dissolved, not performing smart bridge operation.\n", + bridge->uniqueid); + return 0; + } - ast_debug(1, "Joining bridge channel %p to bridge %p\n", bridge_channel, bridge_channel->bridge); + /* Determine new bridge technology capabilities needed. */ + if (2 < bridge->num_channels) { + new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX; + new_capabilities &= bridge->allowed_capabilities; + } else { + new_capabilities = AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX; + new_capabilities &= bridge->allowed_capabilities; + if (!new_capabilities + && (bridge->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)) { + /* Allow switching between different multimix bridge technologies. */ + new_capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX; + } + } - ao2_lock(bridge_channel->bridge); + /* Find a bridge technology to satisfy the new capabilities. */ + new_technology = find_best_technology(new_capabilities, bridge); + if (!new_technology) { + int is_compatible = 0; + + if (old_technology->compatible) { + is_compatible = old_technology->compatible(bridge); + } else if (old_technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) { + is_compatible = 1; + } else if (bridge->num_channels <= 2 + && (old_technology->capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX)) { + is_compatible = 1; + } - if (!bridge_channel->bridge->callid) { - bridge_channel->bridge->callid = ast_read_threadstorage_callid(); + if (is_compatible) { + ast_debug(1, "Bridge %s could not get a new technology, staying with old technology.\n", + bridge->uniqueid); + return 0; + } + ast_log(LOG_WARNING, "Bridge %s has no technology available to support it.\n", + bridge->uniqueid); + return -1; + } + if (new_technology == old_technology) { + ast_debug(1, "Bridge %s is already using the new technology.\n", + bridge->uniqueid); + ast_module_unref(old_technology->mod); + return 0; } - /* Add channel into the bridge */ - AST_LIST_INSERT_TAIL(&bridge_channel->bridge->channels, bridge_channel, entry); - bridge_channel->bridge->num++; - - bridge_array_add(bridge_channel->bridge, bridge_channel->chan); + ast_copy_string(dummy_bridge.uniqueid, bridge->uniqueid, sizeof(dummy_bridge.uniqueid)); - if (bridge_channel->swap) { - struct ast_bridge_channel *bridge_channel2; + if (old_technology->destroy) { + struct tech_deferred_destroy deferred_tech_destroy = { + .tech = dummy_bridge.technology, + .tech_pvt = dummy_bridge.tech_pvt, + }; + struct ast_frame action = { + .frametype = AST_FRAME_BRIDGE_ACTION, + .subclass.integer = AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY, + .data.ptr = &deferred_tech_destroy, + .datalen = sizeof(deferred_tech_destroy), + }; /* - * If we are performing a swap operation we do not need to - * execute the smart bridge operation as the actual number of - * channels involved will not have changed, we just need to tell - * the other channel to leave. + * We need to defer the bridge technology destroy callback + * because we have the bridge locked. */ - bridge_channel2 = find_bridge_channel(bridge_channel->bridge, bridge_channel->swap); - bridge_channel->swap = NULL; - if (bridge_channel2) { - ast_debug(1, "Swapping bridge channel %p out from bridge %p so bridge channel %p can slip in\n", bridge_channel2, bridge_channel->bridge, bridge_channel); - ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP); - } - } else if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) { - /* Perform the smart bridge operation, basically see if we need to move around between technologies */ - smart_bridge_operation(bridge_channel->bridge, bridge_channel, bridge_channel->bridge->num); - } - - /* Make the channel compatible with the bridge */ - bridge_make_compatible(bridge_channel->bridge, bridge_channel); - - /* Tell the bridge technology we are joining so they set us up */ - if (bridge_channel->bridge->technology->join) { - ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge); - if (bridge_channel->bridge->technology->join(bridge_channel->bridge, bridge_channel)) { - ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge); - } - } - - /* Actually execute the respective threading model, and keep our bridge thread alive */ - while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { - /* Update bridge pointer on channel */ - ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge); - /* If the technology requires a thread and one is not running, start it up */ - if (bridge_channel->bridge->thread == AST_PTHREADT_NULL - && (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_THREAD)) { - bridge_channel->bridge->stop = 0; - ast_debug(1, "Starting a bridge thread for bridge %p\n", bridge_channel->bridge); - ao2_ref(bridge_channel->bridge, +1); - if (ast_pthread_create(&bridge_channel->bridge->thread, NULL, bridge_thread, bridge_channel->bridge)) { - ast_debug(1, "Failed to create a bridge thread for bridge %p, giving it another go.\n", bridge_channel->bridge); - ao2_ref(bridge_channel->bridge, -1); - continue; - } - } - /* Execute the threading model */ - state = (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTITHREADED) - ? bridge_channel_join_multithreaded(bridge_channel) - : bridge_channel_join_singlethreaded(bridge_channel); - /* Depending on the above state see what we need to do */ - switch (state) { - case AST_BRIDGE_CHANNEL_STATE_FEATURE: - bridge_channel_suspend(bridge_channel->bridge, bridge_channel); - ao2_unlock(bridge_channel->bridge); - bridge_channel_feature(bridge_channel->bridge, bridge_channel); - ao2_lock(bridge_channel->bridge); - ao2_lock(bridge_channel); - if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_FEATURE) { - ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT); - } - ao2_unlock(bridge_channel); - bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel); - break; - case AST_BRIDGE_CHANNEL_STATE_DTMF: - bridge_channel_suspend(bridge_channel->bridge, bridge_channel); - ao2_unlock(bridge_channel->bridge); - bridge_channel_dtmf_stream(bridge_channel); - ao2_lock(bridge_channel->bridge); - ao2_lock(bridge_channel); - if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_DTMF) { - ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT); - } - ao2_unlock(bridge_channel); - bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel); - break; - case AST_BRIDGE_CHANNEL_STATE_START_TALKING: - case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING: - ao2_unlock(bridge_channel->bridge); - bridge_channel_talking(bridge_channel->bridge, bridge_channel); - ao2_lock(bridge_channel->bridge); - ao2_lock(bridge_channel); - switch (bridge_channel->state) { - case AST_BRIDGE_CHANNEL_STATE_START_TALKING: - case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING: - ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT); - break; - default: - break; - } - ao2_unlock(bridge_channel); - break; - default: - break; + deferred_action = ast_frdup(&action); + if (!deferred_action) { + ast_module_unref(new_technology->mod); + return -1; } + } else { + deferred_action = NULL; } - ast_channel_internal_bridge_set(bridge_channel->chan, NULL); + /* + * We are now committed to changing the bridge technology. We + * must not release the bridge lock until we have installed the + * new bridge technology. + */ + ast_debug(1, "Bridge %s: switching %s technology to %s\n", + bridge->uniqueid, old_technology->name, new_technology->name); - /* See if we need to dissolve the bridge itself if they hung up */ - if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_END) { - bridge_check_dissolve(bridge_channel->bridge, bridge_channel); + /* + * Since we are soon going to pass this bridge to a new + * technology we need to NULL out the tech_pvt pointer but + * don't worry as it still exists in dummy_bridge, ditto for the + * old technology. + */ + bridge->tech_pvt = NULL; + bridge->technology = new_technology; + + /* Setup the new bridge technology. */ + ast_debug(1, "Bridge %s: calling %s technology constructor\n", + bridge->uniqueid, new_technology->name); + if (new_technology->create && new_technology->create(bridge)) { + ast_log(LOG_WARNING, "Bridge %s: failed to setup bridge technology %s\n", + bridge->uniqueid, new_technology->name); + bridge->tech_pvt = dummy_bridge.tech_pvt; + bridge->technology = dummy_bridge.technology; + ast_module_unref(new_technology->mod); + return -1; } - /* Tell the bridge technology we are leaving so they tear us down */ - if (bridge_channel->bridge->technology->leave) { - ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge); - if (bridge_channel->bridge->technology->leave(bridge_channel->bridge, bridge_channel)) { - ast_debug(1, "Bridge technology %s failed to leave %p from bridge %p\n", bridge_channel->bridge->technology->name, bridge_channel, bridge_channel->bridge); + /* Move existing channels over to the new technology. */ + AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { + if (bridge_channel->just_joined) { + /* + * This channel has not completed joining the bridge so it is + * not in the old bridge technology. + */ + continue; + } + + /* First we part them from the old technology */ + ast_debug(1, "Bridge %s: %p(%s) is leaving %s technology (dummy)\n", + dummy_bridge.uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + old_technology->name); + if (old_technology->leave) { + old_technology->leave(&dummy_bridge, bridge_channel); } - } - /* Remove channel from the bridge */ - bridge_channel->bridge->num--; - AST_LIST_REMOVE(&bridge_channel->bridge->channels, bridge_channel, entry); + /* Second we make them compatible again with the bridge */ + bridge_make_compatible(bridge, bridge_channel); - bridge_array_remove(bridge_channel->bridge, bridge_channel->chan); + /* Third we join them to the new technology */ + ast_debug(1, "Bridge %s: %p(%s) is joining %s technology\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + new_technology->name); + if (new_technology->join && new_technology->join(bridge, bridge_channel)) { + ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n", + bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), + new_technology->name); + } + } - /* Perform the smart bridge operation if needed since a channel has left */ - if (ast_test_flag(&bridge_channel->bridge->feature_flags, AST_BRIDGE_FLAG_SMART)) { - smart_bridge_operation(bridge_channel->bridge, NULL, bridge_channel->bridge->num); + /* + * Now that all the channels have been moved over we need to get + * rid of all the information the old technology may have left + * around. + */ + if (old_technology->destroy) { + ast_debug(1, "Bridge %s: deferring %s technology destructor\n", + bridge->uniqueid, old_technology->name); + bridge_queue_action_nodup(bridge, deferred_action); + } else { + ast_debug(1, "Bridge %s: calling %s technology destructor\n", + bridge->uniqueid, old_technology->name); + ast_module_unref(old_technology->mod); } - ao2_unlock(bridge_channel->bridge); + return 0; +} - /* Restore original formats of the channel as they came in */ - if (ast_format_cmp(ast_channel_readformat(bridge_channel->chan), &formats[0]) == AST_FORMAT_CMP_NOT_EQUAL) { - ast_debug(1, "Bridge is returning %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id); - if (ast_set_read_format(bridge_channel->chan, &formats[0])) { - ast_debug(1, "Bridge failed to return channel %p to read format %s(%d)\n", bridge_channel, ast_getformatname(&formats[0]), formats[0].id); - } +/*! + * \internal + * \brief Notify the bridge that it has been reconfigured. + * \since 12.0.0 + * + * \param bridge Reconfigured bridge. + * + * \details + * After a series of bridge_channel_push and + * bridge_channel_pull calls, you need to call this function + * to cause the bridge to complete restruturing for the change + * in the channel makeup of the bridge. + * + * \note On entry, the bridge is already locked. + * + * \return Nothing + */ +static void bridge_reconfigured(struct ast_bridge *bridge) +{ + if (!bridge->reconfigured) { + return; } - if (ast_format_cmp(ast_channel_writeformat(bridge_channel->chan), &formats[1]) == AST_FORMAT_CMP_NOT_EQUAL) { - ast_debug(1, "Bridge is returning %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id); - if (ast_set_write_format(bridge_channel->chan, &formats[1])) { - ast_debug(1, "Bridge failed to return channel %p to write format %s(%d)\n", bridge_channel, ast_getformatname(&formats[1]), formats[1].id); - } + bridge->reconfigured = 0; + if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_SMART) + && smart_bridge_operation(bridge)) { + /* Smart bridge failed. */ + bridge_dissolve(bridge); + return; + } + bridge_complete_join(bridge); +} + +/*! + * \internal + * \brief Suspend a channel from a bridge. + * + * \param bridge_channel Channel to suspend. + * + * \note This function assumes bridge_channel->bridge is locked. + * + * \return Nothing + */ +static void bridge_channel_suspend_nolock(struct ast_bridge_channel *bridge_channel) +{ + bridge_channel->suspended = 1; + if (bridge_channel->in_bridge) { + --bridge_channel->bridge->num_active; } - return bridge_channel->state; + /* Get technology bridge threads off of the channel. */ + if (bridge_channel->bridge->technology->suspend) { + bridge_channel->bridge->technology->suspend(bridge_channel->bridge, bridge_channel); + } } -static void bridge_channel_destroy(void *obj) +/*! + * \internal + * \brief Suspend a channel from a bridge. + * + * \param bridge_channel Channel to suspend. + * + * \return Nothing + */ +static void bridge_channel_suspend(struct ast_bridge_channel *bridge_channel) { - struct ast_bridge_channel *bridge_channel = obj; + ast_bridge_channel_lock_bridge(bridge_channel); + bridge_channel_suspend_nolock(bridge_channel); + ast_bridge_unlock(bridge_channel->bridge); +} - if (bridge_channel->callid) { - bridge_channel->callid = ast_callid_unref(bridge_channel->callid); +/*! + * \internal + * \brief Unsuspend a channel from a bridge. + * + * \param bridge_channel Channel to unsuspend. + * + * \note This function assumes bridge_channel->bridge is locked. + * + * \return Nothing + */ +static void bridge_channel_unsuspend_nolock(struct ast_bridge_channel *bridge_channel) +{ + bridge_channel->suspended = 0; + if (bridge_channel->in_bridge) { + ++bridge_channel->bridge->num_active; + } + + /* Wake technology bridge threads to take care of channel again. */ + if (bridge_channel->bridge->technology->unsuspend) { + bridge_channel->bridge->technology->unsuspend(bridge_channel->bridge, bridge_channel); + } + + /* Wake suspended channel. */ + ast_bridge_channel_lock(bridge_channel); + ast_cond_signal(&bridge_channel->cond); + ast_bridge_channel_unlock(bridge_channel); +} + +/*! + * \internal + * \brief Unsuspend a channel from a bridge. + * + * \param bridge_channel Channel to unsuspend. + * + * \return Nothing + */ +static void bridge_channel_unsuspend(struct ast_bridge_channel *bridge_channel) +{ + ast_bridge_channel_lock_bridge(bridge_channel); + bridge_channel_unsuspend_nolock(bridge_channel); + ast_bridge_unlock(bridge_channel->bridge); +} + +/*! \brief Internal function that activates interval hooks on a bridge channel */ +static void bridge_channel_interval(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_hook *hook; + struct timeval start; + + ast_heap_wrlock(bridge_channel->features->interval_hooks); + start = ast_tvnow(); + while ((hook = ast_heap_peek(bridge_channel->features->interval_hooks, 1))) { + int interval; + unsigned int execution_time; + + if (ast_tvdiff_ms(hook->parms.timer.trip_time, start) > 0) { + ast_debug(1, "Hook %p on %p(%s) wants to happen in the future, stopping our traversal\n", + hook, bridge_channel, ast_channel_name(bridge_channel->chan)); + break; + } + ao2_ref(hook, +1); + ast_heap_unlock(bridge_channel->features->interval_hooks); + + ast_debug(1, "Executing hook %p on %p(%s)\n", + hook, bridge_channel, ast_channel_name(bridge_channel->chan)); + interval = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt); + + ast_heap_wrlock(bridge_channel->features->interval_hooks); + if (ast_heap_peek(bridge_channel->features->interval_hooks, + hook->parms.timer.heap_index) != hook + || !ast_heap_remove(bridge_channel->features->interval_hooks, hook)) { + /* Interval hook is already removed from the bridge_channel. */ + ao2_ref(hook, -1); + continue; + } + ao2_ref(hook, -1); + + if (interval < 0) { + ast_debug(1, "Removed interval hook %p from %p(%s)\n", + hook, bridge_channel, ast_channel_name(bridge_channel->chan)); + ao2_ref(hook, -1); + continue; + } + if (interval) { + /* Set new interval for the hook. */ + hook->parms.timer.interval = interval; + } + + ast_debug(1, "Updating interval hook %p with interval %u on %p(%s)\n", + hook, hook->parms.timer.interval, bridge_channel, + ast_channel_name(bridge_channel->chan)); + + /* resetting start */ + start = ast_tvnow(); + + /* + * Resetup the interval hook for the next interval. We may need + * to skip over any missed intervals because the hook was + * delayed or took too long. + */ + execution_time = ast_tvdiff_ms(start, hook->parms.timer.trip_time); + while (hook->parms.timer.interval < execution_time) { + execution_time -= hook->parms.timer.interval; + } + hook->parms.timer.trip_time = ast_tvadd(start, ast_samp2tv(hook->parms.timer.interval - execution_time, 1000)); + hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &bridge_channel->features->interval_sequence, +1); + + if (ast_heap_push(bridge_channel->features->interval_hooks, hook)) { + /* Could not push the hook back onto the heap. */ + ao2_ref(hook, -1); + } + } + ast_heap_unlock(bridge_channel->features->interval_hooks); +} + +static void bridge_channel_write_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf) +{ + ast_bridge_channel_write_action_data(bridge_channel, + AST_BRIDGE_ACTION_DTMF_STREAM, dtmf, strlen(dtmf) + 1); +} + +/*! + * \brief Internal function that executes a feature on a bridge channel + * \note Neither the bridge nor the bridge_channel locks should be held when entering + * this function. + */ +static void bridge_channel_feature(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook = NULL; + char dtmf[MAXIMUM_DTMF_FEATURE_STRING] = ""; + size_t dtmf_len = 0; + + /* The channel is now under our control and we don't really want any begin frames to do our DTMF matching so disable 'em at the core level */ + ast_set_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY); + + /* Wait for DTMF on the channel and put it into a buffer. If the buffer matches any feature hook execute the hook. */ + do { + int res; + + /* If the above timed out simply exit */ + res = ast_waitfordigit(bridge_channel->chan, 3000); + if (!res) { + ast_debug(1, "DTMF feature string collection on %p(%s) timed out\n", + bridge_channel, ast_channel_name(bridge_channel->chan)); + break; + } + if (res < 0) { + ast_debug(1, "DTMF feature string collection failed on %p(%s) for some reason\n", + bridge_channel, ast_channel_name(bridge_channel->chan)); + break; + } + +/* BUGBUG need to record the duration of DTMF digits so when the string is played back, they are reproduced. */ + /* Add the above DTMF into the DTMF string so we can do our matching */ + dtmf[dtmf_len++] = res; + ast_debug(1, "DTMF feature string on %p(%s) is now '%s'\n", + bridge_channel, ast_channel_name(bridge_channel->chan), dtmf); + + /* See if a DTMF feature hook matches or can match */ + hook = ao2_find(features->dtmf_hooks, dtmf, OBJ_PARTIAL_KEY); + if (!hook) { + ast_debug(1, "No DTMF feature hooks on %p(%s) match '%s'\n", + bridge_channel, ast_channel_name(bridge_channel->chan), dtmf); + break; + } + if (strlen(hook->parms.dtmf.code) == dtmf_len) { + ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on %p(%s)\n", + hook, dtmf, bridge_channel, ast_channel_name(bridge_channel->chan)); + break; + } + ao2_ref(hook, -1); + hook = NULL; + + /* Stop if we have reached the maximum length of a DTMF feature string. */ + } while (dtmf_len < ARRAY_LEN(dtmf) - 1); + + /* Since we are done bringing DTMF in return to using both begin and end frames */ + ast_clear_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_END_DTMF_ONLY); + + /* If a hook was actually matched execute it on this channel, otherwise stream up the DTMF to the other channels */ + if (hook) { + int failed; + + failed = hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt); + if (failed) { + ast_debug(1, "DTMF hook %p is being removed from %p(%s)\n", + hook, bridge_channel, ast_channel_name(bridge_channel->chan)); + ao2_unlink(features->dtmf_hooks, hook); + } + ao2_ref(hook, -1); + + /* + * If we are handing the channel off to an external hook for + * ownership, we are not guaranteed what kind of state it will + * come back in. If the channel hungup, we need to detect that + * here if the hook did not already change the state. + */ + if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) { + bridge_handle_hangup(bridge_channel); + } + } else if (features->dtmf_passthrough) { + bridge_channel_write_dtmf_stream(bridge_channel, dtmf); + } +} + +static void bridge_channel_talking(struct ast_bridge_channel *bridge_channel, int talking) +{ + struct ast_bridge_features *features = bridge_channel->features; + + if (features->talker_cb) { + features->talker_cb(bridge_channel, features->talker_pvt_data, talking); + } +} + +/*! \brief Internal function that plays back DTMF on a bridge channel */ +static void bridge_channel_dtmf_stream(struct ast_bridge_channel *bridge_channel, const char *dtmf) +{ + ast_debug(1, "Playing DTMF stream '%s' out to %p(%s)\n", + dtmf, bridge_channel, ast_channel_name(bridge_channel->chan)); + ast_dtmf_stream(bridge_channel->chan, NULL, dtmf, 0, 0); +} + +struct blind_transfer_data { + char exten[AST_MAX_EXTENSION]; + char context[AST_MAX_CONTEXT]; +}; + +static void bridge_channel_blind_transfer(struct ast_bridge_channel *bridge_channel, + struct blind_transfer_data *blind_data) +{ + ast_async_goto(bridge_channel->chan, blind_data->context, blind_data->exten, 1); + bridge_handle_hangup(bridge_channel); +} + +/*! + * \internal + * \brief Handle bridge channel bridge action frame. + * \since 12.0.0 + * + * \param bridge_channel Channel to execute the action on. + * \param action What to do. + * + * \return Nothing + */ +static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_channel, struct ast_frame *action) +{ + switch (action->subclass.integer) { + case AST_BRIDGE_ACTION_INTERVAL: + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_interval(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + break; + case AST_BRIDGE_ACTION_FEATURE: + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_feature(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + break; + case AST_BRIDGE_ACTION_DTMF_STREAM: + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_dtmf_stream(bridge_channel, action->data.ptr); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + break; + case AST_BRIDGE_ACTION_TALKING_START: + case AST_BRIDGE_ACTION_TALKING_STOP: + bridge_channel_talking(bridge_channel, + action->subclass.integer == AST_BRIDGE_ACTION_TALKING_START); + break; + case AST_BRIDGE_ACTION_PLAY_FILE: + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_playfile(bridge_channel, action->data.ptr); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + break; + case AST_BRIDGE_ACTION_PARK: + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_park(bridge_channel, action->data.ptr); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + break; + case AST_BRIDGE_ACTION_RUN_APP: + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_run_app(bridge_channel, action->data.ptr); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + break; + case AST_BRIDGE_ACTION_BLIND_TRANSFER: + bridge_channel_blind_transfer(bridge_channel, action->data.ptr); + break; + default: + break; + } +} + +/*! + * \internal + * \brief Handle bridge channel control frame action. + * \since 12.0.0 + * + * \param bridge_channel Channel to execute the control frame action on. + * \param fr Control frame to handle. + * + * \return Nothing + */ +static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_channel, struct ast_frame *fr) +{ + struct ast_channel *chan; + struct ast_option_header *aoh; + int is_caller; + int intercept_failed; + + chan = bridge_channel->chan; + switch (fr->subclass.integer) { + case AST_CONTROL_REDIRECTING: + is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING); + bridge_channel_suspend(bridge_channel); + intercept_failed = ast_channel_redirecting_sub(NULL, chan, fr, 1) + && ast_channel_redirecting_macro(NULL, chan, fr, is_caller, 1); + bridge_channel_unsuspend(bridge_channel); + if (intercept_failed) { + ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen); + } + break; + case AST_CONTROL_CONNECTED_LINE: + is_caller = !ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING); + bridge_channel_suspend(bridge_channel); + intercept_failed = ast_channel_connected_line_sub(NULL, chan, fr, 1) + && ast_channel_connected_line_macro(NULL, chan, fr, is_caller, 1); + bridge_channel_unsuspend(bridge_channel); + if (intercept_failed) { + ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen); + } + break; + case AST_CONTROL_HOLD: + case AST_CONTROL_UNHOLD: +/* + * BUGBUG bridge_channels should remember sending/receiving an outstanding HOLD to/from the bridge + * + * When the sending channel is pulled from the bridge it needs to write into the bridge an UNHOLD before being pulled. + * When the receiving channel is pulled from the bridge it needs to generate its own UNHOLD. + * Something similar needs to be done for DTMF begin/end. + */ + case AST_CONTROL_VIDUPDATE: + case AST_CONTROL_SRCUPDATE: + case AST_CONTROL_SRCCHANGE: + case AST_CONTROL_T38_PARAMETERS: +/* BUGBUG may have to do something with a jitter buffer for these. */ + ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen); + break; + case AST_CONTROL_OPTION: + /* + * Forward option Requests, but only ones we know are safe These + * are ONLY sent by chan_iax2 and I'm not convinced that they + * are useful. I haven't deleted them entirely because I just am + * not sure of the ramifications of removing them. + */ + aoh = fr->data.ptr; + if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) { + switch (ntohs(aoh->option)) { + case AST_OPTION_TONE_VERIFY: + case AST_OPTION_TDD: + case AST_OPTION_RELAXDTMF: + case AST_OPTION_AUDIO_MODE: + case AST_OPTION_DIGIT_DETECT: + case AST_OPTION_FAX_DETECT: + ast_channel_setoption(chan, ntohs(aoh->option), aoh->data, + fr->datalen - sizeof(*aoh), 0); + break; + default: + break; + } + } + break; + case AST_CONTROL_ANSWER: + if (ast_channel_state(chan) != AST_STATE_UP) { + ast_answer(chan); + } else { + ast_indicate(chan, -1); + } + break; + default: + ast_indicate_data(chan, fr->subclass.integer, fr->data.ptr, fr->datalen); + break; + } +} + +/*! + * \internal + * \brief Handle bridge channel write frame to channel. + * \since 12.0.0 + * + * \param bridge_channel Channel to write outgoing frame. + * + * \return Nothing + */ +static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channel) +{ + struct ast_frame *fr; + char nudge; + + ast_bridge_channel_lock(bridge_channel); + if (read(bridge_channel->alert_pipe[0], &nudge, sizeof(nudge)) < 0) { + if (errno != EINTR && errno != EAGAIN) { + ast_log(LOG_WARNING, "read() failed for alert pipe on %p(%s): %s\n", + bridge_channel, ast_channel_name(bridge_channel->chan), strerror(errno)); + } + } + fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list); + ast_bridge_channel_unlock(bridge_channel); + if (!fr) { + return; + } + switch (fr->frametype) { + case AST_FRAME_BRIDGE_ACTION: + bridge_channel_handle_action(bridge_channel, fr); + break; + case AST_FRAME_CONTROL: + bridge_channel_handle_control(bridge_channel, fr); + break; + case AST_FRAME_NULL: + break; + default: + /* Write the frame to the channel. */ + bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_SIMPLE; + ast_write(bridge_channel->chan, fr); + break; + } + ast_frfree(fr); +} + +/*! + * \internal + * \brief Handle bridge channel interval expiration. + * \since 12.0.0 + * + * \param bridge_channel Channel to check interval on. + * + * \return Nothing + */ +static void bridge_channel_handle_interval(struct ast_bridge_channel *bridge_channel) +{ + struct ast_timer *interval_timer; + + interval_timer = bridge_channel->features->interval_timer; + if (interval_timer) { + if (ast_wait_for_input(ast_timer_fd(interval_timer), 0) == 1) { + ast_timer_ack(interval_timer, 1); + if (bridge_channel_interval_ready(bridge_channel)) { +/* BUGBUG since this is now only run by the channel thread, there is no need to queue the action once this intervals become a first class wait item in bridge_channel_wait(). */ + struct ast_frame interval_action = { + .frametype = AST_FRAME_BRIDGE_ACTION, + .subclass.integer = AST_BRIDGE_ACTION_INTERVAL, + }; + + ast_bridge_channel_queue_frame(bridge_channel, &interval_action); + } + } + } +} + +/*! + * \internal + * \brief Wait for something to happen on the bridge channel and handle it. + * \since 12.0.0 + * + * \param bridge_channel Channel to wait. + * + * \note Each channel does writing/reading in their own thread. + * + * \return Nothing + */ +static void bridge_channel_wait(struct ast_bridge_channel *bridge_channel) +{ + int ms = -1; + int outfd; + struct ast_channel *chan; + + /* Wait for data to either come from the channel or us to be signaled */ + ast_bridge_channel_lock(bridge_channel); + if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) { + } else if (bridge_channel->suspended) { +/* BUGBUG the external party use of suspended will go away as will these references because this is the bridge channel thread */ + ast_debug(1, "Bridge %s: %p(%s) is going into a signal wait\n", + bridge_channel->bridge->uniqueid, bridge_channel, + ast_channel_name(bridge_channel->chan)); + ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel)); + } else { + ast_debug(10, "Bridge %s: %p(%s) is going into a waitfor\n", + bridge_channel->bridge->uniqueid, bridge_channel, + ast_channel_name(bridge_channel->chan)); + bridge_channel->waiting = 1; + ast_bridge_channel_unlock(bridge_channel); + outfd = -1; +/* BUGBUG need to make the next expiring active interval setup ms timeout rather than holding up the chan reads. */ + chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, + &bridge_channel->alert_pipe[0], 1, NULL, &outfd, &ms); + bridge_channel->waiting = 0; + if (ast_channel_softhangup_internal_flag(bridge_channel->chan) & AST_SOFTHANGUP_UNBRIDGE) { + ast_channel_clear_softhangup(bridge_channel->chan, AST_SOFTHANGUP_UNBRIDGE); + ast_bridge_channel_lock_bridge(bridge_channel); + bridge_channel->bridge->reconfigured = 1; + bridge_reconfigured(bridge_channel->bridge); + ast_bridge_unlock(bridge_channel->bridge); + } + ast_bridge_channel_lock(bridge_channel); + bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_FRAME; + ast_bridge_channel_unlock(bridge_channel); + if (!bridge_channel->suspended + && bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { + if (chan) { + bridge_channel_handle_interval(bridge_channel); + bridge_handle_trip(bridge_channel); + } else if (-1 < outfd) { + bridge_channel_handle_write(bridge_channel); + } + } + bridge_channel->activity = AST_BRIDGE_CHANNEL_THREAD_IDLE; + return; + } + ast_bridge_channel_unlock(bridge_channel); +} + +/*! + * \internal + * \brief Handle bridge channel join event. + * \since 12.0.0 + * + * \param bridge_channel Which channel is joining. + * + * \return Nothing + */ +static void bridge_channel_handle_join(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook; + struct ao2_iterator iter; + + /* Run any join hooks. */ + iter = ao2_iterator_init(features->join_hooks, AO2_ITERATOR_UNLINK); + hook = ao2_iterator_next(&iter); + if (hook) { + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + do { + hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt); + ao2_ref(hook, -1); + } while ((hook = ao2_iterator_next(&iter))); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + } + ao2_iterator_destroy(&iter); +} + +/*! + * \internal + * \brief Handle bridge channel leave event. + * \since 12.0.0 + * + * \param bridge_channel Which channel is leaving. + * + * \return Nothing + */ +static void bridge_channel_handle_leave(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook; + struct ao2_iterator iter; + + /* Run any leave hooks. */ + iter = ao2_iterator_init(features->leave_hooks, AO2_ITERATOR_UNLINK); + hook = ao2_iterator_next(&iter); + if (hook) { + bridge_channel_suspend(bridge_channel); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + do { + hook->callback(bridge_channel->bridge, bridge_channel, hook->hook_pvt); + ao2_ref(hook, -1); + } while ((hook = ao2_iterator_next(&iter))); + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCUPDATE); + bridge_channel_unsuspend(bridge_channel); + } + ao2_iterator_destroy(&iter); +} + +/*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */ +static void bridge_channel_join(struct ast_bridge_channel *bridge_channel) +{ + ast_format_copy(&bridge_channel->read_format, ast_channel_readformat(bridge_channel->chan)); + ast_format_copy(&bridge_channel->write_format, ast_channel_writeformat(bridge_channel->chan)); + + ast_debug(1, "Bridge %s: %p(%s) is joining\n", + bridge_channel->bridge->uniqueid, + bridge_channel, ast_channel_name(bridge_channel->chan)); + + /* + * Get "in the bridge" before pushing the channel for any + * masquerades on the channel to happen before bridging. + */ + ast_channel_lock(bridge_channel->chan); + ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge); + ast_channel_unlock(bridge_channel->chan); + + /* Add the jitterbuffer if the channel requires it */ + ast_jb_enable_for_channel(bridge_channel->chan); + + /* + * Directly locking the bridge is safe here because nobody else + * knows about this bridge_channel yet. + */ + ast_bridge_lock(bridge_channel->bridge); + + if (!bridge_channel->bridge->callid) { + bridge_channel->bridge->callid = ast_read_threadstorage_callid(); + } + + if (bridge_channel_push(bridge_channel)) { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + } + bridge_reconfigured(bridge_channel->bridge); + + if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { + /* + * Indicate a source change since this channel is entering the + * bridge system only if the bridge technology is not MULTIMIX + * capable. The MULTIMIX technology has already done it. + */ + if (!(bridge_channel->bridge->technology->capabilities + & AST_BRIDGE_CAPABILITY_MULTIMIX)) { + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE); + } + + ast_bridge_unlock(bridge_channel->bridge); + bridge_channel_handle_join(bridge_channel); + while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { + /* Wait for something to do. */ + bridge_channel_wait(bridge_channel); + } + bridge_channel_handle_leave(bridge_channel); + ast_bridge_channel_lock_bridge(bridge_channel); + } + + bridge_channel_pull(bridge_channel); + bridge_reconfigured(bridge_channel->bridge); + + ast_bridge_unlock(bridge_channel->bridge); + + /* Indicate a source change since this channel is leaving the bridge system. */ + ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE); + +/* BUGBUG Revisit in regards to moving channels between bridges and local channel optimization. */ +/* BUGBUG This is where outgoing HOLD/UNHOLD memory should write UNHOLD to channel. */ + /* Complete any partial DTMF digit before exiting the bridge. */ + if (ast_channel_sending_dtmf_digit(bridge_channel->chan)) { + ast_bridge_end_dtmf(bridge_channel->chan, + ast_channel_sending_dtmf_digit(bridge_channel->chan), + ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end"); + } + + /* + * Wait for any dual redirect to complete. + * + * Must be done while "still in the bridge" for ast_async_goto() + * to work right. + */ + while (ast_test_flag(ast_channel_flags(bridge_channel->chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) { + sched_yield(); + } + ast_channel_lock(bridge_channel->chan); + ast_channel_internal_bridge_set(bridge_channel->chan, NULL); + ast_channel_unlock(bridge_channel->chan); + + ast_bridge_channel_restore_formats(bridge_channel); +} + +/*! + * \internal + * \brief Close a pipe. + * \since 12.0.0 + * + * \param my_pipe What to close. + * + * \return Nothing + */ +static void pipe_close(int *my_pipe) +{ + if (my_pipe[0] > -1) { + close(my_pipe[0]); + my_pipe[0] = -1; + } + if (my_pipe[1] > -1) { + close(my_pipe[1]); + my_pipe[1] = -1; + } +} + +/*! + * \internal + * \brief Initialize a pipe as non-blocking. + * \since 12.0.0 + * + * \param my_pipe What to initialize. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int pipe_init_nonblock(int *my_pipe) +{ + int flags; + + my_pipe[0] = -1; + my_pipe[1] = -1; + if (pipe(my_pipe)) { + ast_log(LOG_WARNING, "Can't create pipe! Try increasing max file descriptors with ulimit -n\n"); + return -1; + } + flags = fcntl(my_pipe[0], F_GETFL); + if (fcntl(my_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) { + ast_log(LOG_WARNING, "Unable to set read pipe nonblocking! (%d: %s)\n", + errno, strerror(errno)); + return -1; + } + flags = fcntl(my_pipe[1], F_GETFL); + if (fcntl(my_pipe[1], F_SETFL, flags | O_NONBLOCK) < 0) { + ast_log(LOG_WARNING, "Unable to set write pipe nonblocking! (%d: %s)\n", + errno, strerror(errno)); + return -1; + } + return 0; +} + +/* Destroy elements of the bridge channel structure and the bridge channel structure itself */ +static void bridge_channel_destroy(void *obj) +{ + struct ast_bridge_channel *bridge_channel = obj; + struct ast_frame *fr; + + if (bridge_channel->callid) { + bridge_channel->callid = ast_callid_unref(bridge_channel->callid); + } + + if (bridge_channel->bridge) { + ao2_ref(bridge_channel->bridge, -1); + bridge_channel->bridge = NULL; + } + + /* Flush any unhandled wr_queue frames. */ + while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list))) { + ast_frfree(fr); + } + pipe_close(bridge_channel->alert_pipe); + + ast_cond_destroy(&bridge_channel->cond); +} + +static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge) +{ + struct ast_bridge_channel *bridge_channel; + + bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy); + if (!bridge_channel) { + return NULL; + } + ast_cond_init(&bridge_channel->cond, NULL); + if (pipe_init_nonblock(bridge_channel->alert_pipe)) { + ao2_ref(bridge_channel, -1); + return NULL; + } + if (bridge) { + bridge_channel->bridge = bridge; + ao2_ref(bridge_channel->bridge, +1); + } + + return bridge_channel; +} + +struct after_bridge_cb_ds { + /*! Desired callback function. */ + ast_after_bridge_cb callback; + /*! After bridge callback will not be called and destroy any resources data may contain. */ + ast_after_bridge_cb_failed failed; + /*! Extra data to pass to the callback. */ + void *data; +}; + +/*! + * \internal + * \brief Destroy the after bridge callback datastore. + * \since 12.0.0 + * + * \param data After bridge callback data to destroy. + * + * \return Nothing + */ +static void after_bridge_cb_destroy(void *data) +{ + struct after_bridge_cb_ds *after_bridge = data; + + if (after_bridge->failed) { + after_bridge->failed(AST_AFTER_BRIDGE_CB_REASON_DESTROY, after_bridge->data); + after_bridge->failed = NULL; + } +} + +/*! + * \internal + * \brief Fixup the after bridge callback datastore. + * \since 12.0.0 + * + * \param data After bridge callback data to fixup. + * \param old_chan The datastore is moving from this channel. + * \param new_chan The datastore is moving to this channel. + * + * \return Nothing + */ +static void after_bridge_cb_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + /* There can be only one. Discard any already on the new channel. */ + ast_after_bridge_callback_discard(new_chan, AST_AFTER_BRIDGE_CB_REASON_MASQUERADE); +} + +static const struct ast_datastore_info after_bridge_cb_info = { + .type = "after-bridge-cb", + .destroy = after_bridge_cb_destroy, + .chan_fixup = after_bridge_cb_fixup, +}; + +/*! + * \internal + * \brief Remove channel after the bridge callback and return it. + * \since 12.0.0 + * + * \param chan Channel to remove after bridge callback. + * + * \retval datastore on success. + * \retval NULL on error or not found. + */ +static struct ast_datastore *after_bridge_cb_remove(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &after_bridge_cb_info, NULL); + if (datastore && ast_channel_datastore_remove(chan, datastore)) { + datastore = NULL; + } + ast_channel_unlock(chan); + + return datastore; +} + +void ast_after_bridge_callback_discard(struct ast_channel *chan, enum ast_after_bridge_cb_reason reason) +{ + struct ast_datastore *datastore; + + datastore = after_bridge_cb_remove(chan); + if (datastore) { + struct after_bridge_cb_ds *after_bridge = datastore->data; + + if (after_bridge && after_bridge->failed) { + after_bridge->failed(reason, after_bridge->data); + after_bridge->failed = NULL; + } + ast_datastore_free(datastore); + } +} + +/*! + * \internal + * \brief Run any after bridge callback if possible. + * \since 12.0.0 + * + * \param chan Channel to run after bridge callback. + * + * \return Nothing + */ +static void after_bridge_callback_run(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + struct after_bridge_cb_ds *after_bridge; + + if (ast_check_hangup(chan)) { + return; + } + + /* Get after bridge goto datastore. */ + datastore = after_bridge_cb_remove(chan); + if (!datastore) { + return; + } + + after_bridge = datastore->data; + if (after_bridge) { + after_bridge->failed = NULL; + after_bridge->callback(chan, after_bridge->data); + } + + /* Discard after bridge callback datastore. */ + ast_datastore_free(datastore); +} + +int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb callback, ast_after_bridge_cb_failed failed, void *data) +{ + struct ast_datastore *datastore; + struct after_bridge_cb_ds *after_bridge; + + /* Sanity checks. */ + ast_assert(chan != NULL); + if (!chan || !callback) { + return -1; + } + + /* Create a new datastore. */ + datastore = ast_datastore_alloc(&after_bridge_cb_info, NULL); + if (!datastore) { + return -1; + } + after_bridge = ast_calloc(1, sizeof(*after_bridge)); + if (!after_bridge) { + ast_datastore_free(datastore); + return -1; + } + + /* Initialize it. */ + after_bridge->callback = callback; + after_bridge->failed = failed; + after_bridge->data = data; + datastore->data = after_bridge; + + /* Put it on the channel replacing any existing one. */ + ast_channel_lock(chan); + ast_after_bridge_callback_discard(chan, AST_AFTER_BRIDGE_CB_REASON_REPLACED); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + + return 0; +} + +struct after_bridge_goto_ds { + /*! Goto string that can be parsed by ast_parseable_goto(). */ + const char *parseable_goto; + /*! Specific goto context or default context for parseable_goto. */ + const char *context; + /*! Specific goto exten or default exten for parseable_goto. */ + const char *exten; + /*! Specific goto priority or default priority for parseable_goto. */ + int priority; + /*! TRUE if the peer should run the h exten. */ + unsigned int run_h_exten:1; + /*! Specific goto location */ + unsigned int specific:1; +}; + +/*! + * \internal + * \brief Destroy the after bridge goto datastore. + * \since 12.0.0 + * + * \param data After bridge goto data to destroy. + * + * \return Nothing + */ +static void after_bridge_goto_destroy(void *data) +{ + struct after_bridge_goto_ds *after_bridge = data; + + ast_free((char *) after_bridge->parseable_goto); + ast_free((char *) after_bridge->context); + ast_free((char *) after_bridge->exten); +} + +/*! + * \internal + * \brief Fixup the after bridge goto datastore. + * \since 12.0.0 + * + * \param data After bridge goto data to fixup. + * \param old_chan The datastore is moving from this channel. + * \param new_chan The datastore is moving to this channel. + * + * \return Nothing + */ +static void after_bridge_goto_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + /* There can be only one. Discard any already on the new channel. */ + ast_after_bridge_goto_discard(new_chan); +} + +static const struct ast_datastore_info after_bridge_goto_info = { + .type = "after-bridge-goto", + .destroy = after_bridge_goto_destroy, + .chan_fixup = after_bridge_goto_fixup, +}; + +/*! + * \internal + * \brief Remove channel goto location after the bridge and return it. + * \since 12.0.0 + * + * \param chan Channel to remove after bridge goto location. + * + * \retval datastore on success. + * \retval NULL on error or not found. + */ +static struct ast_datastore *after_bridge_goto_remove(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &after_bridge_goto_info, NULL); + if (datastore && ast_channel_datastore_remove(chan, datastore)) { + datastore = NULL; + } + ast_channel_unlock(chan); + + return datastore; +} + +void ast_after_bridge_goto_discard(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + datastore = after_bridge_goto_remove(chan); + if (datastore) { + ast_datastore_free(datastore); + } +} + +int ast_after_bridge_goto_setup(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + struct after_bridge_goto_ds *after_bridge; + int goto_failed = -1; + + /* Determine if we are going to setup a dialplan location and where. */ + if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) { + /* An async goto has already setup a location. */ + ast_channel_clear_softhangup(chan, AST_SOFTHANGUP_ASYNCGOTO); + if (!ast_check_hangup(chan)) { + goto_failed = 0; + } + return goto_failed; + } + + /* Get after bridge goto datastore. */ + datastore = after_bridge_goto_remove(chan); + if (!datastore) { + return goto_failed; + } + + after_bridge = datastore->data; + if (after_bridge->run_h_exten) { + if (ast_exists_extension(chan, after_bridge->context, "h", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL))) { + ast_debug(1, "Running after bridge goto h exten %s,h,1\n", + ast_channel_context(chan)); + ast_pbx_h_exten_run(chan, after_bridge->context); + } + } else if (!ast_check_hangup(chan)) { + if (after_bridge->specific) { + goto_failed = ast_explicit_goto(chan, after_bridge->context, + after_bridge->exten, after_bridge->priority); + } else if (!ast_strlen_zero(after_bridge->parseable_goto)) { + char *context; + char *exten; + int priority; + + /* Option F(x) for Bridge(), Dial(), and Queue() */ + + /* Save current dialplan location in case of failure. */ + context = ast_strdupa(ast_channel_context(chan)); + exten = ast_strdupa(ast_channel_exten(chan)); + priority = ast_channel_priority(chan); + + /* Set current dialplan position to default dialplan position */ + ast_explicit_goto(chan, after_bridge->context, after_bridge->exten, + after_bridge->priority); + + /* Then perform the goto */ + goto_failed = ast_parseable_goto(chan, after_bridge->parseable_goto); + if (goto_failed) { + /* Restore original dialplan location. */ + ast_channel_context_set(chan, context); + ast_channel_exten_set(chan, exten); + ast_channel_priority_set(chan, priority); + } + } else { + /* Option F() for Bridge(), Dial(), and Queue() */ + goto_failed = ast_goto_if_exists(chan, after_bridge->context, + after_bridge->exten, after_bridge->priority + 1); + } + if (!goto_failed) { + ast_debug(1, "Setup after bridge goto location to %s,%s,%d.\n", + ast_channel_context(chan), + ast_channel_exten(chan), + ast_channel_priority(chan)); + } + } + + /* Discard after bridge goto datastore. */ + ast_datastore_free(datastore); + + return goto_failed; +} + +void ast_after_bridge_goto_run(struct ast_channel *chan) +{ + int goto_failed; + + goto_failed = ast_after_bridge_goto_setup(chan); + if (goto_failed || ast_pbx_run(chan)) { + ast_hangup(chan); + } +} + +/*! + * \internal + * \brief Set after bridge goto location of channel. + * \since 12.0.0 + * + * \param chan Channel to setup after bridge goto location. + * \param run_h_exten TRUE if the h exten should be run. + * \param specific TRUE if the context/exten/priority is exactly specified. + * \param context Context to goto after bridge. + * \param exten Exten to goto after bridge. (Could be NULL if run_h_exten) + * \param priority Priority to goto after bridge. + * \param parseable_goto User specified goto string. (Could be NULL) + * + * \details Add a channel datastore to setup the goto location + * when the channel leaves the bridge and run a PBX from there. + * + * If run_h_exten then execute the h exten found in the given context. + * Else if specific then goto the given context/exten/priority. + * Else if parseable_goto then use the given context/exten/priority + * as the relative position for the parseable_goto. + * Else goto the given context/exten/priority+1. + * + * \return Nothing + */ +static void __after_bridge_set_goto(struct ast_channel *chan, int run_h_exten, int specific, const char *context, const char *exten, int priority, const char *parseable_goto) +{ + struct ast_datastore *datastore; + struct after_bridge_goto_ds *after_bridge; + + /* Sanity checks. */ + ast_assert(chan != NULL); + if (!chan) { + return; + } + if (run_h_exten) { + ast_assert(run_h_exten && context); + if (!context) { + return; + } + } else { + ast_assert(context && exten && 0 < priority); + if (!context || !exten || priority < 1) { + return; + } + } + + /* Create a new datastore. */ + datastore = ast_datastore_alloc(&after_bridge_goto_info, NULL); + if (!datastore) { + return; + } + after_bridge = ast_calloc(1, sizeof(*after_bridge)); + if (!after_bridge) { + ast_datastore_free(datastore); + return; + } + + /* Initialize it. */ + after_bridge->parseable_goto = ast_strdup(parseable_goto); + after_bridge->context = ast_strdup(context); + after_bridge->exten = ast_strdup(exten); + after_bridge->priority = priority; + after_bridge->run_h_exten = run_h_exten ? 1 : 0; + after_bridge->specific = specific ? 1 : 0; + datastore->data = after_bridge; + if ((parseable_goto && !after_bridge->parseable_goto) + || (context && !after_bridge->context) + || (exten && !after_bridge->exten)) { + ast_datastore_free(datastore); + return; + } + + /* Put it on the channel replacing any existing one. */ + ast_channel_lock(chan); + ast_after_bridge_goto_discard(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); +} + +void ast_after_bridge_set_goto(struct ast_channel *chan, const char *context, const char *exten, int priority) +{ + __after_bridge_set_goto(chan, 0, 1, context, exten, priority, NULL); +} + +void ast_after_bridge_set_h(struct ast_channel *chan, const char *context) +{ + __after_bridge_set_goto(chan, 1, 0, context, NULL, 1, NULL); +} + +void ast_after_bridge_set_go_on(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *parseable_goto) +{ + char *p_goto; + + if (!ast_strlen_zero(parseable_goto)) { + p_goto = ast_strdupa(parseable_goto); + ast_replace_subargument_delimiter(p_goto); + } else { + p_goto = NULL; + } + __after_bridge_set_goto(chan, 0, 0, context, exten, priority, p_goto); +} + +void ast_bridge_notify_masquerade(struct ast_channel *chan) +{ + struct ast_bridge_channel *bridge_channel; + struct ast_bridge *bridge; + + /* Safely get the bridge_channel pointer for the chan. */ + ast_channel_lock(chan); + bridge_channel = ast_channel_get_bridge_channel(chan); + ast_channel_unlock(chan); + if (!bridge_channel) { + /* Not in a bridge */ + return; + } + + ast_bridge_channel_lock_bridge(bridge_channel); + bridge = bridge_channel->bridge; + if (bridge_channel == find_bridge_channel(bridge, chan)) { +/* BUGBUG this needs more work. The channels need to be made compatible again if the formats change. The bridge_channel thread needs to monitor for this case. */ + /* The channel we want to notify is still in a bridge. */ + bridge->v_table->notify_masquerade(bridge, bridge_channel); + bridge_reconfigured(bridge); + } + ast_bridge_unlock(bridge); + ao2_ref(bridge_channel, -1); +} + +/* + * BUGBUG make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back. + * + * This change is really going to break ConfBridge. All other + * users are easily changed. However, it is needed so the + * bridging code can manipulate features on all channels + * consistently no matter how they joined. + * + * Need to update the features parameter doxygen when this + * change is made to be like ast_bridge_impart(). + */ +enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, + struct ast_channel *chan, + struct ast_channel *swap, + struct ast_bridge_features *features, + struct ast_bridge_tech_optimizations *tech_args, + int pass_reference) +{ + struct ast_bridge_channel *bridge_channel; + enum ast_bridge_channel_state state; + + bridge_channel = bridge_channel_alloc(bridge); + if (pass_reference) { + ao2_ref(bridge, -1); + } + if (!bridge_channel) { + state = AST_BRIDGE_CHANNEL_STATE_HANGUP; + goto join_exit; + } +/* BUGBUG features cannot be NULL when passed in. When it is changed to allocated we can do like ast_bridge_impart() and allocate one. */ + ast_assert(features != NULL); + if (!features) { + ao2_ref(bridge_channel, -1); + state = AST_BRIDGE_CHANNEL_STATE_HANGUP; + goto join_exit; + } + if (tech_args) { + bridge_channel->tech_args = *tech_args; + } + + /* Initialize various other elements of the bridge channel structure that we can't do above */ + ast_channel_lock(chan); + ast_channel_internal_bridge_channel_set(chan, bridge_channel); + ast_channel_unlock(chan); + bridge_channel->thread = pthread_self(); + bridge_channel->chan = chan; + bridge_channel->swap = swap; + bridge_channel->features = features; + + bridge_channel_join(bridge_channel); + state = bridge_channel->state; + + /* Cleanup all the data in the bridge channel after it leaves the bridge. */ + ast_channel_lock(chan); + ast_channel_internal_bridge_channel_set(chan, NULL); + ast_channel_unlock(chan); + bridge_channel->chan = NULL; + bridge_channel->swap = NULL; + bridge_channel->features = NULL; + + ao2_ref(bridge_channel, -1); + +join_exit:; +/* BUGBUG this is going to cause problems for DTMF atxfer attended bridge between B & C. Maybe an ast_bridge_join_internal() that does not do the after bridge goto for this case. */ + after_bridge_callback_run(chan); + if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) + && !ast_after_bridge_goto_setup(chan)) { + /* Claim the after bridge goto is an async goto destination. */ + ast_channel_lock(chan); + ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO); + ast_channel_unlock(chan); + } + return state; +} + +/*! \brief Thread responsible for imparted bridged channels to be departed */ +static void *bridge_channel_depart_thread(void *data) +{ + struct ast_bridge_channel *bridge_channel = data; + + if (bridge_channel->callid) { + ast_callid_threadassoc_add(bridge_channel->callid); + } + + bridge_channel_join(bridge_channel); + + /* cleanup */ + bridge_channel->swap = NULL; + ast_bridge_features_destroy(bridge_channel->features); + bridge_channel->features = NULL; + + ast_after_bridge_callback_discard(bridge_channel->chan, AST_AFTER_BRIDGE_CB_REASON_DEPART); + ast_after_bridge_goto_discard(bridge_channel->chan); + + return NULL; +} + +/*! \brief Thread responsible for independent imparted bridged channels */ +static void *bridge_channel_ind_thread(void *data) +{ + struct ast_bridge_channel *bridge_channel = data; + struct ast_channel *chan; + + if (bridge_channel->callid) { + ast_callid_threadassoc_add(bridge_channel->callid); + } + + bridge_channel_join(bridge_channel); + chan = bridge_channel->chan; + + /* cleanup */ + ast_channel_lock(chan); + ast_channel_internal_bridge_channel_set(chan, NULL); + ast_channel_unlock(chan); + bridge_channel->chan = NULL; + bridge_channel->swap = NULL; + ast_bridge_features_destroy(bridge_channel->features); + bridge_channel->features = NULL; + + ao2_ref(bridge_channel, -1); + + after_bridge_callback_run(chan); + ast_after_bridge_goto_run(chan); + return NULL; +} + +int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int independent) +{ + int res; + struct ast_bridge_channel *bridge_channel; + + /* Supply an empty features structure if the caller did not. */ + if (!features) { + features = ast_bridge_features_new(); + if (!features) { + return -1; + } + } + + /* Try to allocate a structure for the bridge channel */ + bridge_channel = bridge_channel_alloc(bridge); + if (!bridge_channel) { + ast_bridge_features_destroy(features); + return -1; + } + + /* Setup various parameters */ + ast_channel_lock(chan); + ast_channel_internal_bridge_channel_set(chan, bridge_channel); + ast_channel_unlock(chan); + bridge_channel->chan = chan; + bridge_channel->swap = swap; + bridge_channel->features = features; + bridge_channel->depart_wait = independent ? 0 : 1; + bridge_channel->callid = ast_read_threadstorage_callid(); + + /* Actually create the thread that will handle the channel */ + if (independent) { + /* Independently imparted channels cannot have a PBX. */ + ast_assert(!ast_channel_pbx(chan)); + + res = ast_pthread_create_detached(&bridge_channel->thread, NULL, + bridge_channel_ind_thread, bridge_channel); + } else { + /* Imparted channels to be departed should not have a PBX either. */ + ast_assert(!ast_channel_pbx(chan)); + + res = ast_pthread_create(&bridge_channel->thread, NULL, + bridge_channel_depart_thread, bridge_channel); + } + + if (res) { + /* cleanup */ + ast_channel_lock(chan); + ast_channel_internal_bridge_channel_set(chan, NULL); + ast_channel_unlock(chan); + bridge_channel->chan = NULL; + bridge_channel->swap = NULL; + ast_bridge_features_destroy(bridge_channel->features); + bridge_channel->features = NULL; + + ao2_ref(bridge_channel, -1); + return -1; + } + + return 0; +} + +int ast_bridge_depart(struct ast_channel *chan) +{ + struct ast_bridge_channel *bridge_channel; + int departable; + + ast_channel_lock(chan); + bridge_channel = ast_channel_internal_bridge_channel(chan); + departable = bridge_channel && bridge_channel->depart_wait; + ast_channel_unlock(chan); + if (!departable) { + ast_log(LOG_ERROR, "Channel %s cannot be departed.\n", + ast_channel_name(chan)); + /* + * Should never happen. It likely means that + * ast_bridge_depart() is called by two threads for the same + * channel, the channel was never imparted to be departed, or it + * has already been departed. + */ + ast_assert(0); + return -1; + } + + /* + * We are claiming the reference held by the depart bridge + * channel thread. + */ + + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + + /* Wait for the depart thread to die */ + ast_debug(1, "Waiting for %p(%s) bridge thread to die.\n", + bridge_channel, ast_channel_name(bridge_channel->chan)); + pthread_join(bridge_channel->thread, NULL); + + ast_channel_lock(chan); + ast_channel_internal_bridge_channel_set(chan, NULL); + ast_channel_unlock(chan); + + /* We can get rid of the bridge_channel after the depart thread has died. */ + ao2_ref(bridge_channel, -1); + return 0; +} + +int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan) +{ + struct ast_bridge_channel *bridge_channel; + + ast_bridge_lock(bridge); + + /* Try to find the channel that we want to remove */ + if (!(bridge_channel = find_bridge_channel(bridge, chan))) { + ast_bridge_unlock(bridge); + return -1; + } + + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + + ast_bridge_unlock(bridge); + + return 0; +} + +/*! + * \internal + * \brief Point the bridge_channel to a new bridge. + * \since 12.0.0 + * + * \param bridge_channel What is to point to a new bridge. + * \param new_bridge Where the bridge channel should point. + * + * \return Nothing + */ +static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_channel, struct ast_bridge *new_bridge) +{ + struct ast_bridge *old_bridge; + + ao2_ref(new_bridge, +1); + ast_bridge_channel_lock(bridge_channel); + ast_channel_lock(bridge_channel->chan); + old_bridge = bridge_channel->bridge; + bridge_channel->bridge = new_bridge; + ast_channel_internal_bridge_set(bridge_channel->chan, new_bridge); + ast_channel_unlock(bridge_channel->chan); + ast_bridge_channel_unlock(bridge_channel); + ao2_ref(old_bridge, -1); +} + +/*! + * \internal + * \brief Do the merge of two bridges. + * \since 12.0.0 + * + * \param dst_bridge Destination bridge of merge. + * \param src_bridge Source bridge of merge. + * \param kick_me Array of channels to kick from the bridges. + * \param num_kick Number of channels in the kick_me array. + * + * \return Nothing + * + * \note The two bridges are assumed already locked. + * + * This moves the channels in src_bridge into the bridge pointed + * to by dst_bridge. + */ +static void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick) +{ + struct ast_bridge_channel *bridge_channel; + unsigned int idx; + + ast_debug(1, "Merging bridge %s into bridge %s\n", + src_bridge->uniqueid, dst_bridge->uniqueid); + + ast_bridge_publish_merge(dst_bridge, src_bridge); + + /* + * Move channels from src_bridge over to dst_bridge. + * + * We must use AST_LIST_TRAVERSE_SAFE_BEGIN() because + * bridge_channel_pull() alters the list we are traversing. + */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&src_bridge->channels, bridge_channel, entry) { + if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) { + /* + * The channel is already leaving let it leave normally because + * pulling it may delete hooks that should run for this channel. + */ + continue; + } + if (ast_test_flag(&bridge_channel->features->feature_flags, + AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) { + continue; + } + + if (kick_me) { + for (idx = 0; idx < num_kick; ++idx) { + if (bridge_channel == kick_me[idx]) { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + break; + } + } + } + bridge_channel_pull(bridge_channel); + if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) { + /* + * The channel died as a result of being pulled or it was + * kicked. Leave it pointing to the original bridge. + */ + continue; + } + + /* Point to new bridge.*/ + bridge_channel_change_bridge(bridge_channel, dst_bridge); + + if (bridge_channel_push(bridge_channel)) { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + } + } + AST_LIST_TRAVERSE_SAFE_END; + + if (kick_me) { + /* + * Now we can kick any channels in the dst_bridge without + * potentially dissolving the bridge. + */ + for (idx = 0; idx < num_kick; ++idx) { + bridge_channel = kick_me[idx]; + ast_bridge_channel_lock(bridge_channel); + if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { + ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + bridge_channel_pull(bridge_channel); + } + ast_bridge_channel_unlock(bridge_channel); + } + } + + bridge_reconfigured(dst_bridge); + bridge_reconfigured(src_bridge); + + ast_debug(1, "Merged bridge %s into bridge %s\n", + src_bridge->uniqueid, dst_bridge->uniqueid); +} + +struct merge_direction { + /*! Destination merge bridge. */ + struct ast_bridge *dest; + /*! Source merge bridge. */ + struct ast_bridge *src; +}; + +/*! + * \internal + * \brief Determine which bridge should merge into the other. + * \since 12.0.0 + * + * \param bridge1 A bridge for merging + * \param bridge2 A bridge for merging + * + * \note The two bridges are assumed already locked. + * + * \return Which bridge merges into which or NULL bridges if cannot merge. + */ +static struct merge_direction bridge_merge_determine_direction(struct ast_bridge *bridge1, struct ast_bridge *bridge2) +{ + struct merge_direction merge = { NULL, NULL }; + int bridge1_priority; + int bridge2_priority; + + if (!ast_test_flag(&bridge1->feature_flags, + AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM) + && !ast_test_flag(&bridge2->feature_flags, + AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) { + /* + * Can merge either way. Merge to the higher priority merge + * bridge. Otherwise merge to the larger bridge. + */ + bridge1_priority = bridge1->v_table->get_merge_priority(bridge1); + bridge2_priority = bridge2->v_table->get_merge_priority(bridge2); + if (bridge2_priority < bridge1_priority) { + merge.dest = bridge1; + merge.src = bridge2; + } else if (bridge1_priority < bridge2_priority) { + merge.dest = bridge2; + merge.src = bridge1; + } else { + /* Merge to the larger bridge. */ + if (bridge2->num_channels <= bridge1->num_channels) { + merge.dest = bridge1; + merge.src = bridge2; + } else { + merge.dest = bridge2; + merge.src = bridge1; + } + } + } else if (!ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO) + && !ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) { + /* Can merge only one way. */ + merge.dest = bridge1; + merge.src = bridge2; + } else if (!ast_test_flag(&bridge2->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO) + && !ast_test_flag(&bridge1->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) { + /* Can merge only one way. */ + merge.dest = bridge2; + merge.src = bridge1; + } + + return merge; +} + +/*! + * \internal + * \brief Merge two bridges together + * \since 12.0.0 + * + * \param dst_bridge Destination bridge of merge. + * \param src_bridge Source bridge of merge. + * \param merge_best_direction TRUE if don't care about which bridge merges into the other. + * \param kick_me Array of channels to kick from the bridges. + * \param num_kick Number of channels in the kick_me array. + * + * \note The dst_bridge and src_bridge are assumed already locked. + * + * \retval 0 on success + * \retval -1 on failure + */ +static int bridge_merge_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick) +{ + struct merge_direction merge; + struct ast_bridge_channel **kick_them = NULL; + + /* Sanity check. */ + ast_assert(dst_bridge && src_bridge && dst_bridge != src_bridge && (!num_kick || kick_me)); + + if (dst_bridge->dissolved || src_bridge->dissolved) { + ast_debug(1, "Can't merge bridges %s and %s, at least one bridge is dissolved.\n", + src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY) + || ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) { + ast_debug(1, "Can't merge bridges %s and %s, masquerade only.\n", + src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) { + ast_debug(1, "Can't merge bridges %s and %s, merging temporarily inhibited.\n", + src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + + if (merge_best_direction) { + merge = bridge_merge_determine_direction(dst_bridge, src_bridge); + } else { + merge.dest = dst_bridge; + merge.src = src_bridge; + } + + if (!merge.dest + || ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO) + || ast_test_flag(&merge.src->feature_flags, AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM)) { + ast_debug(1, "Can't merge bridges %s and %s, merging inhibited.\n", + src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (merge.src->num_channels < 2) { + /* + * For a two party bridge, a channel may be temporarily removed + * from the source bridge or the initial bridge members have not + * joined yet. + */ + ast_debug(1, "Can't merge bridge %s into bridge %s, not enough channels in source bridge.\n", + merge.src->uniqueid, merge.dest->uniqueid); + return -1; + } + if (2 + num_kick < merge.dest->num_channels + merge.src->num_channels + && !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) + && (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART) + || !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) { + ast_debug(1, "Can't merge bridge %s into bridge %s, multimix is needed and it cannot be acquired.\n", + merge.src->uniqueid, merge.dest->uniqueid); + return -1; + } + + if (num_kick) { + unsigned int num_to_kick = 0; + unsigned int idx; + + kick_them = ast_alloca(num_kick * sizeof(*kick_them)); + for (idx = 0; idx < num_kick; ++idx) { + kick_them[num_to_kick] = find_bridge_channel(merge.src, kick_me[idx]); + if (!kick_them[num_to_kick]) { + kick_them[num_to_kick] = find_bridge_channel(merge.dest, kick_me[idx]); + } + if (kick_them[num_to_kick]) { + ++num_to_kick; + } + } + + if (num_to_kick != num_kick) { + ast_debug(1, "Can't merge bridge %s into bridge %s, at least one kicked channel is not in either bridge.\n", + merge.src->uniqueid, merge.dest->uniqueid); + return -1; + } + } + + bridge_merge_do(merge.dest, merge.src, kick_them, num_kick); + return 0; +} + +int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, int merge_best_direction, struct ast_channel **kick_me, unsigned int num_kick) +{ + int res; + + /* Sanity check. */ + ast_assert(dst_bridge && src_bridge); + + ast_bridge_lock_both(dst_bridge, src_bridge); + res = bridge_merge_locked(dst_bridge, src_bridge, merge_best_direction, kick_me, num_kick); + ast_bridge_unlock(src_bridge); + ast_bridge_unlock(dst_bridge); + return res; +} + +/*! + * \internal + * \brief Move a bridge channel from one bridge to another. + * \since 12.0.0 + * + * \param dst_bridge Destination bridge of bridge channel move. + * \param bridge_channel Channel moving from one bridge to another. + * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge. + * + * \note The dst_bridge and bridge_channel->bridge are assumed already locked. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery) +{ + struct ast_bridge *orig_bridge; + int was_in_bridge; + int res = 0; + +/* BUGBUG need bridge move stasis event and a success/fail event. */ + if (bridge_channel->swap) { + ast_debug(1, "Moving %p(%s) into bridge %s swapping with %s\n", + bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid, + ast_channel_name(bridge_channel->swap)); + } else { + ast_debug(1, "Moving %p(%s) into bridge %s\n", + bridge_channel, ast_channel_name(bridge_channel->chan), dst_bridge->uniqueid); + } + + orig_bridge = bridge_channel->bridge; + was_in_bridge = bridge_channel->in_bridge; + + bridge_channel_pull(bridge_channel); + if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) { + /* + * The channel died as a result of being pulled. Leave it + * pointing to the original bridge. + */ + bridge_reconfigured(orig_bridge); + return -1; + } + + /* Point to new bridge.*/ + ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */ + bridge_channel_change_bridge(bridge_channel, dst_bridge); + + if (bridge_channel_push(bridge_channel)) { + /* Try to put the channel back into the original bridge. */ + if (attempt_recovery && was_in_bridge) { + /* Point back to original bridge. */ + bridge_channel_change_bridge(bridge_channel, orig_bridge); + + if (bridge_channel_push(bridge_channel)) { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + } + } else { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + } + res = -1; + } + + bridge_reconfigured(dst_bridge); + bridge_reconfigured(orig_bridge); + ao2_ref(orig_bridge, -1); + return res; +} + +/*! + * \internal + * \brief Move a channel from one bridge to another. + * \since 12.0.0 + * + * \param dst_bridge Destination bridge of bridge channel move. + * \param src_bridge Source bridge of bridge channel move. + * \param chan Channel to move. + * \param swap Channel to replace in dst_bridge. + * \param attempt_recovery TRUE if failure attempts to push channel back into original bridge. + * + * \note The dst_bridge and src_bridge are assumed already locked. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int bridge_move_locked(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery) +{ + struct ast_bridge_channel *bridge_channel; + + if (dst_bridge->dissolved || src_bridge->dissolved) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, at least one bridge is dissolved.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (ast_test_flag(&dst_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY) + || ast_test_flag(&src_bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, masquerade only.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (dst_bridge->inhibit_merge || src_bridge->inhibit_merge) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, temporarily inhibited.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + + bridge_channel = find_bridge_channel(src_bridge, chan); + if (!bridge_channel) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel not in bridge.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel leaving bridge.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + if (ast_test_flag(&bridge_channel->features->feature_flags, + AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, channel immovable.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid); + return -1; + } + + if (swap) { + struct ast_bridge_channel *bridge_channel_swap; + + bridge_channel_swap = find_bridge_channel(dst_bridge, swap); + if (!bridge_channel_swap) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s not in bridge.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid, + ast_channel_name(swap)); + return -1; + } + if (bridge_channel_swap->state != AST_BRIDGE_CHANNEL_STATE_WAIT) { + ast_debug(1, "Can't move channel %s from bridge %s into bridge %s, swap channel %s leaving bridge.\n", + ast_channel_name(chan), src_bridge->uniqueid, dst_bridge->uniqueid, + ast_channel_name(swap)); + return -1; + } + } + + bridge_channel->swap = swap; + return bridge_move_do(dst_bridge, bridge_channel, attempt_recovery); +} + +int ast_bridge_move(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_channel *chan, struct ast_channel *swap, int attempt_recovery) +{ + int res; + + ast_bridge_lock_both(dst_bridge, src_bridge); + res = bridge_move_locked(dst_bridge, src_bridge, chan, swap, attempt_recovery); + ast_bridge_unlock(src_bridge); + ast_bridge_unlock(dst_bridge); + return res; +} + +struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge *bridge = bridge_channel->bridge; + struct ast_bridge_channel *other = NULL; + + if (bridge_channel->in_bridge && bridge->num_channels == 2) { + AST_LIST_TRAVERSE(&bridge->channels, other, entry) { + if (other != bridge_channel) { + break; + } + } + } + + return other; +} + +/*! + * \internal + * \brief Lock the unreal channel stack for chan and prequalify it. + * \since 12.0.0 + * + * \param chan Unreal channel writing a frame into the channel driver. + * + * \note It is assumed that chan is already locked. + * + * \retval bridge on success with bridge and bridge_channel locked. + * \retval NULL if cannot do optimization now. + */ +static struct ast_bridge *optimize_lock_chan_stack(struct ast_channel *chan) +{ + struct ast_bridge *bridge; + struct ast_bridge_channel *bridge_channel; + + if (!AST_LIST_EMPTY(ast_channel_readq(chan))) { + return NULL; + } + bridge_channel = ast_channel_internal_bridge_channel(chan); + if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) { + return NULL; + } + bridge = bridge_channel->bridge; + if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_SIMPLE + || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT + || ast_bridge_trylock(bridge)) { + ast_bridge_channel_unlock(bridge_channel); + return NULL; + } + if (bridge->inhibit_merge + || bridge->dissolved + || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY) + || !bridge_channel->in_bridge + || !AST_LIST_EMPTY(&bridge_channel->wr_queue)) { + ast_bridge_unlock(bridge); + ast_bridge_channel_unlock(bridge_channel); + return NULL; + } + return bridge; +} + +/*! + * \internal + * \brief Lock the unreal channel stack for peer and prequalify it. + * \since 12.0.0 + * + * \param peer Other unreal channel in the pair. + * + * \retval bridge on success with bridge, bridge_channel, and peer locked. + * \retval NULL if cannot do optimization now. + */ +static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer) +{ + struct ast_bridge *bridge; + struct ast_bridge_channel *bridge_channel; + + if (ast_channel_trylock(peer)) { + return NULL; + } + if (!AST_LIST_EMPTY(ast_channel_readq(peer))) { + ast_channel_unlock(peer); + return NULL; + } + bridge_channel = ast_channel_internal_bridge_channel(peer); + if (!bridge_channel || ast_bridge_channel_trylock(bridge_channel)) { + ast_channel_unlock(peer); + return NULL; + } + bridge = bridge_channel->bridge; + if (bridge_channel->activity != AST_BRIDGE_CHANNEL_THREAD_IDLE + || bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT + || ast_bridge_trylock(bridge)) { + ast_bridge_channel_unlock(bridge_channel); + ast_channel_unlock(peer); + return NULL; + } + if (bridge->inhibit_merge + || bridge->dissolved + || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY) + || !bridge_channel->in_bridge + || !AST_LIST_EMPTY(&bridge_channel->wr_queue)) { + ast_bridge_unlock(bridge); + ast_bridge_channel_unlock(bridge_channel); + ast_channel_unlock(peer); + return NULL; + } + return bridge; +} + +/*! + * \internal + * \brief Check and attempt to swap optimize out the unreal channels. + * \since 12.0.0 + * + * \param chan_bridge + * \param chan_bridge_channel + * \param peer_bridge + * \param peer_bridge_channel + * + * \retval 1 if unreal channels failed to optimize out. + * \retval 0 if unreal channels were not optimized out. + * \retval -1 if unreal channels were optimized out. + */ +static int check_swap_optimize_out(struct ast_bridge *chan_bridge, + struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge, + struct ast_bridge_channel *peer_bridge_channel) +{ + struct ast_bridge *dst_bridge = NULL; + struct ast_bridge_channel *dst_bridge_channel = NULL; + struct ast_bridge_channel *src_bridge_channel = NULL; + int peer_priority; + int chan_priority; + int res = 0; + + if (!ast_test_flag(&chan_bridge->feature_flags, + AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM) + && !ast_test_flag(&peer_bridge->feature_flags, + AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)) { + /* + * Can swap either way. Swap to the higher priority merge + * bridge. + */ + chan_priority = chan_bridge->v_table->get_merge_priority(chan_bridge); + peer_priority = peer_bridge->v_table->get_merge_priority(peer_bridge); + if (chan_bridge->num_channels == 2 + && chan_priority <= peer_priority) { + dst_bridge = peer_bridge; + dst_bridge_channel = peer_bridge_channel; + src_bridge_channel = chan_bridge_channel; + } else if (peer_bridge->num_channels == 2 + && peer_priority <= chan_priority) { + dst_bridge = chan_bridge; + dst_bridge_channel = chan_bridge_channel; + src_bridge_channel = peer_bridge_channel; + } + } else if (chan_bridge->num_channels == 2 + && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM) + && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) { + /* Can swap optimize only one way. */ + dst_bridge = peer_bridge; + dst_bridge_channel = peer_bridge_channel; + src_bridge_channel = chan_bridge_channel; + } else if (peer_bridge->num_channels == 2 + && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM) + && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) { + /* Can swap optimize only one way. */ + dst_bridge = chan_bridge; + dst_bridge_channel = chan_bridge_channel; + src_bridge_channel = peer_bridge_channel; + } + if (dst_bridge) { + struct ast_bridge_channel *other; + + res = 1; + other = ast_bridge_channel_peer(src_bridge_channel); + if (other && other->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { + ast_debug(1, "Move-swap optimizing %s <-- %s.\n", + ast_channel_name(dst_bridge_channel->chan), + ast_channel_name(other->chan)); + + other->swap = dst_bridge_channel->chan; + if (!bridge_move_do(dst_bridge, other, 1)) { + ast_bridge_change_state(src_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + res = -1; + } + } + } + return res; +} + +/*! + * \internal + * \brief Check and attempt to merge optimize out the unreal channels. + * \since 12.0.0 + * + * \param chan_bridge + * \param chan_bridge_channel + * \param peer_bridge + * \param peer_bridge_channel + * + * \retval 0 if unreal channels were not optimized out. + * \retval -1 if unreal channels were optimized out. + */ +static int check_merge_optimize_out(struct ast_bridge *chan_bridge, + struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge, + struct ast_bridge_channel *peer_bridge_channel) +{ + struct merge_direction merge; + int res = 0; + + merge = bridge_merge_determine_direction(chan_bridge, peer_bridge); + if (!merge.dest) { + return res; + } + if (merge.src->num_channels < 2) { + ast_debug(4, "Can't optimize %s -- %s out, not enough channels in bridge %s.\n", + ast_channel_name(chan_bridge_channel->chan), + ast_channel_name(peer_bridge_channel->chan), + merge.src->uniqueid); + } else if ((2 + 2) < merge.dest->num_channels + merge.src->num_channels + && !(merge.dest->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) + && (!ast_test_flag(&merge.dest->feature_flags, AST_BRIDGE_FLAG_SMART) + || !(merge.dest->allowed_capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX))) { + ast_debug(4, "Can't optimize %s -- %s out, multimix is needed and it cannot be acquired.\n", + ast_channel_name(chan_bridge_channel->chan), + ast_channel_name(peer_bridge_channel->chan)); + } else { + struct ast_bridge_channel *kick_me[] = { + chan_bridge_channel, + peer_bridge_channel, + }; + + ast_debug(1, "Merge optimizing %s -- %s out.\n", + ast_channel_name(chan_bridge_channel->chan), + ast_channel_name(peer_bridge_channel->chan)); + + bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me)); + res = -1; + } + + return res; +} + +int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer) +{ + struct ast_bridge *chan_bridge; + struct ast_bridge *peer_bridge; + struct ast_bridge_channel *chan_bridge_channel; + struct ast_bridge_channel *peer_bridge_channel; + int res = 0; + + chan_bridge = optimize_lock_chan_stack(chan); + if (!chan_bridge) { + return res; + } + chan_bridge_channel = ast_channel_internal_bridge_channel(chan); + + peer_bridge = optimize_lock_peer_stack(peer); + if (peer_bridge) { + peer_bridge_channel = ast_channel_internal_bridge_channel(peer); + + res = check_swap_optimize_out(chan_bridge, chan_bridge_channel, + peer_bridge, peer_bridge_channel); + if (!res) { + res = check_merge_optimize_out(chan_bridge, chan_bridge_channel, + peer_bridge, peer_bridge_channel); + } else if (0 < res) { + res = 0; + } + + /* Release peer locks. */ + ast_bridge_unlock(peer_bridge); + ast_bridge_channel_unlock(peer_bridge_channel); + ast_channel_unlock(peer); + } + + /* Release chan locks. */ + ast_bridge_unlock(chan_bridge); + ast_bridge_channel_unlock(chan_bridge_channel); + + return res; +} + +/*! + * \internal + * \brief Adjust the bridge merge inhibit request count. + * \since 12.0.0 + * + * \param bridge What to operate on. + * \param request Inhibit request increment. + * (Positive to add requests. Negative to remove requests.) + * + * \note This function assumes bridge is locked. + * + * \return Nothing + */ +static void bridge_merge_inhibit_nolock(struct ast_bridge *bridge, int request) +{ + int new_request; + + new_request = bridge->inhibit_merge + request; + ast_assert(0 <= new_request); + bridge->inhibit_merge = new_request; +} + +void ast_bridge_merge_inhibit(struct ast_bridge *bridge, int request) +{ + ast_bridge_lock(bridge); + bridge_merge_inhibit_nolock(bridge, request); + ast_bridge_unlock(bridge); +} + +struct ast_bridge *ast_bridge_channel_merge_inhibit(struct ast_bridge_channel *bridge_channel, int request) +{ + struct ast_bridge *bridge; + + ast_bridge_channel_lock_bridge(bridge_channel); + bridge = bridge_channel->bridge; + ao2_ref(bridge, +1); + bridge_merge_inhibit_nolock(bridge, request); + ast_bridge_unlock(bridge); + return bridge; +} + +int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan) +{ + struct ast_bridge_channel *bridge_channel; +/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */ +/* BUGBUG suspend/unsuspend needs to be rethought. The caller must block until it has successfully suspended the channel for temporary control. */ +/* BUGBUG external suspend/unsuspend needs to be eliminated. The channel may be playing a file at the time and stealing it then is not good. */ + + ast_bridge_lock(bridge); + + if (!(bridge_channel = find_bridge_channel(bridge, chan))) { + ast_bridge_unlock(bridge); + return -1; + } + + bridge_channel_suspend_nolock(bridge_channel); + + ast_bridge_unlock(bridge); + + return 0; +} + +int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan) +{ + struct ast_bridge_channel *bridge_channel; +/* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */ + + ast_bridge_lock(bridge); + + if (!(bridge_channel = find_bridge_channel(bridge, chan))) { + ast_bridge_unlock(bridge); + return -1; + } + + bridge_channel_unsuspend_nolock(bridge_channel); + + ast_bridge_unlock(bridge); + + return 0; +} + +void ast_bridge_technology_suspend(struct ast_bridge_technology *technology) +{ + technology->suspended = 1; +} + +void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology) +{ +/* BUGBUG unsuspending a bridge technology probably needs to prod all existing bridges to see if they should start using it. */ + technology->suspended = 0; +} + +int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_hook_callback callback, const char *dtmf) +{ + if (ARRAY_LEN(builtin_features_handlers) <= feature + || builtin_features_handlers[feature]) { + return -1; + } + + if (!ast_strlen_zero(dtmf)) { + ast_copy_string(builtin_features_dtmf[feature], dtmf, sizeof(builtin_features_dtmf[feature])); + } + + builtin_features_handlers[feature] = callback; + + return 0; +} + +int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature) +{ + if (ARRAY_LEN(builtin_features_handlers) <= feature + || !builtin_features_handlers[feature]) { + return -1; + } + + builtin_features_handlers[feature] = NULL; + + return 0; +} + +int ast_bridge_interval_register(enum ast_bridge_builtin_interval interval, ast_bridge_builtin_set_limits_fn callback) +{ + if (ARRAY_LEN(builtin_interval_handlers) <= interval + || builtin_interval_handlers[interval]) { + return -1; + } + + builtin_interval_handlers[interval] = callback; + + return 0; +} + +int ast_bridge_interval_unregister(enum ast_bridge_builtin_interval interval) +{ + if (ARRAY_LEN(builtin_interval_handlers) <= interval + || !builtin_interval_handlers[interval]) { + return -1; + } + + builtin_interval_handlers[interval] = NULL; + + return 0; + +} + +/*! + * \internal + * \brief Bridge hook destructor. + * \since 12.0.0 + * + * \param vhook Object to destroy. + * + * \return Nothing + */ +static void bridge_hook_destroy(void *vhook) +{ + struct ast_bridge_hook *hook = vhook; + + if (hook->destructor) { + hook->destructor(hook->hook_pvt); + } +} + +/*! + * \internal + * \brief Allocate and setup a generic bridge hook. + * \since 12.0.0 + * + * \param size How big an object to allocate. + * \param callback Function to execute upon activation + * \param hook_pvt Unique data + * \param destructor Optional destructor callback for hook_pvt data + * \param remove_on_pull TRUE if remove the hook when the channel is pulled from the bridge. + * + * \retval hook on success. + * \retval NULL on error. + */ +static struct ast_bridge_hook *bridge_hook_generic(size_t size, + ast_bridge_hook_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + struct ast_bridge_hook *hook; + + /* Allocate new hook and setup it's basic variables */ + hook = ao2_alloc_options(size, bridge_hook_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (hook) { + hook->callback = callback; + hook->destructor = destructor; + hook->hook_pvt = hook_pvt; + hook->remove_on_pull = remove_on_pull; + } + + return hook; +} + +int ast_bridge_dtmf_hook(struct ast_bridge_features *features, + const char *dtmf, + ast_bridge_hook_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + struct ast_bridge_hook *hook; + int res; + + /* Allocate new hook and setup it's various variables */ + hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor, + remove_on_pull); + if (!hook) { + return -1; + } + ast_copy_string(hook->parms.dtmf.code, dtmf, sizeof(hook->parms.dtmf.code)); + + /* Once done we put it in the container. */ + res = ao2_link(features->dtmf_hooks, hook) ? 0 : -1; + ao2_ref(hook, -1); + + return res; +} + +int ast_bridge_hangup_hook(struct ast_bridge_features *features, + ast_bridge_hook_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + struct ast_bridge_hook *hook; + int res; + + /* Allocate new hook and setup it's various variables */ + hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor, + remove_on_pull); + if (!hook) { + return -1; + } + + /* Once done we put it in the container. */ + res = ao2_link(features->hangup_hooks, hook) ? 0 : -1; + ao2_ref(hook, -1); + + return res; +} + +int ast_bridge_join_hook(struct ast_bridge_features *features, + ast_bridge_hook_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + struct ast_bridge_hook *hook; + int res; + + /* Allocate new hook and setup it's various variables */ + hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor, + remove_on_pull); + if (!hook) { + return -1; + } + + /* Once done we put it in the container. */ + res = ao2_link(features->join_hooks, hook) ? 0 : -1; + ao2_ref(hook, -1); + + return res; +} + +int ast_bridge_leave_hook(struct ast_bridge_features *features, + ast_bridge_hook_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + struct ast_bridge_hook *hook; + int res; + + /* Allocate new hook and setup it's various variables */ + hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor, + remove_on_pull); + if (!hook) { + return -1; + } + + /* Once done we put it in the container. */ + res = ao2_link(features->leave_hooks, hook) ? 0 : -1; + ao2_ref(hook, -1); + + return res; +} + +void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features, + ast_bridge_talking_indicate_callback talker_cb, + ast_bridge_talking_indicate_destructor talker_destructor, + void *pvt_data) +{ + features->talker_cb = talker_cb; + features->talker_destructor_cb = talker_destructor; + features->talker_pvt_data = pvt_data; +} + +int ast_bridge_interval_hook(struct ast_bridge_features *features, + unsigned int interval, + ast_bridge_hook_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + struct ast_bridge_hook *hook; + int res; + + if (!features ||!interval || !callback) { + return -1; + } + + if (!features->interval_timer) { + if (!(features->interval_timer = ast_timer_open())) { + ast_log(LOG_ERROR, "Failed to open a timer when adding a timed bridging feature.\n"); + return -1; + } + ast_timer_set_rate(features->interval_timer, BRIDGE_FEATURES_INTERVAL_RATE); + } + + /* Allocate new hook and setup it's various variables */ + hook = bridge_hook_generic(sizeof(*hook), callback, hook_pvt, destructor, + remove_on_pull); + if (!hook) { + return -1; + } + hook->parms.timer.interval = interval; + hook->parms.timer.trip_time = ast_tvadd(ast_tvnow(), ast_samp2tv(hook->parms.timer.interval, 1000)); + hook->parms.timer.seqno = ast_atomic_fetchadd_int((int *) &features->interval_sequence, +1); + + ast_debug(1, "Putting interval hook %p with interval %u in the heap on features %p\n", + hook, hook->parms.timer.interval, features); + ast_heap_wrlock(features->interval_hooks); + res = ast_heap_push(features->interval_hooks, hook); + if (res) { + /* Could not push the hook onto the heap. */ + ao2_ref(hook, -1); + } + ast_heap_unlock(features->interval_hooks); + + return res ? -1 : 0; +} + +int ast_bridge_features_enable(struct ast_bridge_features *features, + enum ast_bridge_builtin_feature feature, + const char *dtmf, + void *config, + ast_bridge_hook_pvt_destructor destructor, + int remove_on_pull) +{ + if (ARRAY_LEN(builtin_features_handlers) <= feature + || !builtin_features_handlers[feature]) { + return -1; + } + + /* If no alternate DTMF stream was provided use the default one */ + if (ast_strlen_zero(dtmf)) { + dtmf = builtin_features_dtmf[feature]; + /* If no DTMF is still available (ie: it has been disabled) then error out now */ + if (ast_strlen_zero(dtmf)) { + ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n", + feature, features); + return -1; + } + } + + /* + * The rest is basically pretty easy. We create another hook + * using the built in feature's DTMF callback. Easy as pie. + */ + return ast_bridge_dtmf_hook(features, dtmf, builtin_features_handlers[feature], + config, destructor, remove_on_pull); +} + +int ast_bridge_features_limits_construct(struct ast_bridge_features_limits *limits) +{ + memset(limits, 0, sizeof(*limits)); + + if (ast_string_field_init(limits, 256)) { + ast_free(limits); + return -1; + } + + return 0; +} + +void ast_bridge_features_limits_destroy(struct ast_bridge_features_limits *limits) +{ + ast_string_field_free_memory(limits); +} + +int ast_bridge_features_set_limits(struct ast_bridge_features *features, struct ast_bridge_features_limits *limits, int remove_on_pull) +{ + if (builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS]) { + ast_bridge_builtin_set_limits_fn bridge_features_set_limits_callback; + + bridge_features_set_limits_callback = builtin_interval_handlers[AST_BRIDGE_BUILTIN_INTERVAL_LIMITS]; + return bridge_features_set_limits_callback(features, limits, remove_on_pull); + } + + ast_log(LOG_ERROR, "Attempted to set limits without an AST_BRIDGE_BUILTIN_INTERVAL_LIMITS callback registered.\n"); + return -1; +} + +void ast_bridge_features_set_flag(struct ast_bridge_features *features, unsigned int flag) +{ + ast_set_flag(&features->feature_flags, flag); + features->usable = 1; +} + +/*! + * \internal + * \brief ao2 object match remove_on_pull hooks. + * \since 12.0.0 + * + * \param obj Feature hook object. + * \param arg Not used + * \param flags Not used + * + * \retval CMP_MATCH if hook has remove_on_pull set. + * \retval 0 if not match. + */ +static int hook_remove_on_pull_match(void *obj, void *arg, int flags) +{ + struct ast_bridge_hook *hook = obj; + + if (hook->remove_on_pull) { + return CMP_MATCH; + } else { + return 0; + } +} + +/*! + * \internal + * \brief Remove all remove_on_pull hooks in the container. + * \since 12.0.0 + * + * \param hooks Hooks container to work on. + * + * \return Nothing + */ +static void hooks_remove_on_pull_container(struct ao2_container *hooks) +{ + ao2_callback(hooks, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, + hook_remove_on_pull_match, NULL); +} + +/*! + * \internal + * \brief Remove all remove_on_pull hooks in the heap. + * \since 12.0.0 + * + * \param hooks Hooks heap to work on. + * + * \return Nothing + */ +static void hooks_remove_on_pull_heap(struct ast_heap *hooks) +{ + struct ast_bridge_hook *hook; + int changed; + + ast_heap_wrlock(hooks); + do { + int idx; + + changed = 0; + for (idx = ast_heap_size(hooks); idx; --idx) { + hook = ast_heap_peek(hooks, idx); + if (hook->remove_on_pull) { + ast_heap_remove(hooks, hook); + ao2_ref(hook, -1); + changed = 1; + } + } + } while (changed); + ast_heap_unlock(hooks); +} + +/*! + * \internal + * \brief Remove marked bridge channel feature hooks. + * \since 12.0.0 + * + * \param features Bridge featues structure + * + * \return Nothing + */ +static void bridge_features_remove_on_pull(struct ast_bridge_features *features) +{ + hooks_remove_on_pull_container(features->dtmf_hooks); + hooks_remove_on_pull_container(features->hangup_hooks); + hooks_remove_on_pull_container(features->join_hooks); + hooks_remove_on_pull_container(features->leave_hooks); + hooks_remove_on_pull_heap(features->interval_hooks); +} + +static int interval_hook_time_cmp(void *a, void *b) +{ + struct ast_bridge_hook *hook_a = a; + struct ast_bridge_hook *hook_b = b; + int cmp; + + cmp = ast_tvcmp(hook_b->parms.timer.trip_time, hook_a->parms.timer.trip_time); + if (cmp) { + return cmp; + } + + cmp = hook_b->parms.timer.seqno - hook_a->parms.timer.seqno; + return cmp; +} + +/*! + * \internal + * \brief DTMF hook container sort comparison function. + * \since 12.0.0 + * + * \param obj_left pointer to the (user-defined part) of an object. + * \param obj_right pointer to the (user-defined part) of an object. + * \param flags flags from ao2_callback() + * OBJ_POINTER - if set, 'obj_right', is an object. + * OBJ_KEY - if set, 'obj_right', is a search key item that is not an object. + * OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object. + * + * \retval <0 if obj_left < obj_right + * \retval =0 if obj_left == obj_right + * \retval >0 if obj_left > obj_right + */ +static int bridge_dtmf_hook_sort(const void *obj_left, const void *obj_right, int flags) +{ + const struct ast_bridge_hook *hook_left = obj_left; + const struct ast_bridge_hook *hook_right = obj_right; + const char *right_key = obj_right; + int cmp; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + default: + case OBJ_POINTER: + right_key = hook_right->parms.dtmf.code; + /* Fall through */ + case OBJ_KEY: + cmp = strcasecmp(hook_left->parms.dtmf.code, right_key); + break; + case OBJ_PARTIAL_KEY: + cmp = strncasecmp(hook_left->parms.dtmf.code, right_key, strlen(right_key)); + break; + } + return cmp; +} + +/* BUGBUG make ast_bridge_features_init() static when make ast_bridge_join() requires features to be allocated. */ +int ast_bridge_features_init(struct ast_bridge_features *features) +{ + /* Zero out the structure */ + memset(features, 0, sizeof(*features)); + + /* Initialize the DTMF hooks container */ + features->dtmf_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, + AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_dtmf_hook_sort, NULL); + if (!features->dtmf_hooks) { + return -1; + } + + /* Initialize the hangup hooks container */ + features->hangup_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, + NULL); + if (!features->hangup_hooks) { + return -1; + } + + /* Initialize the join hooks container */ + features->join_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, + NULL); + if (!features->join_hooks) { + return -1; + } + + /* Initialize the leave hooks container */ + features->leave_hooks = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, + NULL); + if (!features->leave_hooks) { + return -1; + } + + /* Initialize the interval hooks heap */ + features->interval_hooks = ast_heap_create(8, interval_hook_time_cmp, + offsetof(struct ast_bridge_hook, parms.timer.heap_index)); + if (!features->interval_hooks) { + return -1; + } + + return 0; +} + +/* BUGBUG make ast_bridge_features_cleanup() static when make ast_bridge_join() requires features to be allocated. */ +void ast_bridge_features_cleanup(struct ast_bridge_features *features) +{ + struct ast_bridge_hook *hook; + + /* Destroy the interval hooks heap. */ + if (features->interval_hooks) { + while ((hook = ast_heap_pop(features->interval_hooks))) { + ao2_ref(hook, -1); + } + features->interval_hooks = ast_heap_destroy(features->interval_hooks); + } + + if (features->interval_timer) { + ast_timer_close(features->interval_timer); + features->interval_timer = NULL; + } + + /* If the features contains a limits pvt, destroy that as well. */ + if (features->limits) { + ast_bridge_features_limits_destroy(features->limits); + ast_free(features->limits); + features->limits = NULL; + } + + if (features->talker_destructor_cb && features->talker_pvt_data) { + features->talker_destructor_cb(features->talker_pvt_data); + features->talker_pvt_data = NULL; + } + + /* Destroy the leave hooks container. */ + ao2_cleanup(features->leave_hooks); + features->leave_hooks = NULL; + + /* Destroy the join hooks container. */ + ao2_cleanup(features->join_hooks); + features->join_hooks = NULL; + + /* Destroy the hangup hooks container. */ + ao2_cleanup(features->hangup_hooks); + features->hangup_hooks = NULL; + + /* Destroy the DTMF hooks container. */ + ao2_cleanup(features->dtmf_hooks); + features->dtmf_hooks = NULL; +} + +void ast_bridge_features_destroy(struct ast_bridge_features *features) +{ + if (!features) { + return; + } + ast_bridge_features_cleanup(features); + ast_free(features); +} + +struct ast_bridge_features *ast_bridge_features_new(void) +{ + struct ast_bridge_features *features; + + features = ast_malloc(sizeof(*features)); + if (features) { + if (ast_bridge_features_init(features)) { + ast_bridge_features_destroy(features); + features = NULL; + } + } + + return features; +} + +void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval) +{ + ast_bridge_lock(bridge); + bridge->internal_mixing_interval = mixing_interval; + ast_bridge_unlock(bridge); +} + +void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate) +{ + ast_bridge_lock(bridge); + bridge->internal_sample_rate = sample_rate; + ast_bridge_unlock(bridge); +} + +static void cleanup_video_mode(struct ast_bridge *bridge) +{ + switch (bridge->video_mode.mode) { + case AST_BRIDGE_VIDEO_MODE_NONE: + break; + case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: + if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) { + ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc); + } + break; + case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: + if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) { + ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc); + } + if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) { + ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc); + } + } + memset(&bridge->video_mode, 0, sizeof(bridge->video_mode)); +} + +void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan) +{ + ast_bridge_lock(bridge); + cleanup_video_mode(bridge); + bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC; + bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan); + ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan)); + ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE); + ast_bridge_unlock(bridge); +} + +void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge) +{ + ast_bridge_lock(bridge); + cleanup_video_mode(bridge); + bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC; + ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode); + ast_bridge_unlock(bridge); +} + +void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe) +{ + struct ast_bridge_video_talker_src_data *data; + /* If the channel doesn't support video, we don't care about it */ + if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) { + return; + } + + ast_bridge_lock(bridge); + data = &bridge->video_mode.mode_data.talker_src_data; + + if (data->chan_vsrc == chan) { + data->average_talking_energy = talker_energy; + } else if ((data->average_talking_energy < talker_energy) && is_keyframe) { + if (data->chan_old_vsrc) { + ast_channel_unref(data->chan_old_vsrc); + } + if (data->chan_vsrc) { + data->chan_old_vsrc = data->chan_vsrc; + ast_indicate(data->chan_old_vsrc, AST_CONTROL_VIDUPDATE); + } + data->chan_vsrc = ast_channel_ref(chan); + data->average_talking_energy = talker_energy; + ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc)); + ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE); + } else if ((data->average_talking_energy < talker_energy) && !is_keyframe) { + ast_indicate(chan, AST_CONTROL_VIDUPDATE); + } else if (!data->chan_vsrc && is_keyframe) { + data->chan_vsrc = ast_channel_ref(chan); + data->average_talking_energy = talker_energy; + ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc)); + ast_indicate(chan, AST_CONTROL_VIDUPDATE); + } else if (!data->chan_old_vsrc && is_keyframe) { + data->chan_old_vsrc = ast_channel_ref(chan); + ast_indicate(chan, AST_CONTROL_VIDUPDATE); + } + ast_bridge_unlock(bridge); +} + +int ast_bridge_number_video_src(struct ast_bridge *bridge) +{ + int res = 0; + + ast_bridge_lock(bridge); + switch (bridge->video_mode.mode) { + case AST_BRIDGE_VIDEO_MODE_NONE: + break; + case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: + if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) { + res = 1; + } + break; + case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: + if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) { + res++; + } + if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) { + res++; + } + } + ast_bridge_unlock(bridge); + return res; +} + +int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan) +{ + int res = 0; + + ast_bridge_lock(bridge); + switch (bridge->video_mode.mode) { + case AST_BRIDGE_VIDEO_MODE_NONE: + break; + case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: + if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) { + res = 1; + } + break; + case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: + if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) { + res = 1; + } else if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) { + res = 2; + } + + } + ast_bridge_unlock(bridge); + return res; +} + +void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan) +{ + ast_bridge_lock(bridge); + switch (bridge->video_mode.mode) { + case AST_BRIDGE_VIDEO_MODE_NONE: + break; + case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: + if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) { + if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) { + ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc); + } + bridge->video_mode.mode_data.single_src_data.chan_vsrc = NULL; + } + break; + case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: + if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) { + if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) { + ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc); + } + bridge->video_mode.mode_data.talker_src_data.chan_vsrc = NULL; + bridge->video_mode.mode_data.talker_src_data.average_talking_energy = 0; + } + if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) { + if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) { + ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc); + } + bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL; + } } + ast_bridge_unlock(bridge); +} - if (bridge_channel->bridge) { - ao2_ref(bridge_channel->bridge, -1); - bridge_channel->bridge = NULL; +static int channel_hash(const void *obj, int flags) +{ + const struct ast_channel *chan = obj; + const char *name = obj; + int hash; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + default: + case OBJ_POINTER: + name = ast_channel_name(chan); + /* Fall through */ + case OBJ_KEY: + hash = ast_str_hash(name); + break; + case OBJ_PARTIAL_KEY: + /* Should never happen in hash callback. */ + ast_assert(0); + hash = 0; + break; } - /* Destroy elements of the bridge channel structure and the bridge channel structure itself */ - ast_cond_destroy(&bridge_channel->cond); + return hash; } -static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge) +static int channel_cmp(void *obj, void *arg, int flags) { - struct ast_bridge_channel *bridge_channel; + const struct ast_channel *left = obj; + const struct ast_channel *right = arg; + const char *right_name = arg; + int cmp; - bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy); - if (!bridge_channel) { - return NULL; - } - ast_cond_init(&bridge_channel->cond, NULL); - if (bridge) { - bridge_channel->bridge = bridge; - ao2_ref(bridge_channel->bridge, +1); + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + default: + case OBJ_POINTER: + right_name = ast_channel_name(right); + /* Fall through */ + case OBJ_KEY: + cmp = strcmp(ast_channel_name(left), right_name); + break; + case OBJ_PARTIAL_KEY: + cmp = strncmp(ast_channel_name(left), right_name, strlen(right_name)); + break; } - return bridge_channel; + return cmp ? 0 : CMP_MATCH; } -enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, - struct ast_channel *chan, - struct ast_channel *swap, - struct ast_bridge_features *features, - struct ast_bridge_tech_optimizations *tech_args) +struct ao2_container *ast_bridge_peers_nolock(struct ast_bridge *bridge) { - struct ast_bridge_channel *bridge_channel; - enum ast_bridge_channel_state state; + struct ao2_container *channels; + struct ast_bridge_channel *iter; - bridge_channel = bridge_channel_alloc(bridge); - if (!bridge_channel) { - return AST_BRIDGE_CHANNEL_STATE_HANGUP; + channels = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, + 13, channel_hash, channel_cmp); + if (!channels) { + return NULL; } - if (tech_args) { - bridge_channel->tech_args = *tech_args; + + AST_LIST_TRAVERSE(&bridge->channels, iter, entry) { + ao2_link(channels, iter->chan); } - /* Initialize various other elements of the bridge channel structure that we can't do above */ - bridge_channel->chan = chan; - bridge_channel->swap = swap; - bridge_channel->features = features; + return channels; +} - state = bridge_channel_join(bridge_channel); +struct ao2_container *ast_bridge_peers(struct ast_bridge *bridge) +{ + struct ao2_container *channels; - /* Cleanup all the data in the bridge channel after it leaves the bridge. */ - bridge_channel->chan = NULL; - bridge_channel->swap = NULL; - bridge_channel->features = NULL; + ast_bridge_lock(bridge); + channels = ast_bridge_peers_nolock(bridge); + ast_bridge_unlock(bridge); - ao2_ref(bridge_channel, -1); - return state; + return channels; } -/*! \brief Thread responsible for imparted bridged channels */ -static void *bridge_channel_thread(void *data) +struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast_channel *chan) { - struct ast_bridge_channel *bridge_channel = data; - enum ast_bridge_channel_state state; - - if (bridge_channel->callid) { - ast_callid_threadassoc_add(bridge_channel->callid); + struct ast_channel *peer = NULL; + struct ast_bridge_channel *iter; + + /* Asking for the peer channel only makes sense on a two-party bridge. */ + if (bridge->num_channels == 2 + && bridge->technology->capabilities + & (AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX)) { + int in_bridge = 0; + + AST_LIST_TRAVERSE(&bridge->channels, iter, entry) { + if (iter->chan != chan) { + peer = iter->chan; + } else { + in_bridge = 1; + } + } + if (in_bridge && peer) { + ast_channel_ref(peer); + } else { + peer = NULL; + } } - state = bridge_channel_join(bridge_channel); - - /* If no other thread is going to take the channel then hang it up, or else we would have to service it until something else came along */ - if (bridge_channel->allow_impart_hangup && (state == AST_BRIDGE_CHANNEL_STATE_END || state == AST_BRIDGE_CHANNEL_STATE_HANGUP)) { - ast_hangup(bridge_channel->chan); - } + return peer; +} - /* cleanup */ - ao2_lock(bridge_channel); - bridge_channel->chan = NULL; - bridge_channel->swap = NULL; - bridge_channel->features = NULL; - ao2_unlock(bridge_channel); +struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan) +{ + struct ast_channel *peer; - ao2_ref(bridge_channel, -1); + ast_bridge_lock(bridge); + peer = ast_bridge_peer_nolock(bridge, chan); + ast_bridge_unlock(bridge); - return NULL; + return peer; } -int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, int allow_hangup) +/*! + * \internal + * \brief Transfer an entire bridge to a specific destination. + * + * This creates a local channel to dial out and swaps the called local channel + * with the transferer channel. By doing so, all participants in the bridge are + * connected to the specified destination. + * + * While this means of transferring would work for both two-party and multi-party + * bridges, this method is only used for multi-party bridges since this method would + * be less efficient for two-party bridges. + * + * \param transferer The channel performing a transfer + * \param bridge The bridge where the transfer is being performed + * \param exten The destination extension for the blind transfer + * \param context The destination context for the blind transfer + * \param hook Framehook to attach to local channel + * \return The success or failure of the operation + */ +static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transferer, + struct ast_bridge *bridge, const char *exten, const char *context, + transfer_channel_cb new_channel_cb, void *user_data) { - struct ast_bridge_channel *bridge_channel; + struct ast_channel *local; + char chan_name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2]; + int cause; + + snprintf(chan_name, sizeof(chan_name), "%s@%s", exten, context); + local = ast_request("Local", ast_channel_nativeformats(transferer), transferer, + chan_name, &cause); + if (!local) { + return AST_BRIDGE_TRANSFER_FAIL; + } - /* Try to allocate a structure for the bridge channel */ - bridge_channel = bridge_channel_alloc(bridge); - if (!bridge_channel) { - return -1; + if (new_channel_cb) { + new_channel_cb(local, user_data); } - /* Setup various parameters */ - bridge_channel->chan = chan; - bridge_channel->swap = swap; - bridge_channel->features = features; - bridge_channel->allow_impart_hangup = allow_hangup; - bridge_channel->callid = ast_read_threadstorage_callid(); + if (ast_call(local, chan_name, 0)) { + ast_hangup(local); + return AST_BRIDGE_TRANSFER_FAIL; + } + if (ast_bridge_impart(bridge, local, transferer, NULL, 1)) { + ast_hangup(local); + return AST_BRIDGE_TRANSFER_FAIL; + } + return AST_BRIDGE_TRANSFER_SUCCESS; +} - /* Actually create the thread that will handle the channel */ - if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_channel)) { - ao2_ref(bridge_channel, -1); - return -1; +/*! + * \internal + * \brief Get the transferee channel + * + * This is only applicable to cases where a transfer is occurring on a + * two-party bridge. The channels container passed in is expected to only + * contain two channels, the transferer and the transferee. The transferer + * channel is passed in as a parameter to ensure we don't return it as + * the transferee channel. + * + * \param channels A two-channel container containing the transferer and transferee + * \param transferer The party that is transfering the call + * \return The party that is being transferred + */ +static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer) +{ + struct ao2_iterator channel_iter; + struct ast_channel *transferee; + + for (channel_iter = ao2_iterator_init(channels, 0); + (transferee = ao2_iterator_next(&channel_iter)); + ao2_cleanup(transferee)) { + if (transferee != transferer) { + break; + } } - return 0; + ao2_iterator_destroy(&channel_iter); + return transferee; } -int ast_bridge_depart(struct ast_bridge *bridge, struct ast_channel *chan) +/*! + * \internal + * \brief Queue a blind transfer action on a transferee bridge channel + * + * This is only relevant for when a blind transfer is performed on a two-party + * bridge. The transferee's bridge channel will have a blind transfer bridge + * action queued onto it, resulting in the party being redirected to a new + * destination + * + * \param transferee The channel to have the action queued on + * \param exten The destination extension for the transferee + * \param context The destination context for the transferee + * \param hook Frame hook to attach to transferee + * \retval 0 Successfully queued the action + * \retval non-zero Failed to queue the action + */ +static int bridge_channel_queue_blind_transfer(struct ast_channel *transferee, + const char *exten, const char *context, + transfer_channel_cb new_channel_cb, void *user_data) { - struct ast_bridge_channel *bridge_channel; - pthread_t thread; + RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup); + struct blind_transfer_data blind_data; - ao2_lock(bridge); + ast_channel_lock(transferee); + transferee_bridge_channel = ast_channel_get_bridge_channel(transferee); + ast_channel_unlock(transferee); - /* Try to find the channel that we want to depart */ - if (!(bridge_channel = find_bridge_channel(bridge, chan))) { - ao2_unlock(bridge); + if (!transferee_bridge_channel) { return -1; } - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DEPART); - thread = bridge_channel->thread; + if (new_channel_cb) { + new_channel_cb(transferee, user_data); + } - ao2_unlock(bridge); + ast_copy_string(blind_data.exten, exten, sizeof(blind_data.exten)); + ast_copy_string(blind_data.context, context, sizeof(blind_data.context)); - pthread_join(thread, NULL); +/* BUGBUG Why doesn't this function return success/failure? */ + ast_bridge_channel_queue_action_data(transferee_bridge_channel, + AST_BRIDGE_ACTION_BLIND_TRANSFER, &blind_data, sizeof(blind_data)); return 0; } -int ast_bridge_remove(struct ast_bridge *bridge, struct ast_channel *chan) +enum try_parking_result { + PARKING_SUCCESS, + PARKING_FAILURE, + PARKING_NOT_APPLICABLE, +}; + +static enum try_parking_result try_parking(struct ast_bridge *bridge, struct ast_channel *transferer, + const char *exten, const char *context) { - struct ast_bridge_channel *bridge_channel; + /* BUGBUG The following is all commented out because the functionality is not + * present yet. The functions referenced here are available at team/jrose/bridge_projects. + * Once the code there has been merged into team/group/bridge_construction, + * this can be uncommented and tested + */ - ao2_lock(bridge); +#if 0 + RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup); + struct ast_exten *parking_exten; - /* Try to find the channel that we want to remove */ - if (!(bridge_channel = find_bridge_channel(bridge, chan))) { - ao2_unlock(bridge); - return -1; - } + ast_channel_lock(transferer); + transfer_bridge_channel = ast_channel_get_bridge_channel(transferer); + ast_channel_unlock(transferer); - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP); + if (!transfer_bridge_channel) { + return PARKING_FAILURE; + } - ao2_unlock(bridge); + parking_exten = ast_get_parking_exten(exten, NULL, context); + if (parking_exten) { + return ast_park_blind_xfer(bridge, transferer, parking_exten) == 0 ? + PARKING_SUCCESS : PARKING_FAILURE; + } +#endif - return 0; + return PARKING_NOT_APPLICABLE; } -int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1) +/*! + * \internal + * \brief Set the BLINDTRANSFER variable as appropriate on channels involved in the transfer + * + * The transferer channel will have its BLINDTRANSFER variable set the same as its BRIDGEPEER + * variable. This will account for all channels that it is bridged to. The other channels + * involved in the transfer will have their BLINDTRANSFER variable set to the transferer + * channel's name. + * + * \param transferer The channel performing the blind transfer + * \param channels The channels belonging to the bridge + */ +static void set_blind_transfer_variables(struct ast_channel *transferer, struct ao2_container *channels) { - struct ast_bridge_channel *bridge_channel; + struct ao2_iterator iter; + struct ast_channel *chan; + const char *transferer_name; + const char *transferer_bridgepeer; + + ast_channel_lock(transferer); + transferer_name = ast_strdupa(ast_channel_name(transferer)); + transferer_bridgepeer = ast_strdupa(S_OR(pbx_builtin_getvar_helper(transferer, "BRIDGEPEER"), "")); + ast_channel_unlock(transferer); + + for (iter = ao2_iterator_init(channels, 0); + (chan = ao2_iterator_next(&iter)); + ao2_cleanup(chan)) { + if (chan == transferer) { + pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_bridgepeer); + } else { + pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", transferer_name); + } + } - ao2_lock(bridge0); - ao2_lock(bridge1); + ao2_iterator_destroy(&iter); +} - /* If the first bridge currently has 2 channels and is not capable of becoming a multimixing bridge we can not merge */ - if (bridge0->num + bridge1->num > 2 - && !(bridge0->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) - && !ast_test_flag(&bridge0->feature_flags, AST_BRIDGE_FLAG_SMART)) { - ao2_unlock(bridge1); - ao2_unlock(bridge0); - ast_debug(1, "Can't merge bridge %p into bridge %p, multimix is needed and it could not be acquired.\n", bridge1, bridge0); - return -1; - } +enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer, + const char *exten, const char *context, + transfer_channel_cb new_channel_cb, void *user_data) +{ + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup); + int do_bridge_transfer; + int transfer_prohibited; + enum try_parking_result parking_result; - ast_debug(1, "Merging channels from bridge %p into bridge %p\n", bridge1, bridge0); + ast_channel_lock(transferer); + bridge = ast_channel_get_bridge(transferer); + ast_channel_unlock(transferer); - /* Perform smart bridge operation on bridge we are merging into so it can change bridge technology if needed */ - if (smart_bridge_operation(bridge0, NULL, bridge0->num + bridge1->num)) { - ao2_unlock(bridge1); - ao2_unlock(bridge0); - ast_debug(1, "Can't merge bridge %p into bridge %p, tried to perform smart bridge operation and failed.\n", bridge1, bridge0); - return -1; + if (!bridge) { + return AST_BRIDGE_TRANSFER_INVALID; } - /* If a thread is currently executing on bridge1 tell it to stop */ - if (bridge1->thread) { - ast_debug(1, "Telling bridge thread on bridge %p to stop as it is being merged into %p\n", bridge1, bridge0); - bridge1->thread = AST_PTHREADT_STOP; + parking_result = try_parking(bridge, transferer, exten, context); + switch (parking_result) { + case PARKING_SUCCESS: + return AST_BRIDGE_TRANSFER_SUCCESS; + case PARKING_FAILURE: + return AST_BRIDGE_TRANSFER_FAIL; + case PARKING_NOT_APPLICABLE: + default: + break; } - /* Move channels from bridge1 over to bridge0 */ - while ((bridge_channel = AST_LIST_REMOVE_HEAD(&bridge1->channels, entry))) { - /* Tell the technology handling bridge1 that the bridge channel is leaving */ - if (bridge1->technology->leave) { - ast_debug(1, "Giving bridge technology %s notification that %p is leaving bridge %p\n", bridge1->technology->name, bridge_channel, bridge1); - if (bridge1->technology->leave(bridge1, bridge_channel)) { - ast_debug(1, "Bridge technology %s failed to allow %p to leave bridge %p\n", bridge1->technology->name, bridge_channel, bridge1); - } + { + SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock); + channels = ast_bridge_peers_nolock(bridge); + if (!channels) { + return AST_BRIDGE_TRANSFER_FAIL; } + if (ao2_container_count(channels) <= 1) { + return AST_BRIDGE_TRANSFER_INVALID; + } + transfer_prohibited = ast_test_flag(&bridge->feature_flags, + AST_BRIDGE_FLAG_TRANSFER_PROHIBITED); + do_bridge_transfer = ast_test_flag(&bridge->feature_flags, + AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) || + ao2_container_count(channels) > 2; + } - /* Drop channel count and reference count on the bridge they are leaving */ - bridge1->num--; - ao2_ref(bridge1, -1); - - bridge_array_remove(bridge1, bridge_channel->chan); + if (transfer_prohibited) { + return AST_BRIDGE_TRANSFER_NOT_PERMITTED; + } - /* Now add them into the bridge they are joining, increase channel count, and bump up reference count */ - bridge_channel->bridge = bridge0; - AST_LIST_INSERT_TAIL(&bridge0->channels, bridge_channel, entry); - bridge0->num++; - ao2_ref(bridge0, +1); + set_blind_transfer_variables(transferer, channels); - bridge_array_add(bridge0, bridge_channel->chan); + if (do_bridge_transfer) { + return blind_transfer_bridge(transferer, bridge, exten, context, + new_channel_cb, user_data); + } - /* Make the channel compatible with the new bridge it is joining or else formats would go amuck */ - bridge_make_compatible(bridge0, bridge_channel); + /* Reaching this portion means that we're dealing with a two-party bridge */ - /* Tell the technology handling bridge0 that the bridge channel is joining */ - if (bridge0->technology->join) { - ast_debug(1, "Giving bridge technology %s notification that %p is joining bridge %p\n", bridge0->technology->name, bridge_channel, bridge0); - if (bridge0->technology->join(bridge0, bridge_channel)) { - ast_debug(1, "Bridge technology %s failed to join %p to bridge %p\n", bridge0->technology->name, bridge_channel, bridge0); - } - } + transferee = get_transferee(channels, transferer); + if (!transferee) { + return AST_BRIDGE_TRANSFER_FAIL; + } - /* Poke the bridge channel, this will cause it to wake up and execute the proper threading model for the new bridge it is in */ - bridge_channel_poke(bridge_channel); + if (bridge_channel_queue_blind_transfer(transferee, exten, context, + new_channel_cb, user_data)) { + return AST_BRIDGE_TRANSFER_FAIL; } - ast_debug(1, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0); + ast_bridge_remove(bridge, transferer); + return AST_BRIDGE_TRANSFER_SUCCESS; +} - ao2_unlock(bridge1); - ao2_unlock(bridge0); +enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee, + struct ast_channel *to_transfer_target, struct ast_framehook *hook) +{ + /* First, check the validity of scenario. If invalid, return AST_BRIDGE_TRANSFER_INVALID. The following are invalid: + * 1) atxfer of an unbridged call to an unbridged call + * 2) atxfer of an unbridged call to a multi-party (n > 2) bridge + * 3) atxfer of a multi-party (n > 2) bridge to an unbridged call + * Second, check that the bridge(s) involved allows transfers. If not, return AST_BRIDGE_TRANSFER_NOT_PERMITTED. + * Third, break into different scenarios for different bridge situations: + * If both channels are bridged, perform a bridge merge. Direction of the merge is TBD. + * If channel A is bridged, and channel B is not (e.g. transferring to IVR or blond transfer) + * Some manner of masquerading is necessary. Presumably, you'd want to move channel A's bridge peer + * into where channel B is. However, it may be possible to do something a bit different, where a + * local channel is created and put into channel A's bridge. The local channel's ;2 channel + * is then masqueraded with channel B in some way. + * If channel A is not bridged and channel B is, then: + * This is similar to what is done in the previous scenario. Create a local channel and place it + * into B's bridge. Then masquerade the ;2 leg of the local channel. + */ - return 0; + /* XXX STUB */ + return AST_BRIDGE_TRANSFER_SUCCESS; } -int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan) +/*! + * \internal + * \brief Service the bridge manager request. + * \since 12.0.0 + * + * \param bridge requesting service. + * + * \return Nothing + */ +static void bridge_manager_service(struct ast_bridge *bridge) { - struct ast_bridge_channel *bridge_channel; + ast_bridge_lock(bridge); + if (bridge->callid) { + ast_callid_threadassoc_change(bridge->callid); + } - ao2_lock(bridge); + /* Do any pending bridge actions. */ + bridge_handle_actions(bridge); + ast_bridge_unlock(bridge); +} - if (!(bridge_channel = find_bridge_channel(bridge, chan))) { - ao2_unlock(bridge); - return -1; - } +/*! + * \internal + * \brief Bridge manager service thread. + * \since 12.0.0 + * + * \return Nothing + */ +static void *bridge_manager_thread(void *data) +{ + struct bridge_manager_controller *manager = data; + struct bridge_manager_request *request; + + ao2_lock(manager); + while (!manager->stop) { + request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node); + if (!request) { + ast_cond_wait(&manager->cond, ao2_object_get_lockaddr(manager)); + continue; + } + ao2_unlock(manager); - bridge_channel_suspend(bridge, bridge_channel); + /* Service the bridge. */ + bridge_manager_service(request->bridge); + ao2_ref(request->bridge, -1); + ast_free(request); - ao2_unlock(bridge); + ao2_lock(manager); + } + ao2_unlock(manager); - return 0; + return NULL; } -int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan) +/*! + * \internal + * \brief Destroy the bridge manager controller. + * \since 12.0.0 + * + * \param obj Bridge manager to destroy. + * + * \return Nothing + */ +static void bridge_manager_destroy(void *obj) { - struct ast_bridge_channel *bridge_channel; - - ao2_lock(bridge); + struct bridge_manager_controller *manager = obj; + struct bridge_manager_request *request; + + if (manager->thread != AST_PTHREADT_NULL) { + /* Stop the manager thread. */ + ao2_lock(manager); + manager->stop = 1; + ast_cond_signal(&manager->cond); + ao2_unlock(manager); + ast_debug(1, "Waiting for bridge manager thread to die.\n"); + pthread_join(manager->thread, NULL); + } - if (!(bridge_channel = find_bridge_channel(bridge, chan))) { - ao2_unlock(bridge); - return -1; + /* Destroy the service request queue. */ + while ((request = AST_LIST_REMOVE_HEAD(&manager->service_requests, node))) { + ao2_ref(request->bridge, -1); + ast_free(request); } - bridge_channel_unsuspend(bridge, bridge_channel); + ast_cond_destroy(&manager->cond); +} + +/*! + * \internal + * \brief Create the bridge manager controller. + * \since 12.0.0 + * + * \retval manager on success. + * \retval NULL on error. + */ +static struct bridge_manager_controller *bridge_manager_create(void) +{ + struct bridge_manager_controller *manager; - ao2_unlock(bridge); + manager = ao2_alloc(sizeof(*manager), bridge_manager_destroy); + if (!manager) { + /* Well. This isn't good. */ + return NULL; + } + ast_cond_init(&manager->cond, NULL); + AST_LIST_HEAD_INIT_NOLOCK(&manager->service_requests); + + /* Create the bridge manager thread. */ + if (ast_pthread_create(&manager->thread, NULL, bridge_manager_thread, manager)) { + /* Well. This isn't good either. */ + manager->thread = AST_PTHREADT_NULL; + ao2_ref(manager, -1); + manager = NULL; + } - return 0; + return manager; } -void ast_bridge_technology_suspend(struct ast_bridge_technology *technology) +/*! + * \internal + * \brief Bridge ao2 container sort function. + * \since 12.0.0 + * + * \param obj_left pointer to the (user-defined part) of an object. + * \param obj_right pointer to the (user-defined part) of an object. + * \param flags flags from ao2_callback() + * OBJ_POINTER - if set, 'obj_right', is an object. + * OBJ_KEY - if set, 'obj_right', is a search key item that is not an object. + * OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object. + * + * \retval <0 if obj_left < obj_right + * \retval =0 if obj_left == obj_right + * \retval >0 if obj_left > obj_right + */ +static int bridge_sort_cmp(const void *obj_left, const void *obj_right, int flags) { - technology->suspended = 1; + const struct ast_bridge *bridge_left = obj_left; + const struct ast_bridge *bridge_right = obj_right; + const char *right_key = obj_right; + int cmp; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + default: + case OBJ_POINTER: + right_key = bridge_right->uniqueid; + /* Fall through */ + case OBJ_KEY: + cmp = strcmp(bridge_left->uniqueid, right_key); + break; + case OBJ_PARTIAL_KEY: + cmp = strncmp(bridge_left->uniqueid, right_key, strlen(right_key)); + break; + } + return cmp; } -void ast_bridge_technology_unsuspend(struct ast_bridge_technology *technology) +struct bridge_complete { + /*! Nth match to return. */ + int state; + /*! Which match currently on. */ + int which; +}; + +static int complete_bridge_search(void *obj, void *arg, void *data, int flags) { - technology->suspended = 0; + struct bridge_complete *search = data; + + if (++search->which > search->state) { + return CMP_MATCH; + } + return 0; } -int ast_bridge_features_register(enum ast_bridge_builtin_feature feature, ast_bridge_features_hook_callback callback, const char *dtmf) +static char *complete_bridge(const char *word, int state) { - if (ARRAY_LEN(builtin_features_handlers) <= feature - || builtin_features_handlers[feature]) { - return -1; + char *ret; + struct ast_bridge *bridge; + struct bridge_complete search = { + .state = state, + }; + + bridge = ao2_callback_data(bridges, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY, + complete_bridge_search, (char *) word, &search); + if (!bridge) { + return NULL; } + ret = ast_strdup(bridge->uniqueid); + ao2_ref(bridge, -1); + return ret; +} - if (!ast_strlen_zero(dtmf)) { - ast_copy_string(builtin_features_dtmf[feature], dtmf, sizeof(builtin_features_dtmf[feature])); +static char *handle_bridge_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ +#define FORMAT_HDR "%-36s %5s %-15s %s\n" +#define FORMAT_ROW "%-36s %5u %-15s %s\n" + + struct ao2_iterator iter; + struct ast_bridge *bridge; + + switch (cmd) { + case CLI_INIT: + e->command = "bridge show all"; + e->usage = + "Usage: bridge show all\n" + " List all bridges\n"; + return NULL; + case CLI_GENERATE: + return NULL; } - builtin_features_handlers[feature] = callback; +/* BUGBUG this command may need to be changed to look at the stasis cache. */ + ast_cli(a->fd, FORMAT_HDR, "Bridge-ID", "Chans", "Type", "Technology"); + iter = ao2_iterator_init(bridges, 0); + for (; (bridge = ao2_iterator_next(&iter)); ao2_ref(bridge, -1)) { + ast_bridge_lock(bridge); + ast_cli(a->fd, FORMAT_ROW, + bridge->uniqueid, + bridge->num_channels, + bridge->v_table ? bridge->v_table->name : "<unknown>", + bridge->technology ? bridge->technology->name : "<unknown>"); + ast_bridge_unlock(bridge); + } + ao2_iterator_destroy(&iter); + return CLI_SUCCESS; - return 0; +#undef FORMAT_HDR +#undef FORMAT_ROW } -int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature) +static char *handle_bridge_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - if (ARRAY_LEN(builtin_features_handlers) <= feature - || !builtin_features_handlers[feature]) { - return -1; + struct ast_bridge *bridge; + struct ast_bridge_channel *bridge_channel; + + switch (cmd) { + case CLI_INIT: + e->command = "bridge show"; + e->usage = + "Usage: bridge show <bridge-id>\n" + " Show information about the <bridge-id> bridge\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_bridge(a->word, a->n); + } + return NULL; + } + +/* BUGBUG this command may need to be changed to look at the stasis cache. */ + if (a->argc != 3) { + return CLI_SHOWUSAGE; } - builtin_features_handlers[feature] = NULL; + bridge = ao2_find(bridges, a->argv[2], OBJ_KEY); + if (!bridge) { + ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]); + return CLI_SUCCESS; + } - return 0; + ast_bridge_lock(bridge); + ast_cli(a->fd, "Id: %s\n", bridge->uniqueid); + ast_cli(a->fd, "Type: %s\n", bridge->v_table ? bridge->v_table->name : "<unknown>"); + ast_cli(a->fd, "Technology: %s\n", + bridge->technology ? bridge->technology->name : "<unknown>"); + ast_cli(a->fd, "Num-Channels: %u\n", bridge->num_channels); + AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { + ast_cli(a->fd, "Channel: %s\n", ast_channel_name(bridge_channel->chan)); + } + ast_bridge_unlock(bridge); + ao2_ref(bridge, -1); + + return CLI_SUCCESS; } -int ast_bridge_features_hook(struct ast_bridge_features *features, - const char *dtmf, - ast_bridge_features_hook_callback callback, - void *hook_pvt, - ast_bridge_features_hook_pvt_destructor destructor) +static char *handle_bridge_destroy_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct ast_bridge_features_hook *hook; + struct ast_bridge *bridge; - /* Allocate new memory and setup it's various variables */ - hook = ast_calloc(1, sizeof(*hook)); - if (!hook) { - return -1; + switch (cmd) { + case CLI_INIT: + e->command = "bridge destroy"; + e->usage = + "Usage: bridge destroy <bridge-id>\n" + " Destroy the <bridge-id> bridge\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_bridge(a->word, a->n); + } + return NULL; } - ast_copy_string(hook->dtmf, dtmf, sizeof(hook->dtmf)); - hook->callback = callback; - hook->destructor = destructor; - hook->hook_pvt = hook_pvt; - /* Once done we add it onto the list. Now it will be picked up when DTMF is used */ - AST_LIST_INSERT_TAIL(&features->hooks, hook, entry); + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } - features->usable = 1; + bridge = ao2_find(bridges, a->argv[2], OBJ_KEY); + if (!bridge) { + ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]); + return CLI_SUCCESS; + } - return 0; -} + ast_cli(a->fd, "Destroying bridge '%s'\n", a->argv[2]); + ast_bridge_destroy(bridge); -void ast_bridge_features_set_talk_detector(struct ast_bridge_features *features, - ast_bridge_talking_indicate_callback talker_cb, - ast_bridge_talking_indicate_destructor talker_destructor, - void *pvt_data) -{ - features->talker_cb = talker_cb; - features->talker_destructor_cb = talker_destructor; - features->talker_pvt_data = pvt_data; + return CLI_SUCCESS; } -int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config) +static char *complete_bridge_participant(const char *bridge_name, const char *line, const char *word, int pos, int state) { - if (ARRAY_LEN(builtin_features_handlers) <= feature - || !builtin_features_handlers[feature]) { - return -1; + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_bridge_channel *bridge_channel; + int which; + int wordlen; + + bridge = ao2_find(bridges, bridge_name, OBJ_KEY); + if (!bridge) { + return NULL; } - /* If no alternate DTMF stream was provided use the default one */ - if (ast_strlen_zero(dtmf)) { - dtmf = builtin_features_dtmf[feature]; - /* If no DTMF is still available (ie: it has been disabled) then error out now */ - if (ast_strlen_zero(dtmf)) { - ast_debug(1, "Failed to enable built in feature %d on %p, no DTMF string is available for it.\n", feature, features); - return -1; + { + SCOPED_LOCK(bridge_lock, bridge, ast_bridge_lock, ast_bridge_unlock); + + which = 0; + wordlen = strlen(word); + AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { + if (!strncasecmp(ast_channel_name(bridge_channel->chan), word, wordlen) + && ++which > state) { + return ast_strdup(ast_channel_name(bridge_channel->chan)); + } } } - /* The rest is basically pretty easy. We create another hook using the built in feature's callback and DTMF, easy as pie. */ - return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL); + return NULL; } -void ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag) +static char *handle_bridge_kick_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - ast_set_flag(&features->feature_flags, flag); - features->usable = 1; -} + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup); + + switch (cmd) { + case CLI_INIT: + e->command = "bridge kick"; + e->usage = + "Usage: bridge kick <bridge-id> <channel-name>\n" + " Kick the <channel-name> channel out of the <bridge-id> bridge\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_bridge(a->word, a->n); + } + if (a->pos == 3) { + return complete_bridge_participant(a->argv[2], a->line, a->word, a->pos, a->n); + } + return NULL; + } -int ast_bridge_features_init(struct ast_bridge_features *features) -{ - /* Zero out the structure */ - memset(features, 0, sizeof(*features)); + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + bridge = ao2_find(bridges, a->argv[2], OBJ_KEY); + if (!bridge) { + ast_cli(a->fd, "Bridge '%s' not found\n", a->argv[2]); + return CLI_SUCCESS; + } - /* Initialize the hooks list, just in case */ - AST_LIST_HEAD_INIT_NOLOCK(&features->hooks); + chan = ast_channel_get_by_name_prefix(a->argv[3], strlen(a->argv[3])); + if (!chan) { + ast_cli(a->fd, "Channel '%s' not found\n", a->argv[3]); + return CLI_SUCCESS; + } - return 0; +/* + * BUGBUG the CLI kick needs to get the bridge to decide if it should dissolve. + * + * Likely the best way to do this is to add a kick method. The + * basic bridge class can then decide to dissolve the bridge if + * one of two channels is kicked. + * + * SIP/foo -- Local;1==Local;2 -- .... -- Local;1==Local;2 -- SIP/bar + * Kick a ;1 channel and the chain toward SIP/foo goes away. + * Kick a ;2 channel and the chain toward SIP/bar goes away. + * + * This can leave a local channel chain between the kicked ;1 + * and ;2 channels that are orphaned until you manually request + * one of those channels to hangup or request the bridge to + * dissolve. + */ + ast_cli(a->fd, "Kicking channel '%s' from bridge '%s'\n", + ast_channel_name(chan), a->argv[2]); + ast_bridge_remove(bridge, chan); + + return CLI_SUCCESS; } -void ast_bridge_features_cleanup(struct ast_bridge_features *features) +/*! Bridge technology capabilities to string. */ +static const char *tech_capability2str(uint32_t capabilities) { - struct ast_bridge_features_hook *hook; - - /* This is relatively simple, hooks are kept as a list on the features structure so we just pop them off and free them */ - while ((hook = AST_LIST_REMOVE_HEAD(&features->hooks, entry))) { - if (hook->destructor) { - hook->destructor(hook->hook_pvt); - } - ast_free(hook); - } - if (features->talker_destructor_cb && features->talker_pvt_data) { - features->talker_destructor_cb(features->talker_pvt_data); - features->talker_pvt_data = NULL; + const char *type; + + if (capabilities & AST_BRIDGE_CAPABILITY_HOLDING) { + type = "Holding"; + } else if (capabilities & AST_BRIDGE_CAPABILITY_EARLY) { + type = "Early"; + } else if (capabilities & AST_BRIDGE_CAPABILITY_NATIVE) { + type = "Native"; + } else if (capabilities & AST_BRIDGE_CAPABILITY_1TO1MIX) { + type = "1to1Mix"; + } else if (capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) { + type = "MultiMix"; + } else { + type = "<Unknown>"; } + return type; } -int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct ast_channel *chan) +static char *handle_bridge_technology_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct ast_bridge_channel *bridge_channel; +#define FORMAT_HDR "%-20s %-20s %8s %s\n" +#define FORMAT_ROW "%-20s %-20s %8d %s\n" - ao2_lock(bridge); + struct ast_bridge_technology *cur; - AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { - if (bridge_channel->chan == chan) { - continue; - } - ast_copy_string(bridge_channel->dtmf_stream_q, dtmf, sizeof(bridge_channel->dtmf_stream_q)); - ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_DTMF); + switch (cmd) { + case CLI_INIT: + e->command = "bridge technology show"; + e->usage = + "Usage: bridge technology show\n" + " List registered bridge technologies\n"; + return NULL; + case CLI_GENERATE: + return NULL; } - ao2_unlock(bridge); + ast_cli(a->fd, FORMAT_HDR, "Name", "Type", "Priority", "Suspended"); + AST_RWLIST_RDLOCK(&bridge_technologies); + AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) { + const char *type; - return 0; -} + /* Decode type for display */ + type = tech_capability2str(cur->capabilities); -void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval) -{ - ao2_lock(bridge); - bridge->internal_mixing_interval = mixing_interval; - ao2_unlock(bridge); + ast_cli(a->fd, FORMAT_ROW, cur->name, type, cur->preference, + AST_CLI_YESNO(cur->suspended)); + } + AST_RWLIST_UNLOCK(&bridge_technologies); + return CLI_SUCCESS; + +#undef FORMAT } -void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate) +static char *complete_bridge_technology(const char *word, int state) { + struct ast_bridge_technology *cur; + char *res; + int which; + int wordlen; - ao2_lock(bridge); - bridge->internal_sample_rate = sample_rate; - ao2_unlock(bridge); + which = 0; + wordlen = strlen(word); + AST_RWLIST_RDLOCK(&bridge_technologies); + AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) { + if (!strncasecmp(cur->name, word, wordlen) && ++which > state) { + res = ast_strdup(cur->name); + AST_RWLIST_UNLOCK(&bridge_technologies); + return res; + } + } + AST_RWLIST_UNLOCK(&bridge_technologies); + return NULL; } -static void cleanup_video_mode(struct ast_bridge *bridge) +static char *handle_bridge_technology_suspend(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - switch (bridge->video_mode.mode) { - case AST_BRIDGE_VIDEO_MODE_NONE: - break; - case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: - if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) { - ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc); + struct ast_bridge_technology *cur; + int suspend; + int successful; + + switch (cmd) { + case CLI_INIT: + e->command = "bridge technology {suspend|unsuspend}"; + e->usage = + "Usage: bridge technology {suspend|unsuspend} <technology-name>\n" + " Suspend or unsuspend a bridge technology.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_bridge_technology(a->word, a->n); } - break; - case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: - if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) { - ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc); + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + suspend = !strcasecmp(a->argv[2], "suspend"); + successful = 0; + AST_RWLIST_WRLOCK(&bridge_technologies); + AST_RWLIST_TRAVERSE(&bridge_technologies, cur, entry) { + if (!strcasecmp(cur->name, a->argv[3])) { + successful = 1; + if (suspend) { + ast_bridge_technology_suspend(cur); + } else { + ast_bridge_technology_unsuspend(cur); + } + break; } - if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) { - ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc); + } + AST_RWLIST_UNLOCK(&bridge_technologies); + + if (successful) { + if (suspend) { + ast_cli(a->fd, "Suspended bridge technology '%s'\n", a->argv[3]); + } else { + ast_cli(a->fd, "Unsuspended bridge technology '%s'\n", a->argv[3]); } + } else { + ast_cli(a->fd, "Bridge technology '%s' not found\n", a->argv[3]); } - memset(&bridge->video_mode, 0, sizeof(bridge->video_mode)); -} -void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan) -{ - ao2_lock(bridge); - cleanup_video_mode(bridge); - bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC; - bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan); - ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan)); - ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE); - ao2_unlock(bridge); + return CLI_SUCCESS; } -void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge) +static struct ast_cli_entry bridge_cli[] = { + AST_CLI_DEFINE(handle_bridge_show_all, "List all bridges"), + AST_CLI_DEFINE(handle_bridge_show_specific, "Show information about a bridge"), + AST_CLI_DEFINE(handle_bridge_destroy_specific, "Destroy a bridge"), + AST_CLI_DEFINE(handle_bridge_kick_channel, "Kick a channel from a bridge"), + AST_CLI_DEFINE(handle_bridge_technology_show, "List registered bridge technologies"), + AST_CLI_DEFINE(handle_bridge_technology_suspend, "Suspend/unsuspend a bridge technology"), +}; + +/*! + * \internal + * \brief Shutdown the bridging system. + * \since 12.0.0 + * + * \return Nothing + */ +static void bridge_shutdown(void) { - ao2_lock(bridge); - cleanup_video_mode(bridge); - bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC; - ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode); - ao2_unlock(bridge); + ast_cli_unregister_multiple(bridge_cli, ARRAY_LEN(bridge_cli)); + ao2_cleanup(bridges); + bridges = NULL; + ao2_cleanup(bridge_manager); + bridge_manager = NULL; + ast_stasis_bridging_shutdown(); } -void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe) +int ast_bridging_init(void) { - struct ast_bridge_video_talker_src_data *data; - /* If the channel doesn't support video, we don't care about it */ - if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO)) { - return; + if (ast_stasis_bridging_init()) { + bridge_shutdown(); + return -1; } - ao2_lock(bridge); - data = &bridge->video_mode.mode_data.talker_src_data; - - if (data->chan_vsrc == chan) { - data->average_talking_energy = talker_energy; - } else if ((data->average_talking_energy < talker_energy) && is_keyframe) { - if (data->chan_old_vsrc) { - ast_channel_unref(data->chan_old_vsrc); - } - if (data->chan_vsrc) { - data->chan_old_vsrc = data->chan_vsrc; - ast_indicate(data->chan_old_vsrc, AST_CONTROL_VIDUPDATE); - } - data->chan_vsrc = ast_channel_ref(chan); - data->average_talking_energy = talker_energy; - ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc)); - ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE); - } else if ((data->average_talking_energy < talker_energy) && !is_keyframe) { - ast_indicate(chan, AST_CONTROL_VIDUPDATE); - } else if (!data->chan_vsrc && is_keyframe) { - data->chan_vsrc = ast_channel_ref(chan); - data->average_talking_energy = talker_energy; - ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc)); - ast_indicate(chan, AST_CONTROL_VIDUPDATE); - } else if (!data->chan_old_vsrc && is_keyframe) { - data->chan_old_vsrc = ast_channel_ref(chan); - ast_indicate(chan, AST_CONTROL_VIDUPDATE); + bridge_manager = bridge_manager_create(); + if (!bridge_manager) { + bridge_shutdown(); + return -1; } - ao2_unlock(bridge); -} -int ast_bridge_number_video_src(struct ast_bridge *bridge) -{ - int res = 0; - - ao2_lock(bridge); - switch (bridge->video_mode.mode) { - case AST_BRIDGE_VIDEO_MODE_NONE: - break; - case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: - if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) { - res = 1; - } - break; - case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: - if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) { - res++; - } - if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) { - res++; - } + bridges = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX, + AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, bridge_sort_cmp, NULL); + if (!bridges) { + bridge_shutdown(); + return -1; } - ao2_unlock(bridge); - return res; -} - -int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan) -{ - int res = 0; - ao2_lock(bridge); - switch (bridge->video_mode.mode) { - case AST_BRIDGE_VIDEO_MODE_NONE: - break; - case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: - if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) { - res = 1; - } - break; - case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: - if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) { - res = 1; - } else if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) { - res = 2; - } + ast_bridging_init_basic(); - } - ao2_unlock(bridge); - return res; -} +/* BUGBUG need AMI action equivalents to the CLI commands. */ + ast_cli_register_multiple(bridge_cli, ARRAY_LEN(bridge_cli)); -void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan) -{ - ao2_lock(bridge); - switch (bridge->video_mode.mode) { - case AST_BRIDGE_VIDEO_MODE_NONE: - break; - case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC: - if (bridge->video_mode.mode_data.single_src_data.chan_vsrc == chan) { - if (bridge->video_mode.mode_data.single_src_data.chan_vsrc) { - ast_channel_unref(bridge->video_mode.mode_data.single_src_data.chan_vsrc); - } - bridge->video_mode.mode_data.single_src_data.chan_vsrc = NULL; - } - break; - case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: - if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc == chan) { - if (bridge->video_mode.mode_data.talker_src_data.chan_vsrc) { - ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_vsrc); - } - bridge->video_mode.mode_data.talker_src_data.chan_vsrc = NULL; - bridge->video_mode.mode_data.talker_src_data.average_talking_energy = 0; - } - if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc == chan) { - if (bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc) { - ast_channel_unref(bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc); - } - bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL; - } - } - ao2_unlock(bridge); + ast_register_atexit(bridge_shutdown); + return 0; } diff --git a/main/bridging_basic.c b/main/bridging_basic.c new file mode 100644 index 0000000000000000000000000000000000000000..00daf771021cb2b436b944e508fa5163cb47c29b --- /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 0000000000000000000000000000000000000000..079cbdb333f69caf2f602b5510ecea8cbee6c6f9 --- /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 0b2dedd637a33768f81084804bd7e1f447ab195a..aec43edf74aec8a3e39bbcc99644cf5dafc6400e 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 f3293d59b8038038d14c41acf7cbeac477b58ebd..42aaada6d3eade73a7987896e27d1e128c476adb 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 7782b2279b096eda6a1bc18070ccb175eac56b24..22232acbc6c82918ac4e7b59df99509b753f476c 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 06b45213194b2e05d8df667822cbd3a3fe77dea5..39a3fbe61c96ddcb57c00d28875ea63aa5a542ff 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 0000000000000000000000000000000000000000..2e0bcc48aea3dbe6fa9b1fee8988a3d04e0c8d65 --- /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 0000000000000000000000000000000000000000..d5e5881117df2eeb6e2d47236805b73757a6a1f5 --- /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 b6cc1916497b41cabb4b0baa52d477a30a427511..be872a80d64ce4769f1f4f3f8ed0f8be7e598ab5 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"); @@ -2814,32 +2652,12 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT); ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT); - /* - * ast_bridge_call clears AST_FLAG_BRIDGE_HANGUP_DONT, but we - * don't want that to happen here because the transferer is in - * another bridge already. - */ - if (ast_test_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT)) { - hangup_dont = 1; - } - - /* - * Don't let the after-bridge code run the h-exten. It is the - * wrong bridge to run the h-exten after. - */ - ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT); - /* * Let party B and C talk as long as they want while party A * languishes in autoservice listening to MOH. */ ast_bridge_call(transferer, newchan, &bconfig); - if (hangup_dont) { - /* Restore the AST_FLAG_BRIDGE_HANGUP_DONT flag */ - ast_set_flag(ast_channel_flags(transferer), AST_FLAG_BRIDGE_HANGUP_DONT); - } - if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) { ast_autoservice_chan_hangup_peer(transferer, newchan); if (ast_stream_and_wait(transferer, xfersound, "")) { @@ -3731,6 +3549,7 @@ static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel return res; } +#if 0//BUGBUG /*! * \brief Check the dynamic features * \param chan,peer,config,code,sense @@ -3777,12 +3596,14 @@ static int feature_interpret(struct ast_channel *chan, struct ast_channel *peer, return res; } +#endif int ast_feature_detect(struct ast_channel *chan, struct ast_flags *features, const char *code, struct ast_call_feature *feature) { return feature_interpret_helper(chan, NULL, NULL, code, 0, NULL, features, FEATURE_INTERPRET_DETECT, feature); } +#if 0//BUGBUG /*! \brief Check if a feature exists */ static int feature_check(struct ast_channel *chan, struct ast_flags *features, char *code) { struct ast_str *chan_dynamic_features; @@ -3801,11 +3622,15 @@ static int feature_check(struct ast_channel *chan, struct ast_flags *features, c return res; } +#endif static void set_config_flags(struct ast_channel *chan, struct ast_bridge_config *config) { int x; +/* BUGBUG there is code that checks AST_BRIDGE_IGNORE_SIGS but no code to set it. */ +/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_0 but no code to set it. */ +/* BUGBUG there is code that checks AST_BRIDGE_REC_CHANNEL_1 but no code to set it. */ ast_clear_flag(config, AST_FLAGS_ALL); ast_rdlock_call_features(); @@ -4208,37 +4033,22 @@ void ast_channel_log(char *title, struct ast_channel *chan) /* for debug, this i { ast_log(LOG_NOTICE, "______ %s (%lx)______\n", title, (unsigned long) chan); ast_log(LOG_NOTICE, "CHAN: name: %s; appl: %s; data: %s; contxt: %s; exten: %s; pri: %d;\n", - ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan)); + ast_channel_name(chan), ast_channel_appl(chan), ast_channel_data(chan), + ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan)); ast_log(LOG_NOTICE, "CHAN: acctcode: %s; dialcontext: %s; amaflags: %x; maccontxt: %s; macexten: %s; macpri: %d;\n", - ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan)); - ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; _bridge: %p; uniqueID: %s; linkedID:%s\n", + ast_channel_accountcode(chan), ast_channel_dialcontext(chan), ast_channel_amaflags(chan), + ast_channel_macrocontext(chan), ast_channel_macroexten(chan), ast_channel_macropriority(chan)); + ast_log(LOG_NOTICE, "CHAN: masq: %p; masqr: %p; uniqueID: %s; linkedID:%s\n", ast_channel_masq(chan), ast_channel_masqr(chan), - ast_channel_internal_bridged_channel(chan), ast_channel_uniqueid(chan), ast_channel_linkedid(chan)); + ast_channel_uniqueid(chan), ast_channel_linkedid(chan)); if (ast_channel_masqr(chan)) { ast_log(LOG_NOTICE, "CHAN: masquerading as: %s; cdr: %p;\n", ast_channel_name(ast_channel_masqr(chan)), ast_channel_cdr(ast_channel_masqr(chan))); } - if (ast_channel_internal_bridged_channel(chan)) { - ast_log(LOG_NOTICE, "CHAN: Bridged to %s\n", ast_channel_name(ast_channel_internal_bridged_channel(chan))); - } ast_log(LOG_NOTICE, "===== done ====\n"); } -/*! - * \brief return the first unlocked cdr in a possible chain - */ -static struct ast_cdr *pick_unlocked_cdr(struct ast_cdr *cdr) -{ - struct ast_cdr *cdr_orig = cdr; - while (cdr) { - if (!ast_test_flag(cdr,AST_CDR_FLAG_LOCKED)) - return cdr; - cdr = cdr->next; - } - return cdr_orig; /* everybody LOCKED or some other weirdness, like a NULL */ -} - static void set_bridge_features_on_config(struct ast_bridge_config *config, const char *features) { const char *feature; @@ -4249,17 +4059,14 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons for (feature = features; *feature; feature++) { struct ast_flags *party; - char this_feature; if (isupper(*feature)) { - party = &(config->features_caller); + party = &config->features_caller; } else { - party = &(config->features_callee); + party = &config->features_callee; } - this_feature = tolower(*feature); - - switch (this_feature) { + switch (tolower(*feature)) { case 't' : ast_set_flag(party, AST_FEATURE_REDIRECT); break; @@ -4277,6 +4084,7 @@ static void set_bridge_features_on_config(struct ast_bridge_config *config, cons break; default : ast_log(LOG_WARNING, "Skipping unknown feature code '%c'\n", *feature); + break; } } } @@ -4332,6 +4140,279 @@ void ast_bridge_end_dtmf(struct ast_channel *chan, char digit, struct timeval st digit, ast_channel_name(chan), why, duration); } +/*! + * \internal + * \brief Setup bridge builtin features. + * \since 12.0.0 + * + * \param features Bridge features to setup. + * \param chan Get features from this channel. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int setup_bridge_features_builtin(struct ast_bridge_features *features, struct ast_channel *chan) +{ + struct ast_flags *flags; + char dtmf[FEATURE_MAX_LEN]; + int res; + + ast_channel_lock(chan); + flags = ast_bridge_features_ds_get(chan); + ast_channel_unlock(chan); + if (!flags) { + return 0; + } + + res = 0; + ast_rdlock_call_features(); + if (ast_test_flag(flags, AST_FEATURE_REDIRECT)) { + /* Add atxfer and blind transfer. */ + builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { +/* BUGBUG need to supply a blind transfer structure and destructor to use other than defaults */ + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf, NULL, NULL, 1); + } + builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { +/* BUGBUG need to supply an attended transfer structure and destructor to use other than defaults */ + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_DISCONNECT)) { + builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf)); + if (ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_PARKCALL)) { + builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_AUTOMON)) { + builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf, NULL, NULL, 1); + } + } + if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON)) { + builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf)); + if (!ast_strlen_zero(dtmf)) { + res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf, NULL, NULL, 1); + } + } + ast_unlock_call_features(); + +#if 0 /* BUGBUG don't report errors untill all of the builtin features are supported. */ + return res ? -1 : 0; +#else + return 0; +#endif +} + +struct dtmf_hook_run_app { + /*! Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER) */ + unsigned int flags; + /*! Offset into app_name[] where the MOH class name starts. (zero if no MOH) */ + int moh_offset; + /*! Offset into app_name[] where the application argument string starts. (zero if no arguments) */ + int app_args_offset; + /*! Application name to run. */ + char app_name[0]; +}; + +/*! + * \internal + * \brief Setup bridge dynamic features. + * \since 12.0.0 + * + * \param bridge The bridge that the channel is part of + * \param bridge_channel Channel executing the feature + * \param hook_pvt Private data passed in when the hook was created + * + * \retval 0 Keep the callback hook. + * \retval -1 Remove the callback hook. + */ +static int app_dtmf_feature_hook(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + struct dtmf_hook_run_app *pvt = hook_pvt; + void (*run_it)(struct ast_bridge_channel *bridge_channel, const char *app_name, const char *app_args, const char *moh_class); + + if (ast_test_flag(pvt, AST_FEATURE_FLAG_ONPEER)) { + run_it = ast_bridge_channel_write_app; + } else { + run_it = ast_bridge_channel_run_app; + } + +/* + * BUGBUG need to pass to run_it the triggering channel name so DYNAMIC_WHO_TRIGGERED can be set on the channel when it is run. + * + * This would replace DYNAMIC_PEERNAME which is redundant with + * BRIDGEPEER anyway. The value of DYNAMIC_WHO_TRIGGERED is + * really useful in the case of a multi-party bridge. + */ + run_it(bridge_channel, pvt->app_name, + pvt->app_args_offset ? &pvt->app_name[pvt->app_args_offset] : NULL, + pvt->moh_offset ? &pvt->app_name[pvt->moh_offset] : NULL); + return 0; +} + +/*! + * \internal + * \brief Add a dynamic DTMF feature hook to the bridge features. + * \since 12.0.0 + * + * \param features Bridge features to setup. + * \param flags Which side of bridge to run app (AST_FEATURE_FLAG_ONSELF/AST_FEATURE_FLAG_ONPEER). + * \param dtmf DTMF trigger sequence. + * \param app_name Dialplan application name to run. + * \param app_args Dialplan application arguments. (Empty or NULL if no arguments) + * \param moh_class MOH class to play to peer. (Empty or NULL if no MOH played) + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int add_dynamic_dtmf_hook(struct ast_bridge_features *features, unsigned int flags, const char *dtmf, const char *app_name, const char *app_args, const char *moh_class) +{ + struct dtmf_hook_run_app *app_data; + size_t len_name = strlen(app_name) + 1; + size_t len_args = ast_strlen_zero(app_args) ? 0 : strlen(app_args) + 1; + size_t len_moh = ast_strlen_zero(moh_class) ? 0 : strlen(moh_class) + 1; + size_t len_data = sizeof(*app_data) + len_name + len_args + len_moh; + + /* Fill in application run hook data. */ + app_data = ast_malloc(len_data); + if (!app_data) { + return -1; + } + app_data->flags = flags; + app_data->app_args_offset = len_args ? len_name : 0; + app_data->moh_offset = len_moh ? len_name + len_args : 0; + strcpy(app_data->app_name, app_name);/* Safe */ + if (len_args) { + strcpy(&app_data->app_name[app_data->app_args_offset], app_args);/* Safe */ + } + if (len_moh) { + strcpy(&app_data->app_name[app_data->moh_offset], moh_class);/* Safe */ + } + + return ast_bridge_dtmf_hook(features, dtmf, app_dtmf_feature_hook, + app_data, ast_free_ptr, 1); +} + +/*! + * \internal + * \brief Setup bridge dynamic features. + * \since 12.0.0 + * + * \param features Bridge features to setup. + * \param chan Get features from this channel. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan) +{ + const char *feat; + char *dynamic_features = NULL; + char *tok; + int res; + + ast_channel_lock(chan); + feat = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"); + if (!ast_strlen_zero(feat)) { + dynamic_features = ast_strdupa(feat); + } + ast_channel_unlock(chan); + if (!dynamic_features) { + return 0; + } + +/* BUGBUG need to pass to add_dynamic_dtmf_hook the applicationmap name (feature->sname) so the DYNAMIC_FEATURENAME can be set on the channel when it is run. */ + res = 0; + while ((tok = strsep(&dynamic_features, "#"))) { + struct feature_group *fg; + struct ast_call_feature *feature; + + AST_RWLIST_RDLOCK(&feature_groups); + fg = find_group(tok); + if (fg) { + struct feature_group_exten *fge; + + AST_LIST_TRAVERSE(&fg->features, fge, entry) { + res |= add_dynamic_dtmf_hook(features, fge->feature->flags, fge->exten, + fge->feature->app, fge->feature->app_args, fge->feature->moh_class); + } + } + AST_RWLIST_UNLOCK(&feature_groups); + + ast_rdlock_call_features(); + feature = find_dynamic_feature(tok); + if (feature) { + res |= add_dynamic_dtmf_hook(features, feature->flags, feature->exten, + feature->app, feature->app_args, feature->moh_class); + } + ast_unlock_call_features(); + } + return res; +} + +/* BUGBUG struct ast_call_feature needs to be made an ao2 object so the basic bridge class can own the code setting up it's DTMF hooks. */ +/* BUGBUG this really should be made a private function of bridging_basic.c after struct ast_call_feature is made an ao2 object. */ +int ast_bridge_channel_setup_features(struct ast_bridge_channel *bridge_channel) +{ + int res = 0; + + /* Always pass through any DTMF digits. */ + bridge_channel->features->dtmf_passthrough = 1; + + res |= setup_bridge_features_builtin(bridge_channel->features, bridge_channel->chan); + res |= setup_bridge_features_dynamic(bridge_channel->features, bridge_channel->chan); + + return res; +} + +static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits) +{ + if (config->end_sound) { + ast_string_field_set(limits, duration_sound, config->end_sound); + } + + if (config->warning_sound) { + ast_string_field_set(limits, warning_sound, config->warning_sound); + } + + if (config->start_sound) { + ast_string_field_set(limits, connect_sound, config->start_sound); + } + + limits->frequency = config->warning_freq; + limits->warning = config->play_warning; +} + +/*! + * \internal brief Setup limit hook structures on calls that need limits + * + * \param config ast_bridge_config which provides the limit data + * \param caller_limits pointer to an ast_bridge_features_limits struct which will store the caller side limits + * \param callee_limits pointer to an ast_bridge_features_limits struct which will store the callee side limits + */ +static void bridge_config_set_limits(struct ast_bridge_config *config, struct ast_bridge_features_limits *caller_limits, struct ast_bridge_features_limits *callee_limits) +{ + if (ast_test_flag(&config->features_caller, AST_FEATURE_PLAY_WARNING)) { + bridge_config_set_limits_warning_values(config, caller_limits); + } + + if (ast_test_flag(&config->features_callee, AST_FEATURE_PLAY_WARNING)) { + bridge_config_set_limits_warning_values(config, callee_limits); + } + + caller_limits->duration = config->timelimit; + callee_limits->duration = config->timelimit; +} + /*! * \internal * \brief Check if Monitor needs to be started on a channel. @@ -4374,6 +4455,24 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p } } +/*! + * \internal + * \brief Send the peer channel on its way on bridge start failure. + * \since 12.0.0 + * + * \param chan Chan to put into autoservice. + * \param peer Chan to send to after bridge goto or run hangup handlers and hangup. + * + * \return Nothing + */ +static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel *peer) +{ + if (ast_after_bridge_goto_setup(peer) + || ast_pbx_start(peer)) { + ast_autoservice_chan_hangup_peer(chan, peer); + } +} + /*! * \brief bridge the call and set CDR * @@ -4388,33 +4487,16 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p */ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config) { - /* Copy voice back and forth between the two channels. Give the peer - the ability to transfer calls with '#<extension' syntax. */ - struct ast_frame *f; - struct ast_channel *who; - char chan_featurecode[FEATURE_MAX_LEN + 1]=""; - char peer_featurecode[FEATURE_MAX_LEN + 1]=""; - char orig_channame[AST_CHANNEL_NAME]; - char orig_peername[AST_CHANNEL_NAME]; int res; - int diff; - int hasfeatures=0; - int hadfeatures=0; - int sendingdtmfdigit = 0; - int we_disabled_peer_cdr = 0; - struct ast_option_header *aoh; - struct ast_cdr *bridge_cdr = NULL; - struct ast_cdr *chan_cdr = ast_channel_cdr(chan); /* the proper chan cdr, if there are forked cdrs */ - struct ast_cdr *peer_cdr = ast_channel_cdr(peer); /* the proper chan cdr, if there are forked cdrs */ - struct ast_cdr *new_chan_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */ - struct ast_cdr *new_peer_cdr = NULL; /* the proper chan cdr, if there are forked cdrs */ - struct ast_silence_generator *silgen = NULL; - /*! TRUE if h-exten or hangup handlers run. */ - int hangup_run = 0; + struct ast_bridge *bridge; + struct ast_bridge_features chan_features; + struct ast_bridge_features *peer_features; +/* BUGBUG these channel vars may need to be made dynamic so they update when transfers happen. */ pbx_builtin_setvar_helper(chan, "BRIDGEPEER", ast_channel_name(peer)); pbx_builtin_setvar_helper(peer, "BRIDGEPEER", ast_channel_name(chan)); +/* BUGBUG revisit how BLINDTRANSFER operates with the new bridging model. */ /* Clear any BLINDTRANSFER since the transfer has completed. */ pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL); pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", NULL); @@ -4422,10 +4504,15 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a set_bridge_features_on_config(config, pbx_builtin_getvar_helper(chan, "BRIDGE_FEATURES")); add_features_datastores(chan, peer, config); - /* This is an interesting case. One example is if a ringing channel gets redirected to - * an extension that picks up a parked call. This will make sure that the call taken - * out of parking gets told that the channel it just got bridged to is still ringing. */ - if (ast_channel_state(chan) == AST_STATE_RINGING && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) { + /* + * This is an interesting case. One example is if a ringing + * channel gets redirected to an extension that picks up a + * parked call. This will make sure that the call taken out of + * parking gets told that the channel it just got bridged to is + * still ringing. + */ + if (ast_channel_state(chan) == AST_STATE_RINGING + && ast_channel_visible_indication(peer) != AST_CONTROL_RINGING) { ast_indicate(peer, AST_CONTROL_RINGING); } @@ -4436,6 +4523,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a /* Answer if need be */ if (ast_channel_state(chan) != AST_STATE_UP) { if (ast_raw_answer(chan, 1)) { + bridge_failed_peer_goto(chan, peer); return -1; } } @@ -4446,571 +4534,123 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a ast_channel_log("Pre-bridge PEER Channel info", peer); #endif /* two channels are being marked as linked here */ - ast_channel_set_linkgroup(chan,peer); + ast_channel_set_linkgroup(chan, peer); - /* copy the userfield from the B-leg to A-leg if applicable */ - if (ast_channel_cdr(chan) && ast_channel_cdr(peer) && !ast_strlen_zero(ast_channel_cdr(peer)->userfield)) { - char tmp[256]; - - ast_channel_lock(chan); - if (!ast_strlen_zero(ast_channel_cdr(chan)->userfield)) { - snprintf(tmp, sizeof(tmp), "%s;%s", ast_channel_cdr(chan)->userfield, ast_channel_cdr(peer)->userfield); - ast_cdr_appenduserfield(chan, tmp); - } else { - ast_cdr_setuserfield(chan, ast_channel_cdr(peer)->userfield); - } - ast_channel_unlock(chan); - /* Don't delete the CDR; just disable it. */ - ast_set_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED); - we_disabled_peer_cdr = 1; - } - ast_copy_string(orig_channame,ast_channel_name(chan),sizeof(orig_channame)); - ast_copy_string(orig_peername,ast_channel_name(peer),sizeof(orig_peername)); - - if (!chan_cdr || (chan_cdr && !ast_test_flag(chan_cdr, AST_CDR_FLAG_POST_DISABLED))) { - ast_channel_lock_both(chan, peer); - if (chan_cdr) { - ast_set_flag(chan_cdr, AST_CDR_FLAG_MAIN); - ast_cdr_update(chan); - bridge_cdr = ast_cdr_dup_unique_swap(chan_cdr); - /* rip any forked CDR's off of the chan_cdr and attach - * them to the bridge_cdr instead */ - bridge_cdr->next = chan_cdr->next; - chan_cdr->next = NULL; - ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata)); - if (peer_cdr && !ast_strlen_zero(peer_cdr->userfield)) { - ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield)); - } - ast_cdr_setaccount(peer, ast_channel_accountcode(chan)); - } else { - /* better yet, in a xfer situation, find out why the chan cdr got zapped (pun unintentional) */ - bridge_cdr = ast_cdr_alloc(); /* this should be really, really rare/impossible? */ - ast_copy_string(bridge_cdr->channel, ast_channel_name(chan), sizeof(bridge_cdr->channel)); - ast_copy_string(bridge_cdr->dstchannel, ast_channel_name(peer), sizeof(bridge_cdr->dstchannel)); - ast_copy_string(bridge_cdr->uniqueid, ast_channel_uniqueid(chan), sizeof(bridge_cdr->uniqueid)); - ast_copy_string(bridge_cdr->lastapp, S_OR(ast_channel_appl(chan), ""), sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, S_OR(ast_channel_data(chan), ""), sizeof(bridge_cdr->lastdata)); - ast_cdr_setcid(bridge_cdr, chan); - bridge_cdr->disposition = (ast_channel_state(chan) == AST_STATE_UP) ? AST_CDR_ANSWERED : AST_CDR_NULL; - bridge_cdr->amaflags = ast_channel_amaflags(chan) ? ast_channel_amaflags(chan) : ast_default_amaflags; - ast_copy_string(bridge_cdr->accountcode, ast_channel_accountcode(chan), sizeof(bridge_cdr->accountcode)); - /* Destination information */ - ast_copy_string(bridge_cdr->dst, ast_channel_exten(chan), sizeof(bridge_cdr->dst)); - ast_copy_string(bridge_cdr->dcontext, ast_channel_context(chan), sizeof(bridge_cdr->dcontext)); - if (peer_cdr) { - bridge_cdr->start = peer_cdr->start; - ast_copy_string(bridge_cdr->userfield, peer_cdr->userfield, sizeof(bridge_cdr->userfield)); - } else { - ast_cdr_start(bridge_cdr); - } - } - ast_channel_unlock(chan); - ast_channel_unlock(peer); + /* + * If we are bridging a call, stop worrying about forwarding + * loops. We presume that if a call is being bridged, that the + * humans in charge know what they're doing. If they don't, + * well, what can we do about that? + */ + clear_dialed_interfaces(chan); + clear_dialed_interfaces(peer); - ast_debug(4, "bridge answer set, chan answer set\n"); - /* peer_cdr->answer will be set when a macro runs on the peer; - in that case, the bridge answer will be delayed while the - macro plays on the peer channel. The peer answered the call - before the macro started playing. To the phone system, - this is billable time for the call, even tho the caller - hears nothing but ringing while the macro does its thing. */ - - /* Another case where the peer cdr's time will be set, is when - A self-parks by pickup up phone and dialing 700, then B - picks up A by dialing its parking slot; there may be more - practical paths that get the same result, tho... in which - case you get the previous answer time from the Park... which - is before the bridge's start time, so I added in the - tvcmp check to the if below */ - - if (peer_cdr && !ast_tvzero(peer_cdr->answer) && ast_tvcmp(peer_cdr->answer, bridge_cdr->start) >= 0) { - ast_cdr_setanswer(bridge_cdr, peer_cdr->answer); - ast_cdr_setdisposition(bridge_cdr, peer_cdr->disposition); - if (chan_cdr) { - ast_cdr_setanswer(chan_cdr, peer_cdr->answer); - ast_cdr_setdisposition(chan_cdr, peer_cdr->disposition); - } - } else { - ast_cdr_answer(bridge_cdr); - if (chan_cdr) { - ast_cdr_answer(chan_cdr); /* for the sake of cli status checks */ - } - } - if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT) && (chan_cdr || peer_cdr)) { - if (chan_cdr) { - ast_set_flag(chan_cdr, AST_CDR_FLAG_BRIDGED); - } - if (peer_cdr) { - ast_set_flag(peer_cdr, AST_CDR_FLAG_BRIDGED); - } - } - /* the DIALED flag may be set if a dialed channel is transfered - * and then bridged to another channel. In order for the - * bridge CDR to be written, the DIALED flag must not be - * present. */ - ast_clear_flag(bridge_cdr, AST_CDR_FLAG_DIALED); + res = 0; + ast_channel_lock(chan); + res |= ast_bridge_features_ds_set(chan, &config->features_caller); + ast_channel_unlock(chan); + ast_channel_lock(peer); + res |= ast_bridge_features_ds_set(peer, &config->features_callee); + ast_channel_unlock(peer); + if (res) { + bridge_failed_peer_goto(chan, peer); + return -1; } - ast_cel_report_event(chan, AST_CEL_BRIDGE_START, NULL, NULL, peer); - - /* If we are bridging a call, stop worrying about forwarding loops. We presume that if - * a call is being bridged, that the humans in charge know what they're doing. If they - * don't, well, what can we do about that? */ - clear_dialed_interfaces(chan); - clear_dialed_interfaces(peer); - for (;;) { - struct ast_channel *other; /* used later */ + /* Setup features. */ + res = ast_bridge_features_init(&chan_features); + peer_features = ast_bridge_features_new(); + if (res || !peer_features) { + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; + } - res = ast_channel_bridge(chan, peer, config, &f, &who); + if (config->timelimit) { + struct ast_bridge_features_limits call_duration_limits_chan; + struct ast_bridge_features_limits call_duration_limits_peer; + int abandon_call = 0; /* TRUE if set limits fails so we can abandon the call. */ - if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) - || ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE)) { - /* Zombies are present time to leave! */ - res = -1; - if (f) { - ast_frfree(f); - } - goto before_you_go; - } - - /* When frame is not set, we are probably involved in a situation - where we've timed out. - When frame is set, we'll come this code twice; once for DTMF_BEGIN - and also for DTMF_END. If we flow into the following 'if' for both, then - our wait times are cut in half, as both will subtract from the - feature_timer. Not good! - */ - if (config->feature_timer && (!f || f->frametype == AST_FRAME_DTMF_END)) { - /* Update feature timer for next pass */ - diff = ast_tvdiff_ms(ast_tvnow(), config->feature_start_time); - if (res == AST_BRIDGE_RETRY) { - /* The feature fully timed out but has not been updated. Skip - * the potential round error from the diff calculation and - * explicitly set to expired. */ - config->feature_timer = -1; - } else { - config->feature_timer -= diff; - } + if (ast_bridge_features_limits_construct(&call_duration_limits_chan)) { + ast_log(LOG_ERROR, "Could not construct caller duration limits. Bridge canceled.\n"); - if (hasfeatures) { - if (config->feature_timer <= 0) { - /* Not *really* out of time, just out of time for - digits to come in for features. */ - ast_debug(1, "Timed out for feature!\n"); - if (!ast_strlen_zero(peer_featurecode)) { - ast_dtmf_stream(chan, peer, peer_featurecode, 0, f ? f->len : 0); - memset(peer_featurecode, 0, sizeof(peer_featurecode)); - } - if (!ast_strlen_zero(chan_featurecode)) { - ast_dtmf_stream(peer, chan, chan_featurecode, 0, f ? f->len : 0); - memset(chan_featurecode, 0, sizeof(chan_featurecode)); - } - if (f) - ast_frfree(f); - hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode); - if (!hasfeatures) { - /* No more digits expected - reset the timer */ - config->feature_timer = 0; - } - hadfeatures = hasfeatures; - /* Continue as we were */ - continue; - } else if (!f) { - /* The bridge returned without a frame and there is a feature in progress. - * However, we don't think the feature has quite yet timed out, so just - * go back into the bridge. */ - continue; - } - } else { - if (config->feature_timer <=0) { - /* We ran out of time */ - config->feature_timer = 0; - who = chan; - if (f) - ast_frfree(f); - f = NULL; - res = 0; - } - } - } - if (res < 0) { - if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) && !ast_test_flag(ast_channel_flags(peer), AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer)) { - ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", ast_channel_name(chan), ast_channel_name(peer)); - } - goto before_you_go; + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - if (!f || (f->frametype == AST_FRAME_CONTROL && - (f->subclass.integer == AST_CONTROL_HANGUP || f->subclass.integer == AST_CONTROL_BUSY || - f->subclass.integer == AST_CONTROL_CONGESTION))) { - res = -1; - break; - } - /* many things should be sent to the 'other' channel */ - other = (who == chan) ? peer : chan; - if (f->frametype == AST_FRAME_CONTROL) { - switch (f->subclass.integer) { - case AST_CONTROL_RINGING: - case AST_CONTROL_FLASH: - case AST_CONTROL_MCID: - case -1: - ast_indicate(other, f->subclass.integer); - break; - case AST_CONTROL_CONNECTED_LINE: - if (ast_channel_connected_line_sub(who, other, f, 1) && - ast_channel_connected_line_macro(who, other, f, who != chan, 1)) { - ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen); - } - break; - case AST_CONTROL_REDIRECTING: - if (ast_channel_redirecting_sub(who, other, f, 1) && - ast_channel_redirecting_macro(who, other, f, who != chan, 1)) { - ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen); - } - break; - case AST_CONTROL_PVT_CAUSE_CODE: - case AST_CONTROL_AOC: - case AST_CONTROL_HOLD: - case AST_CONTROL_UNHOLD: - ast_indicate_data(other, f->subclass.integer, f->data.ptr, f->datalen); - break; - case AST_CONTROL_OPTION: - aoh = f->data.ptr; - /* Forward option Requests, but only ones we know are safe - * These are ONLY sent by chan_iax2 and I'm not convinced that - * they are useful. I haven't deleted them entirely because I - * just am not sure of the ramifications of removing them. */ - if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) { - switch (ntohs(aoh->option)) { - case AST_OPTION_TONE_VERIFY: - case AST_OPTION_TDD: - case AST_OPTION_RELAXDTMF: - case AST_OPTION_AUDIO_MODE: - case AST_OPTION_DIGIT_DETECT: - case AST_OPTION_FAX_DETECT: - ast_channel_setoption(other, ntohs(aoh->option), aoh->data, - f->datalen - sizeof(struct ast_option_header), 0); - } - } - break; - } - } else if (f->frametype == AST_FRAME_DTMF_BEGIN) { - struct ast_flags *cfg; - char dtmfcode[2] = { f->subclass.integer, }; - size_t featurelen; - - if (who == chan) { - featurelen = strlen(chan_featurecode); - cfg = &(config->features_caller); - } else { - featurelen = strlen(peer_featurecode); - cfg = &(config->features_callee); - } - /* Take a peek if this (possibly) matches a feature. If not, just pass this - * DTMF along untouched. If this is not the first digit of a multi-digit code - * then we need to fall through and stream the characters if it matches */ - if (featurelen == 0 - && feature_check(chan, cfg, &dtmfcode[0]) == AST_FEATURE_RETURN_PASSDIGITS) { - if (option_debug > 3) { - ast_log(LOG_DEBUG, "Passing DTMF through, since it is not a feature code\n"); - } - ast_write(other, f); - sendingdtmfdigit = 1; - } else { - /* If ast_opt_transmit_silence is set, then we need to make sure we are - * transmitting something while we hold on to the DTMF waiting for a - * feature. */ - if (!silgen && ast_opt_transmit_silence) { - silgen = ast_channel_start_silence_generator(other); - } - if (option_debug > 3) { - ast_log(LOG_DEBUG, "Not passing DTMF through, since it may be a feature code\n"); - } - } - } else if (f->frametype == AST_FRAME_DTMF_END) { - char *featurecode; - int sense; - unsigned int dtmfduration = f->len; - - hadfeatures = hasfeatures; - /* This cannot overrun because the longest feature is one shorter than our buffer */ - if (who == chan) { - sense = FEATURE_SENSE_CHAN; - featurecode = chan_featurecode; - } else { - sense = FEATURE_SENSE_PEER; - featurecode = peer_featurecode; - } + if (ast_bridge_features_limits_construct(&call_duration_limits_peer)) { + ast_log(LOG_ERROR, "Could not construct callee duration limits. Bridge canceled.\n"); + ast_bridge_features_limits_destroy(&call_duration_limits_chan); - if (sendingdtmfdigit == 1) { - /* We let the BEGIN go through happily, so let's not bother with the END, - * since we already know it's not something we bother with */ - ast_write(other, f); - sendingdtmfdigit = 0; - } else { - /*! append the event to featurecode. we rely on the string being zero-filled, and - * not overflowing it. - * \todo XXX how do we guarantee the latter ? - */ - featurecode[strlen(featurecode)] = f->subclass.integer; - /* Get rid of the frame before we start doing "stuff" with the channels */ - ast_frfree(f); - f = NULL; - if (silgen) { - ast_channel_stop_silence_generator(other, silgen); - silgen = NULL; - } - config->feature_timer = 0; - res = feature_interpret(chan, peer, config, featurecode, sense); - switch(res) { - case AST_FEATURE_RETURN_PASSDIGITS: - ast_dtmf_stream(other, who, featurecode, 0, dtmfduration); - /* Fall through */ - case AST_FEATURE_RETURN_SUCCESS: - memset(featurecode, 0, sizeof(chan_featurecode)); - break; - } - if (res >= AST_FEATURE_RETURN_PASSDIGITS) { - res = 0; - } else { - break; - } - hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode); - if (hadfeatures && !hasfeatures) { - /* Feature completed or timed out */ - config->feature_timer = 0; - } else if (hasfeatures) { - if (config->timelimit) { - /* No warning next time - we are waiting for feature code */ - ast_set_flag(config, AST_FEATURE_WARNING_ACTIVE); - } - config->feature_start_time = ast_tvnow(); - config->feature_timer = featuredigittimeout; - ast_debug(1, "Set feature timer to %ld ms\n", config->feature_timer); - } - } + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - if (f) - ast_frfree(f); - } - ast_cel_report_event(chan, AST_CEL_BRIDGE_END, NULL, NULL, peer); -before_you_go: - if (ast_channel_sending_dtmf_digit(chan)) { - ast_bridge_end_dtmf(chan, ast_channel_sending_dtmf_digit(chan), - ast_channel_sending_dtmf_tv(chan), "bridge end"); - } - if (ast_channel_sending_dtmf_digit(peer)) { - ast_bridge_end_dtmf(peer, ast_channel_sending_dtmf_digit(peer), - ast_channel_sending_dtmf_tv(peer), "bridge end"); - } + bridge_config_set_limits(config, &call_duration_limits_chan, &call_duration_limits_peer); - /* Just in case something weird happened and we didn't clean up the silence generator... */ - if (silgen) { - ast_channel_stop_silence_generator(who == chan ? peer : chan, silgen); - silgen = NULL; - } + if (ast_bridge_features_set_limits(&chan_features, &call_duration_limits_chan, 0)) { + abandon_call = 1; + } + if (ast_bridge_features_set_limits(peer_features, &call_duration_limits_peer, 0)) { + abandon_call = 1; + } - /* Wait for any dual redirect to complete. */ - while (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT)) { - sched_yield(); - } + /* At this point we are done with the limits structs since they have been copied to the individual feature sets. */ + ast_bridge_features_limits_destroy(&call_duration_limits_chan); + ast_bridge_features_limits_destroy(&call_duration_limits_peer); - if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT)) { - ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); /* its job is done */ - if (bridge_cdr) { - ast_cdr_discard(bridge_cdr); - /* QUESTION: should we copy bridge_cdr fields to the peer before we throw it away? */ + if (abandon_call) { + ast_log(LOG_ERROR, "Could not set duration limits on one or more sides of the call. Bridge canceled.\n"); + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */ } - if (config->end_bridge_callback) { - config->end_bridge_callback(config->end_bridge_callback_data); + /* Create bridge */ + bridge = ast_bridge_basic_new(); + if (!bridge) { + ast_bridge_features_destroy(peer_features); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - /* run the hangup exten on the chan object IFF it was NOT involved in a parking situation - * if it were, then chan belongs to a different thread now, and might have been hung up long - * ago. - */ - if (!(ast_channel_softhangup_internal_flag(chan) & (AST_SOFTHANGUP_ASYNCGOTO | AST_SOFTHANGUP_UNBRIDGE)) - && !ast_test_flag(&config->features_caller, AST_FEATURE_NO_H_EXTEN)) { - struct ast_cdr *swapper = NULL; - char savelastapp[AST_MAX_EXTENSION]; - char savelastdata[AST_MAX_EXTENSION]; - char save_context[AST_MAX_CONTEXT]; - char save_exten[AST_MAX_EXTENSION]; - int save_prio; - - ast_channel_lock(chan); - if (bridge_cdr) { - /* - * Swap the bridge_cdr and the chan cdr for a moment, and let - * the hangup dialplan code operate on it. - */ - swapper = ast_channel_cdr(chan); - ast_channel_cdr_set(chan, bridge_cdr); - - /* protect the lastapp/lastdata against the effects of the hangup/dialplan code */ - ast_copy_string(savelastapp, bridge_cdr->lastapp, sizeof(bridge_cdr->lastapp)); - ast_copy_string(savelastdata, bridge_cdr->lastdata, sizeof(bridge_cdr->lastdata)); - } - ast_copy_string(save_context, ast_channel_context(chan), sizeof(save_context)); - ast_copy_string(save_exten, ast_channel_exten(chan), sizeof(save_exten)); - save_prio = ast_channel_priority(chan); - ast_channel_unlock(chan); - - ast_autoservice_start(peer); - if (ast_exists_extension(chan, ast_channel_context(chan), "h", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, - ast_channel_caller(chan)->id.number.str, NULL))) { - ast_pbx_h_exten_run(chan, ast_channel_context(chan)); - hangup_run = 1; - } else if (!ast_strlen_zero(ast_channel_macrocontext(chan)) - && ast_exists_extension(chan, ast_channel_macrocontext(chan), "h", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, - ast_channel_caller(chan)->id.number.str, NULL))) { - ast_pbx_h_exten_run(chan, ast_channel_macrocontext(chan)); - hangup_run = 1; - } - if (ast_pbx_hangup_handler_run(chan)) { - /* Indicate hangup handlers were run. */ - hangup_run = 1; - } - ast_autoservice_stop(peer); - - ast_channel_lock(chan); - - /* swap it back */ - ast_channel_context_set(chan, save_context); - ast_channel_exten_set(chan, save_exten); - ast_channel_priority_set(chan, save_prio); - if (bridge_cdr) { - if (ast_channel_cdr(chan) == bridge_cdr) { - ast_channel_cdr_set(chan, swapper); - - /* Restore the lastapp/lastdata */ - ast_copy_string(bridge_cdr->lastapp, savelastapp, sizeof(bridge_cdr->lastapp)); - ast_copy_string(bridge_cdr->lastdata, savelastdata, sizeof(bridge_cdr->lastdata)); - } else { - bridge_cdr = NULL; - } - } - ast_channel_unlock(chan); + /* Put peer into the bridge */ + if (ast_bridge_impart(bridge, peer, NULL, peer_features, 1)) { + ast_bridge_destroy(bridge); + ast_bridge_features_cleanup(&chan_features); + bridge_failed_peer_goto(chan, peer); + return -1; } - /* obey the NoCDR() wishes. -- move the DISABLED flag to the bridge CDR if it was set on the channel during the bridge... */ - new_chan_cdr = pick_unlocked_cdr(ast_channel_cdr(chan)); /* the proper chan cdr, if there are forked cdrs */ + /* Join bridge */ + ast_bridge_join(bridge, chan, NULL, &chan_features, NULL, 1); /* - * If the channel CDR has been modified during the call, record - * the changes in the bridge cdr, BUT, if hangup_run, the CDR - * got swapped so don't overwrite what was done in the - * h-extension or hangup handlers. What a mess. This is why - * you never touch CDR code. + * If the bridge was broken for a hangup that isn't real, then + * don't run the h extension, because the channel isn't really + * hung up. This should really only happen with + * AST_SOFTHANGUP_ASYNCGOTO. */ - if (new_chan_cdr && bridge_cdr && !hangup_run) { - ast_cdr_copy_vars(bridge_cdr, new_chan_cdr); - ast_copy_string(bridge_cdr->userfield, new_chan_cdr->userfield, sizeof(bridge_cdr->userfield)); - bridge_cdr->amaflags = new_chan_cdr->amaflags; - ast_copy_string(bridge_cdr->accountcode, new_chan_cdr->accountcode, sizeof(bridge_cdr->accountcode)); - if (ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED)) { - ast_set_flag(bridge_cdr, AST_CDR_FLAG_POST_DISABLED); - } - } - - /* we can post the bridge CDR at this point */ - if (bridge_cdr) { - ast_cdr_end(bridge_cdr); - ast_cdr_detach(bridge_cdr); - } - - /* do a specialized reset on the beginning channel - CDR's, if they still exist, so as not to mess up - issues in future bridges; - - Here are the rules of the game: - 1. The chan and peer channel pointers will not change - during the life of the bridge. - 2. But, in transfers, the channel names will change. - between the time the bridge is started, and the - time the channel ends. - Usually, when a channel changes names, it will - also change CDR pointers. - 3. Usually, only one of the two channels (chan or peer) - will change names. - 4. Usually, if a channel changes names during a bridge, - it is because of a transfer. Usually, in these situations, - it is normal to see 2 bridges running simultaneously, and - it is not unusual to see the two channels that change - swapped between bridges. - 5. After a bridge occurs, we have 2 or 3 channels' CDRs - to attend to; if the chan or peer changed names, - we have the before and after attached CDR's. - */ - - if (new_chan_cdr) { - struct ast_channel *chan_ptr = NULL; - - if (strcasecmp(orig_channame, ast_channel_name(chan)) != 0) { - /* old channel */ - if ((chan_ptr = ast_channel_get_by_name(orig_channame))) { - ast_channel_lock(chan_ptr); - if (!ast_bridged_channel(chan_ptr)) { - struct ast_cdr *cur; - for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) { - if (cur == chan_cdr) { - break; - } - } - if (cur) { - ast_cdr_specialized_reset(chan_cdr, 0); - } - } - ast_channel_unlock(chan_ptr); - chan_ptr = ast_channel_unref(chan_ptr); - } - /* new channel */ - ast_cdr_specialized_reset(new_chan_cdr, 0); - } else { - ast_cdr_specialized_reset(ast_channel_cdr(chan), 0); /* nothing changed, reset the chan cdr */ - } - } - - { - struct ast_channel *chan_ptr = NULL; - new_peer_cdr = pick_unlocked_cdr(ast_channel_cdr(peer)); /* the proper chan cdr, if there are forked cdrs */ - if (new_chan_cdr && ast_test_flag(new_chan_cdr, AST_CDR_FLAG_POST_DISABLED) && new_peer_cdr && !ast_test_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED)) - ast_set_flag(new_peer_cdr, AST_CDR_FLAG_POST_DISABLED); /* DISABLED is viral-- it will propagate across a bridge */ - if (strcasecmp(orig_peername, ast_channel_name(peer)) != 0) { - /* old channel */ - if ((chan_ptr = ast_channel_get_by_name(orig_peername))) { - ast_channel_lock(chan_ptr); - if (!ast_bridged_channel(chan_ptr)) { - struct ast_cdr *cur; - for (cur = ast_channel_cdr(chan_ptr); cur; cur = cur->next) { - if (cur == peer_cdr) { - break; - } - } - if (cur) { - ast_cdr_specialized_reset(peer_cdr, 0); - } - } - ast_channel_unlock(chan_ptr); - chan_ptr = ast_channel_unref(chan_ptr); - } - /* new channel */ - if (new_peer_cdr) { - ast_cdr_specialized_reset(new_peer_cdr, 0); - } - } else { - if (we_disabled_peer_cdr) { - ast_clear_flag(ast_channel_cdr(peer), AST_CDR_FLAG_POST_DISABLED); - } - ast_cdr_specialized_reset(ast_channel_cdr(peer), 0); /* nothing changed, reset the peer cdr */ - } + res = -1; + ast_channel_lock(chan); + if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) { + res = 0; + } + ast_channel_unlock(chan); + + ast_bridge_features_cleanup(&chan_features); + +/* BUGBUG this is used by Dial and FollowMe for CDR information. By Queue for Queue stats like CDRs. */ + if (res && config->end_bridge_callback) { + config->end_bridge_callback(config->end_bridge_callback_data); } return res; @@ -5456,395 +5096,6 @@ AST_APP_OPTIONS(park_call_options, BEGIN_OPTIONS AST_APP_OPTION('s', AST_PARK_OPT_SILENCE), END_OPTIONS ); -/*! \brief Park a call */ -static int park_call_exec(struct ast_channel *chan, const char *data) -{ - struct ast_park_call_args args = { 0, }; - struct ast_flags flags = { 0 }; - char orig_exten[AST_MAX_EXTENSION]; - int orig_priority; - int res; - const char *pl_name; - char *parse; - struct park_app_args app_args; - - /* - * Cache the original channel name because we are going to - * masquerade the channel. Prefer the BLINDTRANSFER channel - * name over this channel name. BLINDTRANSFER could be set if - * the parking access extension did not get detected and we are - * executing the Park application from the dialplan. - * - * The orig_chan_name is used to return the call to the - * originator on parking timeout. - */ - args.orig_chan_name = ast_strdupa(S_OR( - pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"), ast_channel_name(chan))); - - /* Answer if call is not up */ - if (ast_channel_state(chan) != AST_STATE_UP) { - if (ast_answer(chan)) { - return -1; - } - - /* Sleep to allow VoIP streams to settle down */ - if (ast_safe_sleep(chan, 1000)) { - return -1; - } - } - - /* Process the dialplan application options. */ - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(app_args, parse); - - if (!ast_strlen_zero(app_args.timeout)) { - if (sscanf(app_args.timeout, "%30d", &args.timeout) != 1) { - ast_log(LOG_WARNING, "Invalid timeout '%s' provided\n", app_args.timeout); - args.timeout = 0; - } - } - if (!ast_strlen_zero(app_args.return_con)) { - args.return_con = app_args.return_con; - } - if (!ast_strlen_zero(app_args.return_ext)) { - args.return_ext = app_args.return_ext; - } - if (!ast_strlen_zero(app_args.return_pri)) { - if (sscanf(app_args.return_pri, "%30d", &args.return_pri) != 1) { - ast_log(LOG_WARNING, "Invalid priority '%s' specified\n", app_args.return_pri); - args.return_pri = 0; - } - } - - ast_app_parse_options(park_call_options, &flags, NULL, app_args.options); - args.flags = flags.flags; - - /* - * Setup the exten/priority to be s/1 since we don't know where - * this call should return. - */ - ast_copy_string(orig_exten, ast_channel_exten(chan), sizeof(orig_exten)); - orig_priority = ast_channel_priority(chan); - ast_channel_exten_set(chan, "s"); - ast_channel_priority_set(chan, 1); - - /* Park the call */ - if (!ast_strlen_zero(app_args.pl_name)) { - pl_name = app_args.pl_name; - } else { - pl_name = findparkinglotname(chan); - } - if (ast_strlen_zero(pl_name)) { - /* Parking lot is not specified, so use the default parking lot. */ - args.parkinglot = parkinglot_addref(default_parkinglot); - } else { - args.parkinglot = find_parkinglot(pl_name); - if (!args.parkinglot && parkeddynamic) { - args.parkinglot = create_dynamic_parkinglot(pl_name, chan); - } - } - if (args.parkinglot) { - res = masq_park_call(chan, chan, &args); - parkinglot_unref(args.parkinglot); - } else { - /* Parking failed because the parking lot does not exist. */ - if (!ast_test_flag(&args, AST_PARK_OPT_SILENCE)) { - ast_stream_and_wait(chan, "pbx-parkingfailed", ""); - } - res = -1; - } - if (res) { - /* Park failed, try to continue in the dialplan. */ - ast_channel_exten_set(chan, orig_exten); - ast_channel_priority_set(chan, orig_priority); - res = 0; - } else { - /* Park succeeded. */ - res = -1; - } - - return res; -} - -/*! \brief Pickup parked call */ -static int parked_call_exec(struct ast_channel *chan, const char *data) -{ - int res; - struct ast_channel *peer = NULL; - struct parkeduser *pu; - struct ast_context *con; - char *parse; - const char *pl_name; - int park = 0; - struct ast_bridge_config config; - struct ast_parkinglot *parkinglot; - AST_DECLARE_APP_ARGS(app_args, - AST_APP_ARG(pl_space); /*!< Parking lot space to retrieve if present. */ - AST_APP_ARG(pl_name); /*!< Parking lot name to use if present. */ - AST_APP_ARG(dummy); /*!< Place to put any remaining args string. */ - ); - - parse = ast_strdupa(data); - AST_STANDARD_APP_ARGS(app_args, parse); - - if (!ast_strlen_zero(app_args.pl_space)) { - if (sscanf(app_args.pl_space, "%30u", &park) != 1) { - ast_log(LOG_WARNING, "Specified parking extension not a number: %s\n", - app_args.pl_space); - park = -1; - } - } - - if (!ast_strlen_zero(app_args.pl_name)) { - pl_name = app_args.pl_name; - } else { - pl_name = findparkinglotname(chan); - } - if (ast_strlen_zero(pl_name)) { - /* Parking lot is not specified, so use the default parking lot. */ - parkinglot = parkinglot_addref(default_parkinglot); - } else { - parkinglot = find_parkinglot(pl_name); - if (!parkinglot) { - /* It helps to answer the channel if not already up. :) */ - if (ast_channel_state(chan) != AST_STATE_UP) { - ast_answer(chan); - } - if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) { - ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", - "pbx-invalidpark", ast_channel_name(chan)); - } - ast_log(LOG_WARNING, - "Channel %s tried to retrieve parked call from unknown parking lot '%s'\n", - ast_channel_name(chan), pl_name); - return -1; - } - } - - AST_LIST_LOCK(&parkinglot->parkings); - AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot->parkings, pu, list) { - if ((ast_strlen_zero(app_args.pl_space) || pu->parkingnum == park) - && !pu->notquiteyet && !ast_channel_pbx(pu->chan)) { - /* The parking space has a call and can be picked up now. */ - AST_LIST_REMOVE_CURRENT(list); - break; - } - } - AST_LIST_TRAVERSE_SAFE_END; - if (pu) { - struct ast_callid *callid = ast_read_threadstorage_callid(); - - /* Found a parked call to pickup. */ - peer = pu->chan; - - /* We need to map the call id we have from this thread to the channel we found. */ - if (callid) { - ast_channel_callid_set(peer, callid); - callid = ast_callid_unref(callid); - } - - con = ast_context_find(parkinglot->cfg.parking_con); - if (con) { - if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL, 0)) { - ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); - } else { - notify_metermaids(pu->parkingexten, parkinglot->cfg.parking_con, AST_DEVICE_NOT_INUSE); - } - } else { - ast_log(LOG_WARNING, "Whoa, no parking context?\n"); - } - - ast_cel_report_event(pu->chan, AST_CEL_PARK_END, NULL, "UnParkedCall", chan); - /*** DOCUMENTATION - <managerEventInstance> - <synopsis>Raised when a call has been unparked.</synopsis> - <syntax> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Exten'])" /> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='Parkinglot'])" /> - <xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter[@name='From'])" /> - </syntax> - <see-also> - <ref type="application">ParkedCall</ref> - <ref type="managerEvent">ParkedCall</ref> - </see-also> - </managerEventInstance> - ***/ - ast_manager_event(pu->chan, EVENT_FLAG_CALL, "UnParkedCall", - "Exten: %s\r\n" - "Channel: %s\r\n" - "Parkinglot: %s\r\n" - "From: %s\r\n" - "CallerIDNum: %s\r\n" - "CallerIDName: %s\r\n" - "ConnectedLineNum: %s\r\n" - "ConnectedLineName: %s\r\n" - "Uniqueid: %s\r\n", - pu->parkingexten, ast_channel_name(pu->chan), pu->parkinglot->name, - ast_channel_name(chan), - S_COR(ast_channel_caller(pu->chan)->id.number.valid, ast_channel_caller(pu->chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_caller(pu->chan)->id.name.valid, ast_channel_caller(pu->chan)->id.name.str, "<unknown>"), - S_COR(ast_channel_connected(pu->chan)->id.number.valid, ast_channel_connected(pu->chan)->id.number.str, "<unknown>"), - S_COR(ast_channel_connected(pu->chan)->id.name.valid, ast_channel_connected(pu->chan)->id.name.str, "<unknown>"), - ast_channel_uniqueid(pu->chan) - ); - - /* Stop entertaining the caller. */ - switch (pu->hold_method) { - case AST_CONTROL_HOLD: - ast_indicate(pu->chan, AST_CONTROL_UNHOLD); - break; - case AST_CONTROL_RINGING: - ast_indicate(pu->chan, -1); - break; - default: - break; - } - pu->hold_method = 0; - - parkinglot_unref(pu->parkinglot); - ast_free(pu); - } - AST_LIST_UNLOCK(&parkinglot->parkings); - - if (peer) { - /* Update connected line between retrieving call and parked call. */ - struct ast_party_connected_line connected; - - ast_party_connected_line_init(&connected); - - /* Send our caller-id to peer. */ - ast_channel_lock(chan); - ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan)); - ast_channel_unlock(chan); - connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; - if (ast_channel_connected_line_sub(chan, peer, &connected, 0) && - ast_channel_connected_line_macro(chan, peer, &connected, 0, 0)) { - ast_channel_update_connected_line(peer, &connected, NULL); - } - - /* - * Get caller-id from peer. - * - * Update the retrieving call before it is answered if possible - * for best results. Some phones do not support updating the - * connected line information after connection. - */ - ast_channel_lock(peer); - ast_connected_line_copy_from_caller(&connected, ast_channel_caller(peer)); - ast_channel_unlock(peer); - connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; - if (ast_channel_connected_line_sub(peer, chan, &connected, 0) && - ast_channel_connected_line_macro(peer, chan, &connected, 1, 0)) { - ast_channel_update_connected_line(chan, &connected, NULL); - } - - ast_party_connected_line_free(&connected); - } - - /* JK02: it helps to answer the channel if not already up */ - if (ast_channel_state(chan) != AST_STATE_UP) { - ast_answer(chan); - } - - if (peer) { - struct ast_datastore *features_datastore; - struct ast_dial_features *dialfeatures; - - /* Play a courtesy to the source(s) configured to prefix the bridge connecting */ - if (!ast_strlen_zero(courtesytone)) { - static const char msg[] = "courtesy tone"; - - switch (parkedplay) { - case 0:/* Courtesy tone to pickup chan */ - res = play_message_to_chans(chan, peer, -1, msg, courtesytone); - break; - case 1:/* Courtesy tone to parked chan */ - res = play_message_to_chans(chan, peer, 1, msg, courtesytone); - break; - case 2:/* Courtesy tone to both chans */ - res = play_message_to_chans(chan, peer, 0, msg, courtesytone); - break; - default: - res = 0; - break; - } - if (res) { - ast_autoservice_chan_hangup_peer(chan, peer); - parkinglot_unref(parkinglot); - return -1; - } - } - - res = ast_channel_make_compatible(chan, peer); - if (res < 0) { - ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", ast_channel_name(chan), ast_channel_name(peer)); - ast_autoservice_chan_hangup_peer(chan, peer); - parkinglot_unref(parkinglot); - return -1; - } - /* This runs sorta backwards, since we give the incoming channel control, as if it - were the person called. */ - ast_verb(3, "Channel %s connected to parked call %d\n", ast_channel_name(chan), park); - - pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer)); - ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer)); - memset(&config, 0, sizeof(struct ast_bridge_config)); - - /* Get datastore for peer and apply it's features to the callee side of the bridge config */ - ast_channel_lock(peer); - features_datastore = ast_channel_datastore_find(peer, &dial_features_info, NULL); - if (features_datastore && (dialfeatures = features_datastore->data)) { - ast_copy_flags(&config.features_callee, &dialfeatures->my_features, - AST_FLAGS_ALL); - } - ast_channel_unlock(peer); - - if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); - } - if ((parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); - } - if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL); - } - if ((parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL); - } - if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT); - } - if ((parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallhangup == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT); - } - if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLEE) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_callee), AST_FEATURE_AUTOMON); - } - if ((parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYCALLER) || (parkinglot->cfg.parkedcallrecording == AST_FEATURE_FLAG_BYBOTH)) { - ast_set_flag(&(config.features_caller), AST_FEATURE_AUTOMON); - } - - res = ast_bridge_call(chan, peer, &config); - - pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", ast_channel_name(peer)); - ast_cdr_setdestchan(ast_channel_cdr(chan), ast_channel_name(peer)); - - /* Simulate the PBX hanging up */ - ast_autoservice_chan_hangup_peer(chan, peer); - } else { - if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) { - ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", - ast_channel_name(chan)); - } - ast_verb(3, "Channel %s tried to retrieve nonexistent parked call %d\n", - ast_channel_name(chan), park); - res = -1; - } - - parkinglot_unref(parkinglot); - return res; -} - /*! * \brief Unreference parkinglot object. */ @@ -6282,6 +5533,10 @@ static void process_applicationmap_line(struct ast_variable *var) return; } + /* + * We will parse and require correct syntax for the ActivatedBy + * option, but the ActivatedBy option is not honored anymore. + */ if (ast_strlen_zero(args.activatedby)) { ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); } else if (!strcasecmp(args.activatedby, "caller")) { @@ -7235,8 +6490,6 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl { int i; struct ast_call_feature *feature; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; #define HFS_FORMAT "%-25s %-7s %-7s\n" switch (cmd) { @@ -7292,28 +6545,7 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl } AST_RWLIST_UNLOCK(&feature_groups); - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - ast_cli(a->fd, "\nCall parking (Parking lot: %s)\n", curlot->name); - ast_cli(a->fd, "------------\n"); - ast_cli(a->fd,"%-22s: %s\n", "Parking extension", curlot->cfg.parkext); - ast_cli(a->fd,"%-22s: %s\n", "Parking context", curlot->cfg.parking_con); - ast_cli(a->fd,"%-22s: %d-%d\n", "Parked call extensions", - curlot->cfg.parking_start, curlot->cfg.parking_stop); - ast_cli(a->fd,"%-22s: %u ms\n", "Parkingtime", curlot->cfg.parkingtime); - ast_cli(a->fd,"%-22s: %s\n", "Comeback to origin", - (curlot->cfg.comebacktoorigin ? "yes" : "no")); - ast_cli(a->fd,"%-22s: %s%s\n", "Comeback context", - curlot->cfg.comebackcontext, (curlot->cfg.comebacktoorigin ? - " (comebacktoorigin=yes, not used)" : "")); - ast_cli(a->fd,"%-22s: %d\n", "Comeback dial time", - curlot->cfg.comebackdialtime); - ast_cli(a->fd,"%-22s: %s\n", "MusicOnHold class", curlot->cfg.mohclass); - ast_cli(a->fd,"%-22s: %s\n", "Enabled", AST_CLI_YESNO(!curlot->disabled)); - ast_cli(a->fd,"\n"); - ao2_ref(curlot, -1); - } - ao2_iterator_destroy(&iter); + ast_cli(a->fd, "\n"); return CLI_SUCCESS; } @@ -7511,8 +6743,8 @@ static int action_bridge(struct mansession *s, const struct message *m) return 0; } - tobj->chan = tmpchana; - tobj->peer = tmpchanb; + tobj->chan = tmpchanb; + tobj->peer = tmpchana; tobj->return_to_pbx = 1; if (ast_true(playtone)) { @@ -7545,6 +6777,7 @@ static int action_bridge(struct mansession *s, const struct message *m) "Channel1: %s\r\n" "Channel2: %s\r\n", ast_channel_name(tmpchana), ast_channel_name(tmpchanb)); +/* BUGBUG there seems to be no COLP update here. */ bridge_call_thread_launch(tobj); astman_send_ack(s, m, "Launched bridge thread with success"); @@ -7552,180 +6785,11 @@ static int action_bridge(struct mansession *s, const struct message *m) return 0; } -/*! - * \brief CLI command to list parked calls - * \param e - * \param cmd - * \param a - * - * Check right usage, lock parking lot, display parked calls, unlock parking lot list. - * \retval CLI_SUCCESS on success. - * \retval CLI_SHOWUSAGE on incorrect number of arguments. - * \retval NULL when tab completion is used. - */ -static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct parkeduser *cur; - int numparked = 0; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; - - switch (cmd) { - case CLI_INIT: - e->command = "parkedcalls show"; - e->usage = - "Usage: parkedcalls show\n" - " List currently parked calls\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc > e->args) - return CLI_SHOWUSAGE; - - ast_cli(a->fd, "%-10s %-25s (%-15s %-12s %4s) %s\n", "Num", "Channel", - "Context", "Extension", "Pri", "Timeout"); - - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - int lotparked = 0; - - /* subtract ref for iterator and for configured parking lot */ - ast_cli(a->fd, "*** Parking lot: %s (%d)\n", curlot->name, - ao2_ref(curlot, 0) - 2 - (curlot == default_parkinglot)); - - AST_LIST_LOCK(&curlot->parkings); - AST_LIST_TRAVERSE(&curlot->parkings, cur, list) { - ast_cli(a->fd, "%-10.10s %-25s (%-15s %-12s %4d) %6lds\n", - cur->parkingexten, ast_channel_name(cur->chan), cur->context, cur->exten, - cur->priority, - (long) (cur->start.tv_sec + (cur->parkingtime / 1000) - time(NULL))); - ++lotparked; - } - AST_LIST_UNLOCK(&curlot->parkings); - if (lotparked) { - numparked += lotparked; - ast_cli(a->fd, " %d parked call%s in parking lot %s\n", lotparked, - ESS(lotparked), curlot->name); - } - - ao2_ref(curlot, -1); - } - ao2_iterator_destroy(&iter); - - ast_cli(a->fd, "---\n%d parked call%s in total.\n", numparked, ESS(numparked)); - - return CLI_SUCCESS; -} - static struct ast_cli_entry cli_features[] = { AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), AST_CLI_DEFINE(handle_features_reload, "Reloads configured features"), - AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls"), }; -static int manager_parkinglot_list(struct mansession *s, const struct message *m) -{ - const char *id = astman_get_header(m, "ActionID"); - char idText[256] = ""; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; - - if (!ast_strlen_zero(id)) - snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - - astman_send_ack(s, m, "Parking lots will follow"); - - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - astman_append(s, "Event: Parkinglot\r\n" - "Name: %s\r\n" - "StartExten: %d\r\n" - "StopExten: %d\r\n" - "Timeout: %d\r\n" - "\r\n", - curlot->name, - curlot->cfg.parking_start, - curlot->cfg.parking_stop, - curlot->cfg.parkingtime ? curlot->cfg.parkingtime / 1000 : curlot->cfg.parkingtime); - ao2_ref(curlot, -1); - } - - astman_append(s, - "Event: ParkinglotsComplete\r\n" - "%s" - "\r\n",idText); - - return RESULT_SUCCESS; -} - -/*! - * \brief Dump parking lot status - * \param s - * \param m - * - * Lock parking lot, iterate list and append parked calls status, unlock parking lot. - * \return Always RESULT_SUCCESS - */ -static int manager_parking_status(struct mansession *s, const struct message *m) -{ - struct parkeduser *cur; - const char *id = astman_get_header(m, "ActionID"); - char idText[256] = ""; - struct ao2_iterator iter; - struct ast_parkinglot *curlot; - int numparked = 0; - long now = time(NULL); - - if (!ast_strlen_zero(id)) - snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); - - astman_send_ack(s, m, "Parked calls will follow"); - - iter = ao2_iterator_init(parkinglots, 0); - while ((curlot = ao2_iterator_next(&iter))) { - AST_LIST_LOCK(&curlot->parkings); - AST_LIST_TRAVERSE(&curlot->parkings, cur, list) { - astman_append(s, "Event: ParkedCall\r\n" - "Parkinglot: %s\r\n" - "Exten: %d\r\n" - "Channel: %s\r\n" - "From: %s\r\n" - "Timeout: %ld\r\n" - "Duration: %ld\r\n" - "CallerIDNum: %s\r\n" - "CallerIDName: %s\r\n" - "ConnectedLineNum: %s\r\n" - "ConnectedLineName: %s\r\n" - "%s" - "\r\n", - curlot->name, - cur->parkingnum, ast_channel_name(cur->chan), cur->peername, - (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - now, - now - (long) cur->start.tv_sec, - S_COR(ast_channel_caller(cur->chan)->id.number.valid, ast_channel_caller(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */ - S_COR(ast_channel_caller(cur->chan)->id.name.valid, ast_channel_caller(cur->chan)->id.name.str, ""), - S_COR(ast_channel_connected(cur->chan)->id.number.valid, ast_channel_connected(cur->chan)->id.number.str, ""), /* XXX in other places it is <unknown> */ - S_COR(ast_channel_connected(cur->chan)->id.name.valid, ast_channel_connected(cur->chan)->id.name.str, ""), - idText); - ++numparked; - } - AST_LIST_UNLOCK(&curlot->parkings); - ao2_ref(curlot, -1); - } - ao2_iterator_destroy(&iter); - - astman_append(s, - "Event: ParkedCallsComplete\r\n" - "Total: %d\r\n" - "%s" - "\r\n", - numparked, idText); - - return RESULT_SUCCESS; -} - /*! * \brief Create manager event for parked calls * \param s @@ -8210,8 +7274,11 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con calldurationlimit->tv_usec = (config->timelimit % 1000) * 1000; ast_verb(3, "Setting call duration limit to %.3lf seconds.\n", calldurationlimit->tv_sec + calldurationlimit->tv_usec / 1000000.0); - config->timelimit = play_to_caller = play_to_callee = - config->play_warning = config->warning_freq = 0; + play_to_caller = 0; + play_to_callee = 0; + config->timelimit = 0; + config->play_warning = 0; + config->warning_freq = 0; } else { ast_verb(4, "Limit Data for this call:\n"); ast_verb(4, "timelimit = %ld ms (%.3lf s)\n", config->timelimit, config->timelimit / 1000.0); @@ -8242,12 +7309,17 @@ int ast_bridge_timelimit(struct ast_channel *chan, struct ast_bridge_config *con */ static int bridge_exec(struct ast_channel *chan, const char *data) { - struct ast_channel *current_dest_chan, *final_dest_chan, *chans[2]; + struct ast_channel *current_dest_chan; + struct ast_channel *final_dest_chan; + struct ast_channel *chans[2]; char *tmp_data = NULL; struct ast_flags opts = { 0, }; struct ast_bridge_config bconfig = { { 0, }, }; char *opt_args[OPT_ARG_ARRAY_SIZE]; struct timeval calldurationlimit = { 0, }; + const char *context; + const char *extension; + int priority; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(dest_chan); @@ -8357,8 +7429,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data) "Channel1: %s\r\n" "Channel2: %s\r\n", ast_channel_name(chan), ast_channel_name(final_dest_chan)); - /* Maybe we should return this channel to the PBX? */ - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); + bridge_failed_peer_goto(chan, final_dest_chan); pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE"); current_dest_chan = ast_channel_unref(current_dest_chan); @@ -8406,60 +7477,28 @@ static int bridge_exec(struct ast_channel *chan, const char *data) if (ast_test_flag(&opts, OPT_CALLER_PARK)) ast_set_flag(&(bconfig.features_caller), AST_FEATURE_PARKCALL); - /* - * Don't let the after-bridge code run the h-exten. We want to - * continue in the dialplan. - */ - ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); + /* Setup after bridge goto location. */ + if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { + ast_channel_lock(chan); + context = ast_strdupa(ast_channel_context(chan)); + extension = ast_strdupa(ast_channel_exten(chan)); + priority = ast_channel_priority(chan); + ast_channel_unlock(chan); + ast_after_bridge_set_go_on(final_dest_chan, context, extension, priority, + opt_args[OPT_ARG_CALLEE_GO_ON]); + } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) { + ast_channel_lock(final_dest_chan); + context = ast_strdupa(ast_channel_context(final_dest_chan)); + extension = ast_strdupa(ast_channel_exten(final_dest_chan)); + priority = ast_channel_priority(final_dest_chan); + ast_channel_unlock(final_dest_chan); + ast_after_bridge_set_goto(final_dest_chan, context, extension, priority); + } + ast_bridge_call(chan, final_dest_chan, &bconfig); /* The bridge has ended, set BRIDGERESULT to SUCCESS. */ pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS"); - - /* If the other channel has not been hung up, return it to the PBX */ - if (!ast_check_hangup(final_dest_chan)) { - if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { - char *caller_context; - char *caller_extension; - int caller_priority; - int goto_opt; - - ast_channel_lock(chan); - caller_context = ast_strdupa(ast_channel_context(chan)); - caller_extension = ast_strdupa(ast_channel_exten(chan)); - caller_priority = ast_channel_priority(chan); - ast_channel_unlock(chan); - - if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) { - ast_replace_subargument_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]); - /* Set current dialplan position to bridger dialplan position */ - goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority) - /* Then perform the goto */ - || ast_parseable_goto(final_dest_chan, opt_args[OPT_ARG_CALLEE_GO_ON]); - } else { /* F() */ - goto_opt = ast_goto_if_exists(final_dest_chan, caller_context, caller_extension, caller_priority + 1); - } - if (goto_opt || ast_pbx_start(final_dest_chan)) { - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } - } else if (!ast_test_flag(&opts, OPT_CALLEE_KILL)) { - ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n", - ast_channel_context(final_dest_chan), ast_channel_exten(final_dest_chan), - ast_channel_priority(final_dest_chan), ast_channel_name(final_dest_chan)); - - if (ast_pbx_start(final_dest_chan)) { - ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", ast_channel_name(final_dest_chan)); - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } else { - ast_debug(1, "SUCCESS continuing PBX on chan %s\n", ast_channel_name(final_dest_chan)); - } - } else { - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } - } else { - ast_debug(1, "chan %s was hungup\n", ast_channel_name(final_dest_chan)); - ast_autoservice_chan_hangup_peer(chan, final_dest_chan); - } done: ast_free((char *) bconfig.warning_sound); ast_free((char *) bconfig.end_sound); @@ -9130,10 +8169,7 @@ static void features_shutdown(void) ast_custom_function_unregister(&feature_function); ast_manager_unregister("Bridge"); ast_manager_unregister("Park"); - ast_manager_unregister("Parkinglots"); - ast_manager_unregister("ParkedCalls"); - ast_unregister_application(parkcall); - ast_unregister_application(parkedcall); + ast_unregister_application(app_bridge); pthread_cancel(parking_thread); @@ -9161,12 +8197,7 @@ int ast_features_init(void) return -1; } ast_register_application2(app_bridge, bridge_exec, NULL, NULL, NULL); - res = ast_register_application2(parkedcall, parked_call_exec, NULL, NULL, NULL); - if (!res) - res = ast_register_application2(parkcall, park_call_exec, NULL, NULL, NULL); if (!res) { - ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status); - ast_manager_register_xml_core("Parkinglots", 0, manager_parkinglot_list); ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park); ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge); } @@ -9182,4 +8213,3 @@ int ast_features_init(void) return res; } - diff --git a/main/frame.c b/main/frame.c index b559d552d2255a7a863908f514a4e3ad1acd9a1a..8822261f6f7b720f4f2a1f08f654a9fa9ba66acf 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 c9b2fbe1e1a74c9a2190f591d7e741586a5e9bf8..c28e6169b24d3a9e49367d1ad4812af98b67eae7 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 0000000000000000000000000000000000000000..01ce68aaf55b850db29cef145649edca4057abc6 --- /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 0cab365628086268b10410a20c68ac192cdd9052..fb579dd9505cca0f2200ca41683562fc1e4de681 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 0000000000000000000000000000000000000000..2a5b72e61fa372103791ddfbdf6941b14124cb50 --- /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 6359c056c2ca924b344e59a14f3693d280e503b0..8408048f2de48cf907e12fc1c96dc37d23c39b76 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 b25cb95b36d58969301497cee38be25e1eb92f80..8e58f658d1f19c0d8f4355cbd610301d2ba5865b 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 0000000000000000000000000000000000000000..2ee4fcfc169fff5748c24538d89642072708f91f --- /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 47715e63ea0f9b05abbd7935fbd33e64cb0aeeb8..d004da2278f2504284064f3f8ebba85d591f17b8 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 2ed719093fb9b65e33eda543fdbbfb2858d1967e..667e097e8c524a30993a5a720d9cb3b9ab754f7b 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 0000000000000000000000000000000000000000..097329b936012ca91799de96b940971faa828b32 --- /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 0000000000000000000000000000000000000000..b9ceb5203d9577d4c746e5b1a44e40e2c19d0d8c --- /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 0000000000000000000000000000000000000000..ddb5e7ff2b97d9c4ebfe784685d325a3b99cb38d --- /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 0000000000000000000000000000000000000000..03d7b8861a198147239cf8ec339372c8b8e2d60f --- /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 0000000000000000000000000000000000000000..d6a05573fb159f2ca2babc47be9e78d0c498b0a5 --- /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 0000000000000000000000000000000000000000..5b432b6898276d7189a2cf94503633dc5d8009f4 --- /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 0000000000000000000000000000000000000000..7f66d6e8f04bd1cf77d9d2e20b203cb42b07199c --- /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 0000000000000000000000000000000000000000..72627f16461ffa06cb05f3eaa6b1f5cea297c0fc --- /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 f843310b62eb38b469b6f9ee0a276d8e3644b94d..10d36be424c49ba4a47fb902aee9b87135d69b71 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 5a260a5f3a24c717c6b0b2aec75c85c214dc1736..e3f59ca763f27111fe6cac1ceca0aa83d7cfe7a2 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 bd1c3263b369b465cdea7f3f3d7e16793b4af727..63abe0f85d8d61a22d982458b27f2cb3c181f475 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 ); /*! @@ -67,6 +83,18 @@ struct ast_json *stasis_json_event_channel_destroyed_create( struct ast_json *blob ); +/*! + * \brief Some part of channel state changed. + * + * \param channel The channel to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_snapshot_create( + struct ast_channel_snapshot *channel_snapshot + ); + /*! * \brief Channel changed Caller ID. * @@ -99,6 +127,18 @@ struct ast_json *stasis_json_event_channel_hangup_request_create( struct ast_json *blob ); +/*! + * \brief Notification that a bridge has been destroyed. + * + * \param bridge The bridge to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_bridge_destroyed_create( + struct ast_bridge_snapshot *bridge_snapshot + ); + /*! * \brief Notification that another WebSocket has taken over for an application. * @@ -129,18 +169,17 @@ struct ast_json *stasis_json_event_channel_varset_create( ); /*! - * \brief User-generated event with additional user-defined fields in the object. + * \brief Notification that a channel has left a bridge. * - * \param channel The channel that signaled the user event. - * \param blob JSON blob containing the following parameters: - * - eventname: string - The name of the user event. (required) + * \param channel The channel to be used to generate this event + * \param bridge The bridge to be used to generate this event * * \retval NULL on error * \retval JSON (ast_json) describing the event */ -struct ast_json *stasis_json_event_channel_userevent_create( - struct ast_channel_snapshot *channel_snapshot, - struct ast_json *blob +struct ast_json *stasis_json_event_channel_left_bridge_create( + struct ast_bridge_snapshot *bridge_snapshot, + struct ast_channel_snapshot *channel_snapshot ); /*! @@ -198,6 +237,20 @@ struct ast_json *stasis_json_event_channel_state_change_create( struct ast_channel_snapshot *channel_snapshot ); +/*! + * \brief Notification that a channel has entered a bridge. + * + * \param channel The channel to be used to generate this event + * \param bridge The bridge to be used to generate this event + * + * \retval NULL on error + * \retval JSON (ast_json) describing the event + */ +struct ast_json *stasis_json_event_channel_entered_bridge_create( + struct ast_bridge_snapshot *bridge_snapshot, + struct ast_channel_snapshot *channel_snapshot + ); + /*! * \brief DTMF received on a channel. * @@ -228,23 +281,26 @@ struct ast_json *stasis_json_event_stasis_end_create( /* * JSON models * - * ChannelSnapshot + * ChannelUserevent + * - eventname: string (required) + * BridgeCreated * ChannelDestroyed * - cause: integer (required) * - cause_txt: string (required) + * ChannelSnapshot * ChannelCallerId * - caller_presentation_txt: string (required) * - caller_presentation: integer (required) * ChannelHangupRequest * - soft: boolean * - cause: integer + * BridgeDestroyed * ApplicationReplaced * - application: string (required) * ChannelVarset * - variable: string (required) * - value: string (required) - * ChannelUserevent - * - eventname: string (required) + * ChannelLeftBridge * ChannelCreated * StasisStart * - args: List[string] (required) @@ -252,22 +308,27 @@ struct ast_json *stasis_json_event_stasis_end_create( * - application: string (required) * - application_data: string (required) * ChannelStateChange + * ChannelEnteredBridge * ChannelDtmfReceived * - digit: string (required) * Event + * - stasis_start: StasisStart * - channel_created: ChannelCreated * - channel_destroyed: ChannelDestroyed + * - channel_entered_bridge: ChannelEnteredBridge + * - channel_left_bridge: ChannelLeftBridge * - channel_dialplan: ChannelDialplan * - channel_varset: ChannelVarset * - application_replaced: ApplicationReplaced * - channel_state_change: ChannelStateChange - * - stasis_start: StasisStart + * - bridge_created: BridgeCreated * - application: string (required) * - channel_hangup_request: ChannelHangupRequest * - channel_userevent: ChannelUserevent * - channel_snapshot: ChannelSnapshot * - channel_dtmf_received: ChannelDtmfReceived * - channel_caller_id: ChannelCallerId + * - bridge_destroyed: BridgeDestroyed * - stasis_end: StasisEnd * StasisEnd */ diff --git a/rest-api-templates/res_stasis_json_resource.c.mustache b/rest-api-templates/res_stasis_json_resource.c.mustache index 0398302702371d400237e835f230fdf65be3fe3c..a55389c070cf7cb106ffe1de2ee3f49e4db54fbc 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 f55e830bda0c1a416ae037f3b6ce66b08a0b9eb8..8cfd2c1f7e185b0a635d39ecaa99bd08dd06aff8 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 5b6e0549dc58e7a6981aed00dfda910df8f611ec..4a36da3b8dcec03ec67af0199865c42222ea781f 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.",