diff options
author | Mark Michelson <mmichelson@digium.com> | 2013-05-28 14:45:31 +0000 |
---|---|---|
committer | Mark Michelson <mmichelson@digium.com> | 2013-05-28 14:45:31 +0000 |
commit | fac3839e6837241c10bee6f2563a27f1d367ddc6 (patch) | |
tree | 76e37ff2fddc7d30ee75f8d6d498aabca58f21e8 /main/bridging.c | |
parent | 2d2a47fae380a78ef9a2f14cb8dcb84d58ef58c5 (diff) |
Adds support for a core attended transfer function plus adds some hiding of masquerades.
The attended transfer API call can complete the attended transfer in a number of ways
depending on the current bridged states of the channels involved.
The hiding of masquerades is done in some bridging-related functions, such as the manager
Bridge action and the Bridge dialplan application. In addition, call pickup was edited
to "move" a channel rather than masquerade it.
Review: https://reviewboard.asterisk.org/r/2511
(closes issue ASTERISK-21334)
Reported by Matt Jordan
(closes issue Asterisk-21336)
Reported by Matt Jordan
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389848 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main/bridging.c')
-rw-r--r-- | main/bridging.c | 627 |
1 files changed, 530 insertions, 97 deletions
diff --git a/main/bridging.c b/main/bridging.c index f332dfab2..35cb85974 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/features.h" #include "asterisk/cli.h" #include "asterisk/parking.h" +#include "asterisk/core_local.h" /*! All bridges container. */ static struct ao2_container *bridges; @@ -2003,6 +2004,48 @@ static void bridge_channel_blind_transfer(struct ast_bridge_channel *bridge_chan bridge_handle_hangup(bridge_channel); } +static void after_bridge_move_channel(struct ast_channel *chan_bridged, void *data) +{ + RAII_VAR(struct ast_channel *, chan_target, data, ao2_cleanup); + ast_channel_move(chan_target, chan_bridged); +} + +static void after_bridge_move_channel_fail(enum ast_after_bridge_cb_reason reason, void *data) +{ + RAII_VAR(struct ast_channel *, chan_target, data, ao2_cleanup); + + ast_log(LOG_WARNING, "Unable to complete transfer: %s\n", + ast_after_bridge_cb_reason_string(reason)); +} + +static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_channel, + const char *target_chan_name) +{ + RAII_VAR(struct ast_channel *, chan_target, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, chan_bridged, NULL, ao2_cleanup); + + chan_target = ast_channel_get_by_name(target_chan_name); + if (!chan_target) { + /* Dang, it disappeared somehow */ + return; + } + + { + SCOPED_CHANNELLOCK(lock, bridge_channel); + chan_bridged = bridge_channel->chan; + if (!chan_bridged) { + return; + } + ao2_ref(chan_bridged, +1); + } + + if (ast_after_bridge_callback_set(chan_bridged, after_bridge_move_channel, + after_bridge_move_channel_fail, ast_channel_ref(chan_target))) { + return; + } + bridge_handle_hangup(bridge_channel); +} + /*! * \internal * \brief Handle bridge channel bridge action frame. @@ -2066,6 +2109,9 @@ static void bridge_channel_handle_action(struct ast_bridge_channel *bridge_chann case AST_BRIDGE_ACTION_BLIND_TRANSFER: bridge_channel_blind_transfer(bridge_channel, action->data.ptr); break; + case AST_BRIDGE_ACTION_ATTENDED_TRANSFER: + bridge_channel_attended_transfer(bridge_channel, action->data.ptr); + break; default: break; } @@ -2714,6 +2760,23 @@ int ast_after_bridge_callback_set(struct ast_channel *chan, ast_after_bridge_cb return 0; } +const char *reason_strings[] = { + [AST_AFTER_BRIDGE_CB_REASON_DESTROY] = "Bridge Destroyed", + [AST_AFTER_BRIDGE_CB_REASON_REPLACED] = "Channel replaced", + [AST_AFTER_BRIDGE_CB_REASON_MASQUERADE] = "Channel masqueraded", + [AST_AFTER_BRIDGE_CB_REASON_DEPART] = "Channel departed", + [AST_AFTER_BRIDGE_CB_REASON_REMOVED] = "Channel removed", +}; + +const char *ast_after_bridge_cb_reason_string(enum ast_after_bridge_cb_reason reason) +{ + if (reason < AST_AFTER_BRIDGE_CB_REASON_DESTROY || reason > AST_AFTER_BRIDGE_CB_REASON_REMOVED) { + return "Unknown"; + } + + return reason_strings[reason]; +} + struct after_bridge_goto_ds { /*! Goto string that can be parsed by ast_parseable_goto(). */ const char *parseable_goto; @@ -3742,6 +3805,19 @@ struct ast_bridge_channel *ast_bridge_channel_peer(struct ast_bridge_channel *br return other; } +static int bridge_allows_optimization(struct ast_bridge *bridge) +{ + return !(bridge->inhibit_merge + || bridge->dissolved + || ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)); +} + +static int bridge_channel_allows_optimization(struct ast_bridge_channel *bridge_channel) +{ + return bridge_channel->in_bridge + && AST_LIST_EMPTY(&bridge_channel->wr_queue); +} + /*! * \internal * \brief Lock the unreal channel stack for chan and prequalify it. @@ -3773,11 +3849,8 @@ static struct ast_bridge *optimize_lock_chan_stack(struct ast_channel *chan) 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)) { + if (!bridge_channel_allows_optimization(bridge_channel) || + !bridge_allows_optimization(bridge)) { ast_bridge_unlock(bridge); ast_bridge_channel_unlock(bridge_channel); return NULL; @@ -3820,11 +3893,8 @@ static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer) 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)) { + if (!bridge_allows_optimization(bridge) || + !bridge_channel_allows_optimization(bridge_channel)) { ast_bridge_unlock(bridge); ast_bridge_channel_unlock(bridge_channel); ast_channel_unlock(peer); @@ -3835,33 +3905,37 @@ static struct ast_bridge *optimize_lock_peer_stack(struct ast_channel *peer) /*! * \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 + * \brief Indicates allowability of a swap optimization + */ +enum bridge_allow_swap { + /*! Bridges cannot allow for a swap optimization to occur */ + SWAP_PROHIBITED, + /*! Bridge swap optimization can occur into the chan_bridge */ + SWAP_TO_CHAN_BRIDGE, + /*! Bridge swap optimization can occur into the peer_bridge */ + SWAP_TO_PEER_BRIDGE, +}; + +/*! + * \internal + * \brief Determine if two bridges allow for swap optimization to occur * - * \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. + * \param chan_bridge First bridge being tested + * \param peer_bridge Second bridge being tested + * \return Allowability of swap optimization */ -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) +static enum bridge_allow_swap bridges_allow_swap_optimization(struct ast_bridge *chan_bridge, + struct ast_bridge *peer_bridge) { - 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; + int peer_priority; if (!ast_test_flag(&chan_bridge->feature_flags, - AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM) + AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | + AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) && !ast_test_flag(&peer_bridge->feature_flags, - AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM)) { + AST_BRIDGE_FLAG_SWAP_INHIBIT_TO | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | + AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY)) { /* * Can swap either way. Swap to the higher priority merge * bridge. @@ -3870,45 +3944,76 @@ static int check_swap_optimize_out(struct ast_bridge *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; + return SWAP_TO_PEER_BRIDGE; } 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; + return SWAP_TO_CHAN_BRIDGE; } } else if (chan_bridge->num_channels == 2 - && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM) + && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) && !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; + return SWAP_TO_PEER_BRIDGE; } else if (peer_bridge->num_channels == 2 - && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM) + && !ast_test_flag(&peer_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) && !ast_test_flag(&chan_bridge->feature_flags, AST_BRIDGE_FLAG_SWAP_INHIBIT_TO)) { /* Can swap optimize only one way. */ + return SWAP_TO_CHAN_BRIDGE; + } + + return SWAP_PROHIBITED; +} + +/*! + * \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; + struct ast_bridge_channel *dst_bridge_channel; + struct ast_bridge_channel *src_bridge_channel; + struct ast_bridge_channel *other; + int res = 1; + + switch (bridges_allow_swap_optimization(chan_bridge, peer_bridge)) { + case SWAP_TO_CHAN_BRIDGE: dst_bridge = chan_bridge; dst_bridge_channel = chan_bridge_channel; src_bridge_channel = peer_bridge_channel; + break; + case SWAP_TO_PEER_BRIDGE: + dst_bridge = peer_bridge; + dst_bridge_channel = peer_bridge_channel; + src_bridge_channel = chan_bridge_channel; + break; + case SWAP_PROHIBITED: + default: + return 0; } - 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 = 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; - } + 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; @@ -3916,6 +4021,53 @@ static int check_swap_optimize_out(struct ast_bridge *chan_bridge, /*! * \internal + * \brief Indicates allowability of a merge optimization + */ +enum bridge_allow_merge { + /*! Bridge properties prohibit merge optimization */ + MERGE_PROHIBITED, + /*! Merge optimization cannot occur because the source bridge has too few channels */ + MERGE_NOT_ENOUGH_CHANNELS, + /*! Merge optimization cannot occur because multimix capability could not be requested */ + MERGE_NO_MULTIMIX, + /*! Merge optimization allowed between bridges */ + MERGE_ALLOWED, +}; + +/*! + * \internal + * \brief Determines allowability of a merge optimization + * + * \note The merge output parameter is undefined if MERGE_PROHIBITED is returned. For success + * and other failure returns, a merge direction was determined, and the parameter is safe to + * access. + * + * \param chan_bridge First bridge being tested + * \param peer_bridge Second bridge being tested + * \param num_kick_channels The number of channels to remove from the bridges during merging + * \param[out] merge Indicates the recommended direction for the bridge merge + */ +static enum bridge_allow_merge bridges_allow_merge_optimization(struct ast_bridge *chan_bridge, + struct ast_bridge *peer_bridge, int num_kick_channels, struct merge_direction *merge) +{ + *merge = bridge_merge_determine_direction(chan_bridge, peer_bridge); + if (!merge->dest) { + return MERGE_PROHIBITED; + } + if (merge->src->num_channels < 2) { + return MERGE_NOT_ENOUGH_CHANNELS; + } else if ((2 + num_kick_channels) < 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))) { + return MERGE_NO_MULTIMIX; + } + + return MERGE_ALLOWED; +} + +/*! + * \internal * \brief Check and attempt to merge optimize out the unreal channels. * \since 12.0.0 * @@ -3932,39 +4084,36 @@ static int check_merge_optimize_out(struct ast_bridge *chan_bridge, struct ast_bridge_channel *peer_bridge_channel) { struct merge_direction merge; - int res = 0; + struct ast_bridge_channel *kick_me[] = { + chan_bridge_channel, + peer_bridge_channel, + }; - merge = bridge_merge_determine_direction(chan_bridge, peer_bridge); - if (!merge.dest) { - return res; - } - if (merge.src->num_channels < 2) { + switch (bridges_allow_merge_optimization(chan_bridge, peer_bridge, ARRAY_LEN(kick_me), &merge)) { + case MERGE_ALLOWED: + break; + case MERGE_PROHIBITED: + return 0; + case MERGE_NOT_ENOUGH_CHANNELS: 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))) { + return 0; + case MERGE_NO_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, - }; + return 0; + } - ast_debug(1, "Merge optimizing %s -- %s out.\n", - ast_channel_name(chan_bridge_channel->chan), - ast_channel_name(peer_bridge_channel->chan)); + 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; - } + bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me)); - return res; + return -1; } int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer) @@ -4007,6 +4156,37 @@ int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel return res; } +enum ast_bridge_optimization ast_bridges_allow_optimization(struct ast_bridge *chan_bridge, + struct ast_bridge *peer_bridge) +{ + struct merge_direction merge; + + if (!bridge_allows_optimization(chan_bridge) || !bridge_allows_optimization(peer_bridge)) { + return AST_BRIDGE_OPTIMIZE_PROHIBITED; + } + + switch (bridges_allow_swap_optimization(chan_bridge, peer_bridge)) { + case SWAP_TO_CHAN_BRIDGE: + return AST_BRIDGE_OPTIMIZE_SWAP_TO_CHAN_BRIDGE; + case SWAP_TO_PEER_BRIDGE: + return AST_BRIDGE_OPTIMIZE_SWAP_TO_PEER_BRIDGE; + case SWAP_PROHIBITED: + default: + break; + } + + /* Two channels will be kicked from the bridges, the unreal;1 and unreal;2 channels */ + if (bridges_allow_merge_optimization(chan_bridge, peer_bridge, 2, &merge) != MERGE_ALLOWED) { + return AST_BRIDGE_OPTIMIZE_PROHIBITED; + } + + if (merge.dest == chan_bridge) { + return AST_BRIDGE_OPTIMIZE_MERGE_TO_CHAN_BRIDGE; + } else { + return AST_BRIDGE_OPTIMIZE_MERGE_TO_PEER_BRIDGE; + } +} + /*! * \internal * \brief Adjust the bridge merge inhibit request count. @@ -4999,7 +5179,7 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf } if (new_channel_cb) { - new_channel_cb(local, user_data); + new_channel_cb(local, user_data, AST_BRIDGE_TRANSFER_MULTI_PARTY); } if (ast_call(local, chan_name, 0)) { @@ -5014,6 +5194,67 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf } /*! + * \brief Perform an attended transfer of a bridge + * + * This performs an attended transfer of an entire bridge to a target. + * The target varies, depending on what bridges exist during the transfer + * attempt. + * + * If two bridges exist, then a local channel is created to link the two + * bridges together. Local channel optimization may result in the bridges + * merging. + * + * If only one bridge exists, then a local channel is created with one end + * placed into the existing bridge and the other end masquerading into + * the unbridged channel. + * + * \param chan1 Transferer channel. Guaranteed to be bridged. + * \param chan2 Other transferer channel. May or may not be bridged. + * \param bridge1 Bridge that chan1 is in. Guaranteed to be non-NULL. + * \param bridge2 Bridge that chan2 is in. If NULL, then chan2 is not bridged. + * \retval AST_BRIDGE_TRANSFER_FAIL Internal error occurred + * \retval AST_BRIDGE_TRANSFER_SUCCESS Succesfully transferred the bridge + */ +static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *chan1, + struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2) +{ + static const char *dest = "_attended@transfer/m"; + struct ast_channel *local_chan; + int cause; + int res; + + local_chan = ast_request("Local", ast_channel_nativeformats(chan1), chan1, + dest, &cause); + + if (!local_chan) { + return AST_BRIDGE_TRANSFER_FAIL; + } + + if (bridge2) { + res = ast_local_setup_bridge(local_chan, bridge2, chan2, NULL); + } else { + res = ast_local_setup_masquerade(local_chan, chan2); + } + + if (res) { + ast_hangup(local_chan); + return AST_BRIDGE_TRANSFER_FAIL; + } + + if (ast_call(local_chan, dest, 0)) { + ast_hangup(local_chan); + return AST_BRIDGE_TRANSFER_FAIL; + } + + if (ast_bridge_impart(bridge1, local_chan, chan1, NULL, 1)) { + ast_hangup(local_chan); + return AST_BRIDGE_TRANSFER_FAIL; + } + + return AST_BRIDGE_TRANSFER_SUCCESS; +} + +/*! * \internal * \brief Get the transferee channel * @@ -5076,7 +5317,7 @@ static int bridge_channel_queue_blind_transfer(struct ast_channel *transferee, } if (new_channel_cb) { - new_channel_cb(transferee, user_data); + new_channel_cb(transferee, user_data, AST_BRIDGE_TRANSFER_SINGLE_PARTY); } ast_copy_string(blind_data.exten, exten, sizeof(blind_data.exten)); @@ -5089,6 +5330,30 @@ static int bridge_channel_queue_blind_transfer(struct ast_channel *transferee, return 0; } +static int bridge_channel_queue_attended_transfer(struct ast_channel *transferee, + struct ast_channel *unbridged_chan) +{ + RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup); + char unbridged_chan_name[AST_CHANNEL_NAME]; + + ast_channel_lock(transferee); + transferee_bridge_channel = ast_channel_get_bridge_channel(transferee); + ast_channel_unlock(transferee); + + if (!transferee_bridge_channel) { + return -1; + } + + ast_copy_string(unbridged_chan_name, ast_channel_name(unbridged_chan), + sizeof(unbridged_chan_name)); + + ast_bridge_channel_queue_action_data(transferee_bridge_channel, + AST_BRIDGE_ACTION_ATTENDED_TRANSFER, unbridged_chan_name, + sizeof(unbridged_chan_name)); + + return 0; +} + enum try_parking_result { PARKING_SUCCESS, PARKING_FAILURE, @@ -5236,27 +5501,195 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere return AST_BRIDGE_TRANSFER_SUCCESS; } +static struct ast_bridge *acquire_bridge(struct ast_channel *chan) +{ + struct ast_bridge *bridge; + + ast_channel_lock(chan); + bridge = ast_channel_get_bridge(chan); + ast_channel_unlock(chan); + + if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_MASQUERADE_ONLY)) { + ao2_ref(bridge, -1); + bridge = NULL; + } + + return bridge; +} + +/*! + * \internal + * \brief Performs an attended transfer by moving a channel from one bridge to another + * + * The channel that is bridged to the source_channel is moved into the dest_bridge from + * the source_bridge_channel's bridge. The swap_channel is swapped out of the dest_bridge and placed in + * the source_bridge_channel's bridge. + * + * \note dest_bridge and source_bridge_channel's bridge MUST be locked before calling this function. + * + * \param dest_bridge The final bridge for the attended transfer + * \param source_channel Channel who is bridged to the channel that will move + * \param swap_channel Channel to be swapped out of the dest_bridge + * \return The success or failure of the swap attempt + */ +static enum ast_transfer_result bridge_swap_attended_transfer(struct ast_bridge *dest_bridge, + struct ast_bridge_channel *source_bridge_channel, struct ast_channel *swap_channel) +{ + struct ast_bridge_channel *bridged_to_source; + + bridged_to_source = ast_bridge_channel_peer(source_bridge_channel); + if (bridged_to_source && bridged_to_source->state == AST_BRIDGE_CHANNEL_STATE_WAIT + && !ast_test_flag(&bridged_to_source->features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE)) { + bridged_to_source->swap = swap_channel; + return bridge_move_do(dest_bridge, bridged_to_source, 1) ? + AST_BRIDGE_TRANSFER_FAIL : AST_BRIDGE_TRANSFER_SUCCESS; + } else { + return AST_BRIDGE_TRANSFER_INVALID; + } +} + +/*! + * \internal + * \brief Function that performs an attended transfer when both transferer channels are bridged + * + * The method by which the transfer is performed is dependent on whether the bridges allow for + * optimization to occur between them. If no optimization is permitted, then an unreal channel + * is placed as a link between the two bridges. If optimization is permitted, then that means + * we are free to perform move or merge operations in order to perform the transfer. + * + * \note to_transferee_bridge and to_target_bridge MUST be locked before calling this function + * + * \param to_transferee The channel that is bridged to the transferee + * \param to_transferee_bridge_channel to_transferee's bridge_channel + * \param to_transfer_target The channel that is bridged to the transfer target + * \param to_target_bridge_channel to_transfer_target's bridge_channel + * \param to_transferee_bridge The bridge between to_transferee and the transferee + * \param to_target_bridge The bridge between to_transfer_target and the transfer_target + * \return The success or failure of the attended transfer + */ +static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel *to_transferee, + struct ast_bridge_channel *to_transferee_bridge_channel, + struct ast_channel *to_transfer_target, + struct ast_bridge_channel *to_target_bridge_channel, + struct ast_bridge *to_transferee_bridge, struct ast_bridge *to_target_bridge) +{ + struct ast_bridge_channel *kick_me[] = { + to_transferee_bridge_channel, + to_target_bridge_channel, + }; + + switch (ast_bridges_allow_optimization(to_transferee_bridge, to_target_bridge)) { + case AST_BRIDGE_OPTIMIZE_SWAP_TO_CHAN_BRIDGE: + return bridge_swap_attended_transfer(to_transferee_bridge, to_target_bridge_channel, to_transferee); + case AST_BRIDGE_OPTIMIZE_SWAP_TO_PEER_BRIDGE: + return bridge_swap_attended_transfer(to_target_bridge, to_transferee_bridge_channel, to_transfer_target); + case AST_BRIDGE_OPTIMIZE_MERGE_TO_CHAN_BRIDGE: + bridge_merge_do(to_transferee_bridge, to_target_bridge, kick_me, ARRAY_LEN(kick_me)); + return AST_BRIDGE_TRANSFER_SUCCESS; + case AST_BRIDGE_OPTIMIZE_MERGE_TO_PEER_BRIDGE: + bridge_merge_do(to_target_bridge, to_transferee_bridge, kick_me, ARRAY_LEN(kick_me)); + return AST_BRIDGE_TRANSFER_SUCCESS; + case AST_BRIDGE_OPTIMIZE_PROHIBITED: + default: + /* Just because optimization wasn't doable doesn't necessarily mean + * that we can actually perform the transfer. Some reasons for non-optimization + * indicate bridge invalidity, so let's check those before proceeding. + */ + if (to_transferee_bridge->inhibit_merge || to_transferee_bridge->dissolved || + to_target_bridge->inhibit_merge || to_target_bridge->dissolved) { + return AST_BRIDGE_TRANSFER_INVALID; + } + return attended_transfer_bridge(to_transferee, to_transfer_target, + to_transferee_bridge, to_target_bridge); + } +} + 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. - */ + struct ast_channel *to_transfer_target) +{ + RAII_VAR(struct ast_bridge *, to_transferee_bridge, NULL, ao2_cleanup); + RAII_VAR(struct ast_bridge *, to_target_bridge, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup); + struct ast_bridge *the_bridge; + struct ast_channel *chan_bridged; + struct ast_channel *chan_unbridged; + int transfer_prohibited; + int do_bridge_transfer; + + to_transferee_bridge = acquire_bridge(to_transferee); + to_target_bridge = acquire_bridge(to_transfer_target); + + /* They can't both be unbridged, you silly goose! */ + if (!to_transferee_bridge && !to_target_bridge) { + return AST_BRIDGE_TRANSFER_INVALID; + } + + /* Let's get the easy one out of the way first */ + if (to_transferee_bridge && to_target_bridge) { + RAII_VAR(struct ast_bridge_channel *, to_transferee_bridge_channel, NULL, ao2_cleanup); + RAII_VAR(struct ast_bridge_channel *, to_target_bridge_channel, NULL, ao2_cleanup); + enum ast_transfer_result res; + + ast_channel_lock(to_transferee); + to_transferee_bridge_channel = ast_channel_get_bridge_channel(to_transferee); + ast_channel_unlock(to_transferee); + + ast_channel_lock(to_transfer_target); + to_target_bridge_channel = ast_channel_get_bridge_channel(to_transfer_target); + ast_channel_unlock(to_transfer_target); + + ast_bridge_lock_both(to_transferee_bridge, to_target_bridge); + res = two_bridge_attended_transfer(to_transferee, to_transferee_bridge_channel, + to_transfer_target, to_target_bridge_channel, + to_transferee_bridge, to_target_bridge); + ast_bridge_unlock(to_transferee_bridge); + ast_bridge_unlock(to_target_bridge); + + return res; + } + + the_bridge = to_transferee_bridge ?: to_target_bridge; + chan_bridged = to_transferee_bridge ? to_transferee : to_transfer_target; + chan_unbridged = to_transferee_bridge ? to_transfer_target : to_transferee; + + { + int chan_count; + SCOPED_LOCK(lock, the_bridge, ast_bridge_lock, ast_bridge_unlock); + + channels = ast_bridge_peers_nolock(the_bridge); + if (!channels) { + return AST_BRIDGE_TRANSFER_FAIL; + } + chan_count = ao2_container_count(channels); + if (chan_count <= 1) { + return AST_BRIDGE_TRANSFER_INVALID; + } + transfer_prohibited = ast_test_flag(&the_bridge->feature_flags, + AST_BRIDGE_FLAG_TRANSFER_PROHIBITED); + do_bridge_transfer = ast_test_flag(&the_bridge->feature_flags, + AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) || + chan_count > 2; + } + + if (transfer_prohibited) { + return AST_BRIDGE_TRANSFER_NOT_PERMITTED; + } + + if (do_bridge_transfer) { + return attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL); + } + + transferee = get_transferee(channels, chan_bridged); + if (!transferee) { + return AST_BRIDGE_TRANSFER_FAIL; + } + + if (bridge_channel_queue_attended_transfer(transferee, chan_unbridged)) { + return AST_BRIDGE_TRANSFER_FAIL; + } - /* XXX STUB */ + ast_bridge_remove(the_bridge, chan_bridged); return AST_BRIDGE_TRANSFER_SUCCESS; } |