Make DTMF attended transfer support feature-complete.

This greatly modifies the operation of DTMF attended transfers so that
the full range of options from features.conf applies.

In addition, a new option has been added that allows for a transferer
to switch between bridges during a transfer before completing the
transfer.

(closes issue ASTERISK-21543)
reported by Matt Jordan

Review: https://reviewboard.asterisk.org/r/2654



git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@395151 65c4cc65-6c06-0410-ace0-fbb531ad65f3
changes/78/78/1
Mark Michelson 12 years ago
parent e148c6e867
commit bf22391b8d

@ -54,421 +54,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/mixmonitor.h"
#include "asterisk/audiohook.h"
/*!
* \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;
int digit_timeout;
RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
ast_channel_lock(chan);
xfer_cfg = ast_get_chan_features_xfer_config(chan);
if (!xfer_cfg) {
ast_log(LOG_ERROR, "Unable to get transfer configuration\n");
ast_channel_unlock(chan);
return -1;
}
digit_timeout = xfer_cfg->transferdigittimeout;
ast_channel_unlock(chan);
/* Play the simple "transfer" prompt out and wait */
res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
ast_stopstream(chan);
if (res < 0) {
/* Hangup or error */
return -1;
}
if (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, exten_len - 1, digit_timeout);
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;
}
static void copy_caller_data(struct ast_channel *dest, struct ast_channel *caller)
{
ast_channel_lock_both(caller, dest);
ast_connected_line_copy_from_caller(ast_channel_connected(dest), ast_channel_caller(caller));
ast_channel_inherit_variables(caller, dest);
ast_channel_datastore_inherit(caller, dest);
ast_channel_unlock(dest);
ast_channel_unlock(caller);
}
/*! \brief Helper function that creates an outgoing channel and returns it immediately */
static struct ast_channel *dial_transfer(struct ast_channel *caller, const char *exten, const char *context)
{
char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
struct ast_channel *chan;
int cause;
/* Fill the variable with the extension and context we want to call */
snprintf(destination, sizeof(destination), "%s@%s", exten, context);
/* 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;
}
/* Who is transferring the call. */
pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", ast_channel_name(caller));
/* To work as an analog to BLINDTRANSFER */
pbx_builtin_setvar_helper(chan, "ATTENDEDTRANSFER", ast_channel_name(caller));
/* Before we actually dial out let's inherit appropriate information. */
copy_caller_data(chan, caller);
/* Since the above worked fine now we actually call it and return the channel */
if (ast_call(chan, destination, 0)) {
ast_hangup(chan);
return NULL;
}
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";
}
static void blind_transfer_cb(struct ast_channel *new_channel, void *user_data,
enum ast_transfer_type transfer_type)
{
struct ast_channel *transferer_channel = user_data;
if (transfer_type == AST_BRIDGE_TRANSFER_MULTI_PARTY) {
copy_caller_data(new_channel, transferer_channel);
}
}
/*! \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_bridge_features_blind_transfer *blind_transfer = hook_pvt;
const char *context;
char *goto_on_blindxfr;
ast_bridge_channel_write_hold(bridge_channel, NULL);
ast_channel_lock(bridge_channel->chan);
context = ast_strdupa(get_transfer_context(bridge_channel->chan,
blind_transfer ? blind_transfer->context : NULL));
goto_on_blindxfr = ast_strdupa(S_OR(pbx_builtin_getvar_helper(bridge_channel->chan,
"GOTO_ON_BLINDXFR"), ""));
ast_channel_unlock(bridge_channel->chan);
/* Grab the extension to transfer to */
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
if (!ast_strlen_zero(goto_on_blindxfr)) {
ast_debug(1, "After transfer, transferer %s goes to %s\n",
ast_channel_name(bridge_channel->chan), goto_on_blindxfr);
ast_after_bridge_set_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr);
}
if (ast_bridge_transfer_blind(0, bridge_channel->chan, exten, context, blind_transfer_cb,
bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS &&
!ast_strlen_zero(goto_on_blindxfr)) {
ast_after_bridge_goto_discard(bridge_channel->chan);
}
return 0;
}
/*! 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)
{
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 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)
{
enum atxfer_code *transfer_code = hook_pvt;
*transfer_code = ATXFER_THREEWAY;
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
return 0;
}
/*! \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;
}
/*! \brief Internal built in feature for attended transfers */
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 *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 *complete_sound;
const char *context;
enum atxfer_code transfer_code = ATXFER_INCOMPLETE;
const char *atxfer_abort;
const char *atxfer_threeway;
const char *atxfer_complete;
const char *fail_sound;
RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
ast_bridge_channel_write_hold(bridge_channel, NULL);
bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
ast_channel_lock(bridge_channel->chan);
context = ast_strdupa(get_transfer_context(bridge_channel->chan,
attended_transfer ? attended_transfer->context : NULL));
xfer_cfg = ast_get_chan_features_xfer_config(bridge_channel->chan);
if (!xfer_cfg) {
ast_log(LOG_ERROR, "Unable to get transfer configuration options\n");
ast_channel_unlock(bridge_channel->chan);
return 0;
}
if (attended_transfer) {
atxfer_abort = ast_strdupa(S_OR(attended_transfer->abort, xfer_cfg->atxferabort));
atxfer_threeway = ast_strdupa(S_OR(attended_transfer->threeway, xfer_cfg->atxferthreeway));
atxfer_complete = ast_strdupa(S_OR(attended_transfer->complete, xfer_cfg->atxfercomplete));
} else {
atxfer_abort = ast_strdupa(xfer_cfg->atxferabort);
atxfer_threeway = ast_strdupa(xfer_cfg->atxferthreeway);
atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete);
}
fail_sound = ast_strdupa(xfer_cfg->xferfailsound);
ast_channel_unlock(bridge_channel->chan);
/* Grab the extension to transfer to */
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
/* Get a channel that is the destination we wish to call */
peer = dial_transfer(bridge_channel->chan, exten, context);
if (!peer) {
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
/* 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, atxfer_abort,
attended_transfer_abort, &transfer_code, NULL, 0)
|| ast_bridge_dtmf_hook(&caller_features, atxfer_complete,
attended_transfer_complete, &transfer_code, NULL, 0)
|| ast_bridge_dtmf_hook(&caller_features, atxfer_threeway,
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);
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
/* 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);
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
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 */
/* 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);
ast_stream_and_wait(bridge_channel->chan, fail_sound, AST_DIGIT_NONE);
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
/*
* 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);
/*
* 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);
}
/* 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);
/* Is there a courtesy sound to play to the peer? */
ast_channel_lock(bridge_channel->chan);
complete_sound = pbx_builtin_getvar_helper(bridge_channel->chan,
"ATTENDED_TRANSFER_COMPLETE_SOUND");
if (!ast_strlen_zero(complete_sound)) {
complete_sound = ast_strdupa(complete_sound);
} else {
complete_sound = NULL;
}
ast_channel_unlock(bridge_channel->chan);
if (complete_sound) {
pbx_builtin_setvar_helper(peer, "BRIDGE_PLAY_SOUND", complete_sound);
}
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_channel_write_unhold(bridge_channel);
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.
*/
ast_bridge_channel_write_unhold(bridge_channel);
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, fail_sound, AST_DIGIT_NONE);
}
ast_bridge_channel_write_unhold(bridge_channel);
}
return 0;
}
enum set_touch_variables_res {
SET_TOUCH_SUCCESS,
SET_TOUCH_UNSET,
@ -909,8 +494,6 @@ static int unload_module(void)
static int load_module(void)
{
ast_bridge_features_register(AST_BRIDGE_BUILTIN_BLINDTRANSFER, feature_blind_transfer, NULL);
ast_bridge_features_register(AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, feature_attended_transfer, NULL);
ast_bridge_features_register(AST_BRIDGE_BUILTIN_HANGUP, feature_hangup, NULL);
ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMON, feature_automonitor, NULL);
ast_bridge_features_register(AST_BRIDGE_BUILTIN_AUTOMIXMON, feature_automixmonitor, NULL);

@ -396,6 +396,8 @@ struct ast_bridge_methods {
struct ast_bridge {
/*! Bridge virtual method table. */
const struct ast_bridge_methods *v_table;
/*! "Personality" currently exhibited by bridge subclass */
void *personality;
/*! Immutable bridge UUID. */
char uniqueid[AST_UUID_STR_LEN];
/*! Bridge technology that is handling the bridge */
@ -1785,6 +1787,16 @@ struct ast_channel *ast_bridge_peer_nolock(struct ast_bridge *bridge, struct ast
*/
struct ast_channel *ast_bridge_peer(struct ast_bridge *bridge, struct ast_channel *chan);
/*!
* \brief Remove marked bridge channel feature hooks.
* \since 12.0.0
*
* \param features Bridge features structure
* \param flags Determinator for whether hook is removed.
*
* \return Nothing
*/
void ast_bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags flags);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

@ -183,6 +183,8 @@ struct ast_bridge_hook_timer {
enum ast_bridge_hook_remove_flags {
/*! The hook is removed when the channel is pulled from the bridge. */
AST_BRIDGE_HOOK_REMOVE_ON_PULL = (1 << 0),
/*! The hook is removed when the bridge's personality changes. */
AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE = (1 << 1),
};
/* BUGBUG Need to be able to selectively remove DTMF, hangup, and interval hooks. */
@ -265,6 +267,8 @@ struct ast_bridge_features_attended_transfer {
char threeway[MAXIMUM_DTMF_FEATURE_STRING];
/*! DTMF string used to complete the transfer (If not empty.) */
char complete[MAXIMUM_DTMF_FEATURE_STRING];
/*! DTMF string used to swap bridged targets (If not empty.) */
char swap[MAXIMUM_DTMF_FEATURE_STRING];
};
enum ast_bridge_features_monitor {

@ -0,0 +1,88 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013 Digium, Inc.
*
* Mark Michelson <mmichelson@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 Private Bridging API
*
* \author Mark Michelson <mmichelson@digium.com>
*
* See Also:
* \arg \ref AstCREDITS
*/
#ifndef _ASTERISK_PRIVATE_BRIDGING_H
#define _ASTERISK_PRIVATE_BRIDGING_H
struct ast_bridge;
struct ast_bridge_channel;
/*!
* \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.
* \param optimized Indicates whether the move is part of an unreal channel optimization.
*
* \note The dst_bridge and bridge_channel->bridge are assumed already locked.
*
* \retval 0 on success.
* \retval -1 on failure.
*/
int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel,
int attempt_recovery, unsigned int optimized);
/*!
* \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.
* \param optimized Indicates whether the merge is part of an unreal channel optimization.
*
* \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.
*/
void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge,
struct ast_bridge_channel **kick_me, unsigned int num_kick, unsigned int optimized);
/*!
* \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.
*/
struct ast_bridge_channel *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan);
#endif /* _ASTERISK_PRIVATE_BRIDGING_H */

@ -63,6 +63,37 @@ void ast_channel_remove_bridge_role(struct ast_channel *chan, const char *role_n
*/
int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *role_name, const char *option, const char *value);
/*!
* \brief Check if a role exists on a channel
*
* \param channel The channel to check
* \param role_name The name of the role to search for
*
* \retval 0 The requested role does not exist on the channel
* \retval 1 The requested role exists on the channel
*
* This is an alternative to \ref ast_bridge_channel_has_role that is useful if bridge
* roles have not yet been established on a channel's bridge_channel. A possible example of
* when this could be used is in a bridge v_table's push() callback.
*/
int ast_channel_has_role(struct ast_channel *channel, const char *role_name);
/*!
* \brief Retrieve the value of a requested role option from a channel
*
* \param channel The channel to retrieve the requested option from
* \param role_name The role to which the option belongs
* \param option The name of the option to retrieve
*
* \retval NULL The option does not exist
* \retval non-NULL The value of the option
*
* This is an alternative to \ref ast_bridge_channel_get_role_option that is useful if bridge
* roles have not yet been esstablished on a channel's bridge_channel. A possible example of
* when this could be used is in a bridge v_table's push() callback.
*/
const char *ast_channel_get_role_option(struct ast_channel *channel, const char *role_name, const char *option);
/*!
* \brief Check to see if a bridge channel inherited a specific role from its channel
*
@ -72,7 +103,7 @@ int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *
* \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
* \note Before a bridge_channel can effectively check roles against a bridge, ast_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.
*/
@ -88,10 +119,10 @@ int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const
* \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 See ast_channel_set_role_option note about the need to call ast_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
* ast_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.
*/
@ -119,12 +150,12 @@ int ast_bridge_channel_establish_roles(struct ast_bridge_channel *bridge_channel
* \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
* If roles are already established on a bridge channel, ast_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.
* ast_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);

@ -66,6 +66,8 @@ struct ast_features_xfer_config {
AST_STRING_FIELD(atxfercomplete);
/*! DTMF sequence used to turn an attempted atxfer into a three-way call */
AST_STRING_FIELD(atxferthreeway);
/*! DTMF sequence used to swap which party the transferer is talking to */
AST_STRING_FIELD(atxferswap);
);
/*! Milliseconds allowed between digit presses when dialing transfer destination */
unsigned int transferdigittimeout;

@ -256,6 +256,8 @@ enum ast_attended_transfer_dest_type {
AST_ATTENDED_TRANSFER_DEST_APP,
/*! The transfer results in both bridges remaining with a local channel linking them */
AST_ATTENDED_TRANSFER_DEST_LINK,
/*! The transfer results in a threeway call between transferer, transferee, and transfer target */
AST_ATTENDED_TRANSFER_DEST_THREEWAY,
};
/*!
@ -279,6 +281,8 @@ struct ast_attended_transfer_message {
char app[AST_MAX_APP];
/*! Pair of local channels linking the bridges. Applicable for AST_ATTENDED_TRANSFER_DEST_LINK */
struct ast_channel_snapshot *links[2];
/*! Transferer channel and bridge that survived the transition to a threeway call. Applicable for AST_ATTENDED_TRANSFER_DEST_THREEWAY */
struct ast_bridge_channel_snapshot_pair threeway;
} dest;
};
@ -328,6 +332,25 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
struct ast_bridge *final_bridge);
/*!
* \since 12
* \brief Publish an attended transfer that results in a threeway call.
*
* Publish an \ref ast_attended_transfer_message with the dest_type set to
* \c AST_ATTENDED_TRANSFER_DEST_THREEWAY. Like with \ref ast_bridge_publish_attended_transfer_bridge_merge,
* this results from merging two bridges together. The difference is that a
* transferer channel survives the bridge merge
*
* \param is_external Indicates if the transfer was initiated externally
* \param result The result of the transfer.
* \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge
* \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge
* \param final_pair The bridge that the parties end up in, and the transferer channel that is in this bridge.
*/
void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_transfer_result result,
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
struct ast_bridge_channel_pair *final_pair);
/*!
* \since 12
* \brief Publish an attended transfer that results in an application being run

@ -63,6 +63,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/core_local.h"
#include "asterisk/core_unreal.h"
#include "asterisk/features_config.h"
#include "asterisk/bridging_internal.h"
/*! All bridges container. */
static struct ao2_container *bridges;
@ -77,7 +78,6 @@ static AST_RWLIST_HEAD_STATIC(bridge_technologies, ast_bridge_technology);
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(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags remove_flags);
/*! Default DTMF keys for built in features */
static char builtin_features_dtmf[AST_BRIDGE_BUILTIN_END][MAXIMUM_DTMF_FEATURE_STRING];
@ -463,19 +463,7 @@ void ast_bridge_channel_restore_formats(struct ast_bridge_channel *bridge_channe
}
}
/*!
* \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 *find_bridge_channel(struct ast_bridge *bridge, struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
@ -757,7 +745,7 @@ static int bridge_channel_push(struct ast_bridge_channel *bridge_channel)
|| ast_bridge_channel_establish_roles(bridge_channel)) {
ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n",
bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
return -1;
}
bridge_channel->in_bridge = 1;
@ -1719,7 +1707,7 @@ static int bridge_base_push(struct ast_bridge *self, struct ast_bridge_channel *
*/
static void bridge_base_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
{
bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
}
/*!
@ -4164,24 +4152,7 @@ static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_chann
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,
void bridge_merge_do(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick,
unsigned int optimized)
{
struct ast_bridge_channel *bridge_channel;
@ -4440,21 +4411,7 @@ int ast_bridge_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridg
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,
int bridge_move_do(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bridge_channel, int attempt_recovery,
unsigned int optimized)
{
struct ast_bridge *orig_bridge;
@ -5646,17 +5603,7 @@ static void hooks_remove_heap(struct ast_heap *hooks, enum ast_bridge_hook_remov
ast_heap_unlock(hooks);
}
/*!
* \internal
* \brief Remove marked bridge channel feature hooks.
* \since 12.0.0
*
* \param features Bridge features structure
* \param remove_flags Determinator for whether hook is removed.
*
* \return Nothing
*/
static void bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags remove_flags)
void ast_bridge_features_remove(struct ast_bridge_features *features, enum ast_bridge_hook_remove_flags remove_flags)
{
hooks_remove_container(features->dtmf_hooks, remove_flags);
hooks_remove_container(features->hangup_hooks, remove_flags);

File diff suppressed because it is too large Load Diff

@ -374,6 +374,26 @@ int ast_channel_set_bridge_role_option(struct ast_channel *channel, const char *
return setup_bridge_role_option(role, option, value);
}
int ast_channel_has_role(struct ast_channel *channel, const char *role_name)
{
return get_role_from_channel(channel, role_name) ? 1 : 0;
}
const char *ast_channel_get_role_option(struct ast_channel *channel, const char *role_name, const char *option)
{
struct bridge_role *role;
struct bridge_role_option *role_option;
role = get_role_from_channel(channel, role_name);
if (!role) {
return NULL;
}
role_option = get_role_option(role, option);
return role_option ? role_option->value : NULL;
}
int ast_bridge_channel_has_role(struct ast_bridge_channel *bridge_channel, const char *role_name)
{
if (!bridge_channel->bridge_roles) {

@ -1433,9 +1433,10 @@ static void cel_attended_transfer_cb(
switch (xfer->dest_type) {
case AST_ATTENDED_TRANSFER_DEST_FAIL:
return;
/* handle these two the same */
/* handle these three the same */
case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE:
case AST_ATTENDED_TRANSFER_DEST_LINK:
case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
extra = ast_json_pack("{s: s, s: s, s: s}",
"bridge1_id", bridge1->uniqueid,
"channel2_name", channel2->name,

@ -3212,37 +3212,37 @@ static int setup_bridge_features_builtin(struct ast_bridge_features *features, s
if (!builtin_feature_get_exten(chan, "blindxfer", dtmf, sizeof(dtmf))
&& !ast_strlen_zero(dtmf)) {
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_BLINDTRANSFER, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
}
if (!builtin_feature_get_exten(chan, "atxfer", dtmf, sizeof(dtmf)) &&
!ast_strlen_zero(dtmf)) {
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
}
}
if (ast_test_flag(flags, AST_FEATURE_DISCONNECT) &&
!builtin_feature_get_exten(chan, "disconnect", dtmf, sizeof(dtmf)) &&
!ast_strlen_zero(dtmf)) {
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_HANGUP, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
}
if (ast_test_flag(flags, AST_FEATURE_PARKCALL) &&
!builtin_feature_get_exten(chan, "parkcall", dtmf, sizeof(dtmf)) &&
!ast_strlen_zero(dtmf)) {
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_PARKCALL, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
}
if (ast_test_flag(flags, AST_FEATURE_AUTOMON) &&
!builtin_feature_get_exten(chan, "automon", dtmf, sizeof(dtmf)) &&
!ast_strlen_zero(dtmf)) {
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMON, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
}
if (ast_test_flag(flags, AST_FEATURE_AUTOMIXMON) &&
!builtin_feature_get_exten(chan, "automixmon", dtmf, sizeof(dtmf)) &&
!ast_strlen_zero(dtmf)) {
res |= ast_bridge_features_enable(features, AST_BRIDGE_BUILTIN_AUTOMIXMON, dtmf,
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
NULL, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL | AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE);
}
return res ? -1 : 0;

@ -106,6 +106,16 @@
the transferees, and the transfer target all being in a single bridge together.</para>
</description>
</configOption>
<configOption name="atxferswap" default="*4">
<synopsis>Digits to dial to toggle who the transferrer is currently bridged to during an attended transfer</synopsis>
<description>
<para>This option is only available to the transferrer during an attended
transfer operation. Pressing this DTMF sequence will result in the transferrer swapping
which party he is bridged with. For instance, if the transferrer is currently bridged with
the transfer target, then pressing this DTMF sequence will cause the transferrer to be
bridged with the transferees.</para>
</description>
</configOption>
<configOption name="pickupexten" default="*8">
<synopsis>Digits used for picking up ringing calls</synopsis>
<description>
@ -355,6 +365,7 @@
#define DEFAULT_ATXFER_ABORT "*1"
#define DEFAULT_ATXFER_COMPLETE "*2"
#define DEFAULT_ATXFER_THREEWAY "*3"
#define DEFAULT_ATXFER_SWAP "*4"
/*! Default pickup options */
#define DEFAULT_PICKUPEXTEN "*8"
@ -846,6 +857,8 @@ static int xfer_set(struct ast_features_xfer_config *xfer, const char *name,
ast_string_field_set(xfer, atxfercomplete, value);
} else if (!strcasecmp(name, "atxferthreeway")) {
ast_string_field_set(xfer, atxferthreeway, value);
} else if (!strcasecmp(name, "atxferswap")) {
ast_string_field_set(xfer, atxferswap, value);
} else {
/* Unrecognized option */
res = -1;
@ -879,6 +892,8 @@ static int xfer_get(struct ast_features_xfer_config *xfer, const char *field,
ast_copy_string(buf, xfer->atxfercomplete, len);
} else if (!strcasecmp(field, "atxferthreeway")) {
ast_copy_string(buf, xfer->atxferthreeway, len);
} else if (!strcasecmp(field, "atxferswap")) {
ast_copy_string(buf, xfer->atxferswap, len);
} else {
/* Unrecognized option */
res = -1;
@ -1629,6 +1644,8 @@ static int load_config(void)
DEFAULT_ATXFER_COMPLETE, xfer_handler, 0);
aco_option_register_custom(&cfg_info, "atxferthreeway", ACO_EXACT, global_options,
DEFAULT_ATXFER_THREEWAY, xfer_handler, 0);
aco_option_register_custom(&cfg_info, "atxferswap", ACO_EXACT, global_options,
DEFAULT_ATXFER_SWAP, xfer_handler, 0);
aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options,
DEFAULT_PICKUPEXTEN, pickup_handler, 0);

@ -221,12 +221,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<enum name="Bridge"><para>The transfer was accomplished by merging two bridges into one.</para></enum>
<enum name="App"><para>The transfer was accomplished by having a channel or bridge run a dialplan application.</para></enum>
<enum name="Link"><para>The transfer was accomplished by linking two bridges together using a local channel pair.</para></enum>
<enum name="Threeway"><para>The transfer was accomplished by placing all parties into a threeway call.</para></enum>
<enum name="Fail"><para>The transfer failed.</para></enum>
</enumlist>
</parameter>
<parameter name="DestBridgeUniqueid">
<para>Indicates the surviving bridge when bridges were merged to complete the transfer</para>
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Bridge</literal></para></note>
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Bridge</literal> or <literal>Threeway</literal></para></note>
</parameter>
<parameter name="DestApp">
<para>Indicates the application that is running when the transfer completes</para>
@ -333,6 +334,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<parameter name="LocalTwoUniqueid">
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
</parameter>
<parameter name="DestTransfererChannel">
<para>The name of the surviving transferer channel when a transfer results in a threeway call</para>
<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Threeway</literal></para></note>
</parameter>
</syntax>
<description>
<para>The headers in this event attempt to describe all the major details of the attended transfer. The two transferer channels
@ -835,6 +840,11 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes
ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local1_state));
ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local2_state));
break;
case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
ast_str_append(&variable_data, 0, "DestType: Threeway\r\n");
ast_str_append(&variable_data, 0, "DestBridgeUniqueid: %s\r\n", transfer_msg->dest.threeway.bridge_snapshot->uniqueid);
ast_str_append(&variable_data, 0, "DestTransfererChannel: %s\r\n", transfer_msg->dest.threeway.channel_snapshot->name);
break;
case AST_ATTENDED_TRANSFER_DEST_FAIL:
ast_str_append(&variable_data, 0, "DestType: Fail\r\n");
break;
@ -941,6 +951,39 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast
stasis_publish(ast_bridge_topic_all(), msg);
}
void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_transfer_result result,
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
struct ast_bridge_channel_pair *final_pair)
{
RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
transfer_msg = attended_transfer_message_create(is_external, result, transferee, target);
if (!transfer_msg) {
return;
}
transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_THREEWAY;
if (final_pair->channel == transferee->channel) {
transfer_msg->dest.threeway.channel_snapshot = transfer_msg->to_transferee.channel_snapshot;
} else {
transfer_msg->dest.threeway.channel_snapshot = transfer_msg->to_transfer_target.channel_snapshot;
}
if (final_pair->bridge == transferee->bridge) {
transfer_msg->dest.threeway.bridge_snapshot = transfer_msg->to_transferee.bridge_snapshot;
} else {
transfer_msg->dest.threeway.bridge_snapshot = transfer_msg->to_transfer_target.bridge_snapshot;
}
msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg);
if (!msg) {
return;
}
stasis_publish(ast_bridge_topic_all(), msg);
}
void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer_result result,
struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
const char *dest_app)

Loading…
Cancel
Save