#include "asterisk.h"
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
#include "asterisk/channel.h"
#include "asterisk/utils.h"
#include "asterisk/linkedlists.h"
#include "asterisk/app.h"
#include "asterisk/dial.h"
#include "asterisk/stasis_bridges.h"
+#include "asterisk/stasis_channels.h"
#include "asterisk/features.h"
+#include "asterisk/format_cache.h"
+#include "asterisk/test.h"
#define NORMAL_FLAGS (AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_DISSOLVE_EMPTY \
| AST_BRIDGE_FLAG_SMART)
*/
static int setup_bridge_features_dynamic(struct ast_bridge_features *features, struct ast_channel *chan)
{
- RAII_VAR(struct ao2_container *, applicationmap, NULL, ao2_cleanup);
+ struct ao2_container *applicationmap;
int res = 0;
ast_channel_lock(chan);
applicationmap = ast_get_chan_applicationmap(chan);
ast_channel_unlock(chan);
- if (!applicationmap) {
- return 0;
+ if (applicationmap) {
+ ao2_callback_data(applicationmap, 0, setup_dynamic_feature, features, &res);
+ ao2_ref(applicationmap, -1);
}
- ao2_callback_data(applicationmap, 0, setup_dynamic_feature, features, &res);
-
return res;
}
return -1;
}
- ast_bridge_channel_update_accountcodes(bridge_channel, swap);
- ast_bridge_channel_update_linkedids(bridge_channel, swap);
return 0;
}
ast_assert(personality != NULL);
- if (personality->details[personality->current].v_table->push(self, bridge_channel, swap)) {
+ if (personality->details[personality->current].v_table->push
+ && personality->details[personality->current].v_table->push(self, bridge_channel, swap)) {
return -1;
}
+ ast_bridge_channel_update_linkedids(bridge_channel, swap);
+ ast_bridge_channel_update_accountcodes(bridge_channel, swap);
+
return ast_bridge_base_v_table.push(self, bridge_channel, swap);
}
personality->details[personality->current].v_table->pull(self, bridge_channel);
}
+ ast_bridge_channel_update_accountcodes(NULL, bridge_channel);
+
ast_bridge_base_v_table.pull(self, bridge_channel);
}
AST_STRING_FIELD(exten);
/*! Context of transfer target */
AST_STRING_FIELD(context);
- /*! Sound to play on failure */
- AST_STRING_FIELD(failsound);
/*! Sound to play when transfer completes */
AST_STRING_FIELD(xfersound);
/*! The channel technology of the transferer channel */
struct ast_channel *transfer_target;
/*! The party that is currently being recalled. Depending on
* the current state, this may be either the party that originally
- * was the transferer or the original transfer target
+ * was the transferer or the original transfer target. This is
+ * set with reference when entering the BLOND_NONFINAL, RECALLING,
+ * and RETRANSFER states, and the reference released on state exit
+ * if continuing with recall or retransfer to avoid leak.
*/
struct ast_channel *recall_target;
/*! The absolute starting time for running timers */
struct ast_dial *dial;
/*! The bridging features the transferer has available */
struct ast_flags transferer_features;
+ /*! Saved transferer connected line data for recalling the transferer. */
+ struct ast_party_connected_line original_transferer_colp;
};
static void attended_transfer_properties_destructor(void *obj)
ast_channel_cleanup(props->transferer);
ast_channel_cleanup(props->transfer_target);
ast_channel_cleanup(props->recall_target);
+ ast_party_connected_line_free(&props->original_transferer_colp);
ast_string_field_free_memory(props);
ast_cond_destroy(&props->cond);
}
char *tech;
char *addr;
char *serial;
- RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
+ struct ast_features_xfer_config *xfer_cfg;
struct ast_flags *transferer_features;
props = ao2_alloc(sizeof(*props), attended_transfer_properties_destructor);
- if (!props || ast_string_field_init(props, 64)) {
+ if (!props) {
+ ast_log(LOG_ERROR, "Unable to create props - channel %s, context %s\n",
+ ast_channel_name(transferer), context);
return NULL;
}
ast_cond_init(&props->cond, NULL);
+ if (ast_string_field_init(props, 64)) {
+ ast_log(LOG_ERROR, "Unable to initialize prop fields - channel %s, context %s\n",
+ ast_channel_name(transferer), context);
+ ao2_ref(props, -1);
+ return NULL;
+ }
+
props->target_framehook_id = -1;
props->transferer = ast_channel_ref(transferer);
xfer_cfg = ast_get_chan_features_xfer_config(props->transferer);
if (!xfer_cfg) {
ast_log(LOG_ERROR, "Unable to get transfer configuration from channel %s\n", ast_channel_name(props->transferer));
+ ast_channel_unlock(props->transferer);
ao2_ref(props, -1);
return NULL;
}
props->atxfernoanswertimeout = xfer_cfg->atxfernoanswertimeout;
props->atxferloopdelay = xfer_cfg->atxferloopdelay;
ast_string_field_set(props, context, get_transfer_context(transferer, context));
- ast_string_field_set(props, failsound, xfer_cfg->xferfailsound);
ast_string_field_set(props, xfersound, xfer_cfg->xfersound);
+ ao2_ref(xfer_cfg, -1);
+
+ /*
+ * Save the transferee's party information for any recall calls.
+ * This is the only piece of information needed that gets overwritten
+ * on the transferer channel by the inital call to the transfer target.
+ */
+ ast_party_connected_line_copy(&props->original_transferer_colp,
+ ast_channel_connected(props->transferer));
tech = ast_strdupa(ast_channel_name(props->transferer));
addr = strchr(tech, '/');
if (!addr) {
ast_log(LOG_ERROR, "Transferer channel name does not follow typical channel naming format (tech/address)\n");
- ast_channel_unref(props->transferer);
+ ast_channel_unlock(props->transferer);
+ ao2_ref(props, -1);
return NULL;
}
*addr++ = '\0';
ao2_unlock(props);
}
+static void remove_attended_transfer_stimulus(struct attended_transfer_properties *props,
+ enum attended_transfer_stimulus stimulus)
+{
+ struct stimulus_list *list;
+
+ ao2_lock(props);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&props->stimulus_queue, list, next) {
+ if (list->stimulus == stimulus) {
+ AST_LIST_REMOVE_CURRENT(next);
+ ast_free(list);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ ao2_unlock(props);
+}
+
+/*!
+ * \brief Get a desired transfer party for a bridge the transferer is not in.
+ *
+ * \param bridge The bridge to get the party from. May be NULL.
+ * \param[out] party The lone channel in the bridge. Will be set NULL if bridge is NULL or multiple parties are present.
+ */
+static void get_transfer_party_non_transferer_bridge(struct ast_bridge *bridge,
+ struct ast_channel **party)
+{
+ if (bridge && bridge->num_channels == 1) {
+ *party = ast_channel_ref(AST_LIST_FIRST(&bridge->channels)->chan);
+ } else {
+ *party = NULL;
+ }
+}
+
+/*!
+ * \brief Get the transferee and transfer target when the transferer is in a bridge with
+ * one of the desired parties.
+ *
+ * \param transferer_bridge The bridge the transferer is in
+ * \param other_bridge The bridge the transferer is not in. May be NULL.
+ * \param transferer The transferer party
+ * \param[out] transferer_peer The party that is in the bridge with the transferer
+ * \param[out] other_party The party that is in the other_bridge
+ */
+static void get_transfer_parties_transferer_bridge(struct ast_bridge *transferer_bridge,
+ struct ast_bridge *other_bridge, struct ast_channel *transferer,
+ struct ast_channel **transferer_peer, struct ast_channel **other_party)
+{
+ *transferer_peer = ast_bridge_peer(transferer_bridge, transferer);
+ get_transfer_party_non_transferer_bridge(other_bridge, other_party);
+}
+
+/*!
+ * \brief determine transferee and transfer target for an attended transfer
+ *
+ * In builtin attended transfers, there is a single transferer channel that jumps between
+ * the two bridges involved. At the time the attended transfer occurs, the transferer could
+ * be in either bridge, so determining the parties is a bit more complex than normal.
+ *
+ * The method used here is to determine which of the two bridges the transferer is in, and
+ * grabbing the peer from that bridge. The other bridge, if it only has a single channel in it,
+ * has the other desired channel.
+ *
+ * \param transferer The channel performing the transfer
+ * \param transferee_bridge The bridge that the transferee is in
+ * \param target_bridge The bridge that the transfer target is in
+ * \param[out] transferee The transferee channel
+ * \param[out] transfer_target The transfer target channel
+ */
+static void get_transfer_parties(struct ast_channel *transferer, struct ast_bridge *transferee_bridge,
+ struct ast_bridge *target_bridge, struct ast_channel **transferee,
+ struct ast_channel **transfer_target)
+{
+ struct ast_bridge *transferer_bridge;
+
+ ast_channel_lock(transferer);
+ transferer_bridge = ast_channel_get_bridge(transferer);
+ ast_channel_unlock(transferer);
+
+ if (transferer_bridge == transferee_bridge) {
+ get_transfer_parties_transferer_bridge(transferee_bridge, target_bridge,
+ transferer, transferee, transfer_target);
+ } else if (transferer_bridge == target_bridge) {
+ get_transfer_parties_transferer_bridge(target_bridge, transferee_bridge,
+ transferer, transfer_target, transferee);
+ } else {
+ get_transfer_party_non_transferer_bridge(transferee_bridge, transferee);
+ get_transfer_party_non_transferer_bridge(target_bridge, transfer_target);
+ }
+
+ ao2_cleanup(transferer_bridge);
+}
+
/*!
* \brief Send a stasis publication for a successful attended transfer
*/
-static void publish_transfer_success(struct attended_transfer_properties *props)
+static void publish_transfer_success(struct attended_transfer_properties *props,
+ struct ast_channel *transferee_channel, struct ast_channel *target_channel)
{
- struct ast_bridge_channel_pair transferee = {
- .channel = props->transferer,
- .bridge = props->transferee_bridge,
- };
- struct ast_bridge_channel_pair transfer_target = {
- .channel = props->transferer,
- .bridge = props->target_bridge,
- };
+ struct ast_attended_transfer_message *transfer_msg;
+
+ transfer_msg = ast_attended_transfer_message_create(0, props->transferer,
+ props->transferee_bridge, props->transferer, props->target_bridge,
+ transferee_channel, target_channel);
- ast_bridge_publish_attended_transfer_bridge_merge(0, AST_BRIDGE_TRANSFER_SUCCESS,
- &transferee, &transfer_target, props->transferee_bridge);
+ if (!transfer_msg) {
+ ast_log(LOG_ERROR, "Unable to publish successful attended transfer from %s\n",
+ ast_channel_name(props->transferer));
+ return;
+ }
+
+ ast_attended_transfer_message_add_merge(transfer_msg, props->transferee_bridge);
+ ast_bridge_publish_attended_transfer(transfer_msg);
+ ao2_cleanup(transfer_msg);
}
/*!
* \brief Send a stasis publication for an attended transfer that ends in a threeway call
*/
-static void publish_transfer_threeway(struct attended_transfer_properties *props)
+static void publish_transfer_threeway(struct attended_transfer_properties *props,
+ struct ast_channel *transferee_channel, struct ast_channel *target_channel)
{
- struct ast_bridge_channel_pair transferee = {
- .channel = props->transferer,
- .bridge = props->transferee_bridge,
- };
- struct ast_bridge_channel_pair transfer_target = {
- .channel = props->transferer,
- .bridge = props->target_bridge,
- };
- struct ast_bridge_channel_pair threeway = {
- .channel = props->transferer,
- .bridge = props->transferee_bridge,
- };
+ struct ast_attended_transfer_message *transfer_msg;
+
+ transfer_msg = ast_attended_transfer_message_create(0, props->transferer,
+ props->transferee_bridge, props->transferer, props->target_bridge,
+ transferee_channel, target_channel);
- ast_bridge_publish_attended_transfer_threeway(0, AST_BRIDGE_TRANSFER_SUCCESS,
- &transferee, &transfer_target, &threeway);
+ if (!transfer_msg) {
+ ast_log(LOG_ERROR, "Unable to publish successful three-way transfer from %s\n",
+ ast_channel_name(props->transferer));
+ return;
+ }
+
+ ast_attended_transfer_message_add_threeway(transfer_msg, props->transferer,
+ props->transferee_bridge);
+ ast_bridge_publish_attended_transfer(transfer_msg);
+ ao2_cleanup(transfer_msg);
}
/*!
*/
static void publish_transfer_fail(struct attended_transfer_properties *props)
{
- struct ast_bridge_channel_pair transferee = {
- .channel = props->transferer,
- .bridge = props->transferee_bridge,
- };
- struct ast_bridge_channel_pair transfer_target = {
- .channel = props->transferer,
- .bridge = props->target_bridge,
- };
+ struct ast_attended_transfer_message *transfer_msg;
+
+ transfer_msg = ast_attended_transfer_message_create(0, props->transferer,
+ props->transferee_bridge, props->transferer, props->target_bridge,
+ NULL, NULL);
- ast_bridge_publish_attended_transfer_fail(0, AST_BRIDGE_TRANSFER_FAIL,
- &transferee, &transfer_target);
+ if (!transfer_msg) {
+ ast_log(LOG_ERROR, "Unable to publish failed transfer from %s\n",
+ ast_channel_name(props->transferer));
+ return;
+ }
+
+ transfer_msg->result = AST_BRIDGE_TRANSFER_FAIL;
+ ast_bridge_publish_attended_transfer(transfer_msg);
+ ao2_cleanup(transfer_msg);
}
/*!
*/
static void play_sound(struct ast_channel *chan, const char *sound)
{
- RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
ast_channel_lock(chan);
bridge_channel = ast_channel_get_bridge_channel(chan);
ast_channel_unlock(chan);
- if (!bridge_channel) {
- return;
+ if (bridge_channel) {
+ ast_bridge_channel_queue_playfile(bridge_channel, NULL, sound, NULL);
+ ao2_ref(bridge_channel, -1);
}
+}
- ast_bridge_channel_queue_playfile(bridge_channel, NULL, sound, NULL);
+/*!
+ * \brief Helper method to play a fail sound on a channel in a bridge
+ *
+ * \param chan The channel to play the fail sound to
+ */
+static void play_failsound(struct ast_channel *chan)
+{
+ char *sound;
+
+ ast_channel_lock(chan);
+ sound = ast_get_chan_features_xferfailsound(chan);
+ ast_channel_unlock(chan);
+
+ if (sound) {
+ play_sound(chan, sound);
+ ast_free(sound);
+ }
+}
+
+/*!
+ * \brief Helper method to stream a fail sound on a channel
+ *
+ * \param chan The channel to stream the fail sound to
+ */
+static void stream_failsound(struct ast_channel *chan)
+{
+ char *sound;
+
+ ast_channel_lock(chan);
+ sound = ast_get_chan_features_xferfailsound(chan);
+ ast_channel_unlock(chan);
+
+ if (sound) {
+ ast_stream_and_wait(chan, sound, AST_DIGIT_NONE);
+ ast_free(sound);
+ }
}
/*!
*/
static void hold(struct ast_channel *chan)
{
- RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
- if (chan) {
- ast_channel_lock(chan);
- bridge_channel = ast_channel_get_bridge_channel(chan);
- ast_channel_unlock(chan);
+ if (!chan) {
+ return;
+ }
- ast_assert(bridge_channel != NULL);
+ ast_channel_lock(chan);
+ bridge_channel = ast_channel_get_bridge_channel(chan);
+ ast_channel_unlock(chan);
+ if (bridge_channel) {
ast_bridge_channel_write_hold(bridge_channel, NULL);
+ ao2_ref(bridge_channel, -1);
}
}
*/
static void unhold(struct ast_channel *chan)
{
- RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
+
+ if (!chan) {
+ return;
+ }
ast_channel_lock(chan);
bridge_channel = ast_channel_get_bridge_channel(chan);
ast_channel_unlock(chan);
- ast_assert(bridge_channel != NULL);
-
- ast_bridge_channel_write_unhold(bridge_channel);
+ if (bridge_channel) {
+ ast_bridge_channel_write_unhold(bridge_channel);
+ ao2_ref(bridge_channel, -1);
+ }
}
/*!
*/
static void ringing(struct ast_channel *chan)
{
- RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
ast_channel_lock(chan);
bridge_channel = ast_channel_get_bridge_channel(chan);
ast_channel_unlock(chan);
- ast_assert(bridge_channel != NULL);
-
- ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_RINGING, NULL, 0);
+ if (bridge_channel) {
+ ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_RINGING, NULL, 0);
+ ao2_ref(bridge_channel, -1);
+ }
}
/*!
/*!
* \brief Wrapper for \ref bridge_do_move
*/
-static int bridge_move(struct ast_bridge *dest, struct ast_bridge *src, struct ast_channel *channel, struct ast_channel *swap)
+static void bridge_move(struct ast_bridge *dest, struct ast_bridge *src, struct ast_channel *channel, struct ast_channel *swap)
{
- int res;
- RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *bridge_channel;
ast_bridge_lock_both(src, dest);
bridge_channel = ast_channel_get_bridge_channel(channel);
ast_channel_unlock(channel);
- ast_assert(bridge_channel != NULL);
+ if (bridge_channel) {
+ ao2_lock(bridge_channel);
+ bridge_channel->swap = swap;
+ ao2_unlock(bridge_channel);
- ao2_lock(bridge_channel);
- bridge_channel->swap = swap;
- ao2_unlock(bridge_channel);
-
- res = bridge_do_move(dest, bridge_channel, 1, 0);
+ bridge_do_move(dest, bridge_channel, 1, 0);
+ }
ast_bridge_unlock(dest);
ast_bridge_unlock(src);
- return res;
+ ao2_cleanup(bridge_channel);
}
/*!
static int calling_target_enter(struct attended_transfer_properties *props)
{
- return bridge_move(props->target_bridge, props->transferee_bridge, props->transferer, NULL);
+ bridge_move(props->target_bridge, props->transferee_bridge, props->transferer, NULL);
+ return 0;
}
static enum attended_transfer_state calling_target_exit(struct attended_transfer_properties *props,
{
switch (stimulus) {
case STIMULUS_TRANSFEREE_HANGUP:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
publish_transfer_fail(props);
return TRANSFER_FAIL;
case STIMULUS_DTMF_ATXFER_COMPLETE:
case STIMULUS_TRANSFER_TARGET_HANGUP:
case STIMULUS_TIMEOUT:
case STIMULUS_DTMF_ATXFER_ABORT:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
return TRANSFER_REBRIDGE;
case STIMULUS_DTMF_ATXFER_THREEWAY:
bridge_unhold(props->transferee_bridge);
static int hesitant_enter(struct attended_transfer_properties *props)
{
- if (bridge_move(props->transferee_bridge, props->target_bridge, props->transferer, NULL)) {
- return -1;
- }
-
+ bridge_move(props->transferee_bridge, props->target_bridge, props->transferer, NULL);
unhold(props->transferer);
return 0;
}
{
switch (stimulus) {
case STIMULUS_TRANSFEREE_HANGUP:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
publish_transfer_fail(props);
return TRANSFER_FAIL;
case STIMULUS_DTMF_ATXFER_COMPLETE:
case STIMULUS_TRANSFER_TARGET_HANGUP:
case STIMULUS_TIMEOUT:
case STIMULUS_DTMF_ATXFER_ABORT:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
return TRANSFER_RESUME;
case STIMULUS_DTMF_ATXFER_THREEWAY:
return TRANSFER_THREEWAY;
static int rebridge_enter(struct attended_transfer_properties *props)
{
- if (bridge_move(props->transferee_bridge, props->target_bridge,
- props->transferer, NULL)) {
- return -1;
- }
-
+ bridge_move(props->transferee_bridge, props->target_bridge, props->transferer, NULL);
unhold(props->transferer);
return 0;
}
static int threeway_enter(struct attended_transfer_properties *props)
{
+ struct ast_channel *transferee_channel;
+ struct ast_channel *target_channel;
+
+ get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge,
+ &transferee_channel, &target_channel);
bridge_merge(props->transferee_bridge, props->target_bridge, NULL, 0);
play_sound(props->transfer_target, props->xfersound);
play_sound(props->transferer, props->xfersound);
- publish_transfer_threeway(props);
+ publish_transfer_threeway(props, transferee_channel, target_channel);
+ ast_channel_cleanup(transferee_channel);
+ ast_channel_cleanup(target_channel);
return 0;
}
* a sound to the transferer to indicate the transferee is gone.
*/
bridge_basic_change_personality(props->target_bridge, BRIDGE_BASIC_PERSONALITY_NORMAL, NULL);
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
ast_bridge_merge_inhibit(props->target_bridge, -1);
/* These next two lines are here to ensure that our reference to the target bridge
* is cleaned up properly and that the target bridge is not destroyed when the
bridge_unhold(props->transferee_bridge);
return TRANSFER_COMPLETE;
case STIMULUS_TRANSFER_TARGET_HANGUP:
+ return TRANSFER_REBRIDGE;
case STIMULUS_DTMF_ATXFER_ABORT:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
return TRANSFER_REBRIDGE;
case STIMULUS_DTMF_ATXFER_THREEWAY:
bridge_unhold(props->transferee_bridge);
{
switch (stimulus) {
case STIMULUS_TRANSFEREE_HANGUP:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
publish_transfer_fail(props);
return TRANSFER_FAIL;
case STIMULUS_TRANSFERER_HANGUP:
return TRANSFER_COMPLETE;
case STIMULUS_TRANSFER_TARGET_HANGUP:
case STIMULUS_DTMF_ATXFER_ABORT:
- play_sound(props->transferer, props->failsound);
+ play_failsound(props->transferer);
return TRANSFER_RESUME;
case STIMULUS_DTMF_ATXFER_THREEWAY:
bridge_unhold(props->target_bridge);
static int complete_enter(struct attended_transfer_properties *props)
{
+ struct ast_channel *transferee_channel;
+ struct ast_channel *target_channel;
+
+ get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge,
+ &transferee_channel, &target_channel);
bridge_merge(props->transferee_bridge, props->target_bridge, &props->transferer, 1);
play_sound(props->transfer_target, props->xfersound);
- publish_transfer_success(props);
+ publish_transfer_success(props, transferee_channel, target_channel);
+
+ ast_channel_cleanup(transferee_channel);
+ ast_channel_cleanup(target_channel);
return 0;
}
static int blond_enter(struct attended_transfer_properties *props)
{
+ struct ast_channel *transferee_channel;
+ struct ast_channel *target_channel;
+
+ get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge,
+ &transferee_channel, &target_channel);
bridge_merge(props->transferee_bridge, props->target_bridge, &props->transferer, 1);
ringing(props->transfer_target);
- publish_transfer_success(props);
+ publish_transfer_success(props, transferee_channel, target_channel);
+
+ ast_channel_cleanup(transferee_channel);
+ ast_channel_cleanup(target_channel);
return 0;
}
{
int res;
props->superstate = SUPERSTATE_RECALL;
+ /* move the transfer target to the recall target along with its reference */
props->recall_target = ast_channel_ref(props->transfer_target);
res = blond_enter(props);
props->transfer_target = ast_channel_unref(props->transfer_target);
return TRANSFER_RESUME;
case STIMULUS_TIMEOUT:
ast_softhangup(props->recall_target, AST_SOFTHANGUP_EXPLICIT);
- props->recall_target = ast_channel_unref(props->recall_target);
+ /* It is possible before we hung them up that they queued up a recall target answer
+ * so we remove it if present as it should not exist.
+ */
+ remove_attended_transfer_stimulus(props, STIMULUS_RECALL_TARGET_ANSWER);
case STIMULUS_RECALL_TARGET_HANGUP:
+ props->recall_target = ast_channel_unref(props->recall_target);
return TRANSFER_RECALLING;
case STIMULUS_NONE:
case STIMULUS_DTMF_ATXFER_ABORT:
}
}
+/*!
+ * \internal
+ * \brief Setup common things to transferrer and transfer_target recall channels.
+ *
+ * \param recall Channel for recalling a party.
+ * \param transferer Channel supplying recall information.
+ *
+ * \details
+ * Setup callid, variables, datastores, accountcode, and peeraccount.
+ *
+ * \pre Both channels are locked on entry.
+ *
+ * \pre COLP and CLID on the recall channel are setup by the caller but not
+ * explicitly published yet.
+ *
+ * \return Nothing
+ */
+static void common_recall_channel_setup(struct ast_channel *recall, struct ast_channel *transferer)
+{
+ ast_callid callid;
+
+ callid = ast_read_threadstorage_callid();
+ if (callid) {
+ ast_channel_callid_set(recall, callid);
+ }
+
+ ast_channel_inherit_variables(transferer, recall);
+ ast_channel_datastore_inherit(transferer, recall);
+
+ /*
+ * Stage a snapshot to ensure that a snapshot is always done
+ * on the recall channel so earler COLP and CLID setup will
+ * get published.
+ */
+ ast_channel_stage_snapshot(recall);
+ ast_channel_req_accountcodes(recall, transferer, AST_CHANNEL_REQUESTOR_REPLACEMENT);
+ ast_channel_stage_snapshot_done(recall);
+}
static int recalling_enter(struct attended_transfer_properties *props)
{
- RAII_VAR(struct ast_format_cap *, cap, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_NOLOCK), ast_format_cap_destroy);
- struct ast_format fmt;
+ RAII_VAR(struct ast_format_cap *, cap, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
+ struct ast_channel *recall;
if (!cap) {
return -1;
}
- ast_format_cap_add(cap, ast_format_set(&fmt, AST_FORMAT_SLINEAR, 0));
+ ast_format_cap_append(cap, ast_format_slin, 0);
/* When we dial the transfer target, since we are communicating
* with a local channel, we can place the local channel in a bridge
return -1;
}
- if (ast_dial_append(props->dial, props->transferer_type, props->transferer_addr)) {
+ if (ast_dial_append(props->dial, props->transferer_type, props->transferer_addr, NULL)) {
return -1;
}
return -1;
}
- ast_dial_set_state_callback(props->dial, &recall_callback);
+ /*
+ * Setup callid, variables, datastores, accountcode, peeraccount,
+ * COLP, and CLID on the recalled transferrer.
+ */
+ recall = ast_dial_get_channel(props->dial, 0);
+ if (!recall) {
+ return -1;
+ }
+ ast_channel_lock_both(recall, props->transferer);
+
+ ast_party_caller_copy(ast_channel_caller(recall),
+ ast_channel_caller(props->transferer));
+ ast_party_connected_line_copy(ast_channel_connected(recall),
+ &props->original_transferer_colp);
+
+ common_recall_channel_setup(recall, props->transferer);
+ ast_channel_unlock(recall);
+ ast_channel_unlock(props->transferer);
+
+ ast_dial_set_state_callback(props->dial, recall_callback);
ao2_ref(props, +1);
ast_dial_set_user_data(props->dial, props);
if (ast_bridge_impart(props->transferee_bridge, props->recall_target, NULL, NULL,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
ast_hangup(props->recall_target);
+ ast_channel_unref(props->recall_target);
return TRANSFER_FAIL;
}
return TRANSFER_RESUME;
static int retransfer_enter(struct attended_transfer_properties *props)
{
- RAII_VAR(struct ast_format_cap *, cap, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_NOLOCK), ast_format_cap_destroy);
- struct ast_format fmt;
+ RAII_VAR(struct ast_format_cap *, cap, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
int cause;
snprintf(destination, sizeof(destination), "%s@%s", props->exten, props->context);
- ast_format_cap_add(cap, ast_format_set(&fmt, AST_FORMAT_SLINEAR, 0));
+ ast_format_cap_append(cap, ast_format_slin, 0);
/* Get a channel that is the destination we wish to call */
- props->recall_target = ast_request("Local", cap, NULL, destination, &cause);
+ props->recall_target = ast_request("Local", cap, NULL, NULL, destination, &cause);
if (!props->recall_target) {
ast_log(LOG_ERROR, "Unable to request outbound channel for recall target\n");
return -1;
return -1;
}
+ /*
+ * Setup callid, variables, datastores, accountcode, peeraccount,
+ * and COLP on the recalled transfer target.
+ */
+ ast_channel_lock_both(props->recall_target, props->transferer);
+
+ ast_party_connected_line_copy(ast_channel_connected(props->recall_target),
+ &props->original_transferer_colp);
+ ast_party_id_reset(&ast_channel_connected(props->recall_target)->priv);
+
+ common_recall_channel_setup(props->recall_target, props->recall_target);
+ ast_channel_unlock(props->recall_target);
+ ast_channel_unlock(props->transferer);
+
if (ast_call(props->recall_target, destination, 0)) {
ast_log(LOG_ERROR, "Unable to place outbound call to recall target\n");
ast_hangup(props->recall_target);
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
ast_log(LOG_ERROR, "Unable to place recall target into bridge\n");
ast_hangup(props->recall_target);
+ ast_channel_unref(props->recall_target);
return -1;
}
if (event == AST_FRAMEHOOK_EVENT_READ &&
frame && frame->frametype == AST_FRAME_CONTROL &&
- frame->subclass.integer == AST_CONTROL_ANSWER) {
+ frame->subclass.integer == AST_CONTROL_ANSWER &&
+ !ast_check_hangup(chan)) {
ast_debug(1, "Detected an answer for recall attempt on attended transfer %p\n", props);
if (props->superstate == SUPERSTATE_TRANSFER) {
return frame;
}
+/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
+static int transfer_target_framehook_consume(void *data, enum ast_frame_type type)
+{
+ return (type == AST_FRAME_CONTROL ? 1 : 0);
+}
+
static void transfer_target_framehook_destroy_cb(void *data)
{
struct attended_transfer_properties *props = data;
}
if (self->num_channels == 1) {
- RAII_VAR(struct ast_bridge_channel *, transferer_bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *transferer_bridge_channel;
+ int not_transferer;
ast_channel_lock(props->transferer);
transferer_bridge_channel = ast_channel_get_bridge_channel(props->transferer);
return;
}
- if (AST_LIST_FIRST(&self->channels) != transferer_bridge_channel) {
+ not_transferer = AST_LIST_FIRST(&self->channels) != transferer_bridge_channel;
+ ao2_ref(transferer_bridge_channel, -1);
+ if (not_transferer) {
return;
}
}
}
if (self->num_channels == 1) {
- RAII_VAR(struct ast_bridge_channel *, target_bridge_channel, NULL, ao2_cleanup);
+ struct ast_bridge_channel *target_bridge_channel;
+
if (!props->recall_target) {
/* No recall target means that the pull happened on a transferee. If there's still
* a channel left in the bridge, we don't need to send a stimulus
target_bridge_channel = ast_channel_get_bridge_channel(props->recall_target);
ast_channel_unlock(props->recall_target);
- if (!target_bridge_channel) {
- return;
- }
-
- if (AST_LIST_FIRST(&self->channels) == target_bridge_channel) {
- stimulate_attended_transfer(props, STIMULUS_TRANSFEREE_HANGUP);
+ if (target_bridge_channel) {
+ if (AST_LIST_FIRST(&self->channels) == target_bridge_channel) {
+ stimulate_attended_transfer(props, STIMULUS_TRANSFEREE_HANGUP);
+ }
+ ao2_ref(target_bridge_channel, -1);
}
}
}
static enum attended_transfer_stimulus wait_for_stimulus(struct attended_transfer_properties *props)
{
- RAII_VAR(struct stimulus_list *, list, NULL, ast_free_ptr);
+ enum attended_transfer_stimulus stimulus;
+ struct stimulus_list *list;
SCOPED_MUTEX(lock, ao2_object_get_lockaddr(props));
while (!(list = AST_LIST_REMOVE_HEAD(&props->stimulus_queue, next))) {
}
}
}
- return list->stimulus;
+ stimulus = list->stimulus;
+ ast_free(list);
+ return stimulus;
}
/*!
static void *attended_transfer_monitor_thread(void *data)
{
struct attended_transfer_properties *props = data;
+ ast_callid callid;
+
+ /*
+ * Set thread callid to the transferer's callid because we
+ * are doing all this on that channel's behalf.
+ */
+ ast_channel_lock(props->transferer);
+ callid = ast_channel_callid(props->transferer);
+ ast_channel_unlock(props->transferer);
+ if (callid) {
+ ast_callid_threadassoc_add(callid);
+ }
for (;;) {
enum attended_transfer_stimulus stimulus;
attended_transfer_properties_shutdown(props);
+ if (callid) {
+ ast_callid_threadassoc_remove();
+ }
+
return NULL;
}
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = transfer_target_framehook_cb,
.destroy_cb = transfer_target_framehook_destroy_cb,
+ .consume_cb = transfer_target_framehook_consume,
+ .disable_inheritance = 1,
};
ao2_ref(props, +1);
target_interface.data = props;
+ ast_channel_lock(channel);
props->target_framehook_id = ast_framehook_attach(channel, &target_interface);
+ ast_channel_unlock(channel);
if (props->target_framehook_id == -1) {
ao2_ref(props, -1);
return -1;
const char *atxfer_threeway;
const char *atxfer_complete;
const char *atxfer_swap;
- RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
+ struct ast_features_xfer_config *xfer_cfg;
SCOPED_CHANNELLOCK(lock, chan);
xfer_cfg = ast_get_chan_features_xfer_config(chan);
atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete);
atxfer_swap = ast_strdupa(xfer_cfg->atxferswap);
}
+ ao2_ref(xfer_cfg, -1);
return ast_channel_add_bridge_role(chan, AST_TRANSFERER_ROLE_NAME) ||
ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "abort", atxfer_abort) ||
{
int res;
int digit_timeout;
- RAII_VAR(struct ast_features_xfer_config *, xfer_cfg, NULL, ao2_cleanup);
+ int attempts = 0;
+ int max_attempts;
+ struct ast_features_xfer_config *xfer_cfg;
+ char *retry_sound;
+ char *invalid_sound;
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_log(LOG_ERROR, "Channel %s: Unable to get transfer configuration\n",
+ ast_channel_name(chan));
ast_channel_unlock(chan);
return -1;
}
digit_timeout = xfer_cfg->transferdigittimeout * 1000;
+ max_attempts = xfer_cfg->transferdialattempts;
+ retry_sound = ast_strdupa(xfer_cfg->transferretrysound);
+ invalid_sound = ast_strdupa(xfer_cfg->transferinvalidsound);
+ ao2_ref(xfer_cfg, -1);
ast_channel_unlock(chan);
/* Play the simple "transfer" prompt out and wait */
}
/* 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));
+ do {
+ ++attempts;
+
+ ast_test_suite_event_notify("TRANSFER_BEGIN_DIAL",
+ "Channel: %s\r\n"
+ "Attempt: %d",
+ ast_channel_name(chan), attempts);
+ res = ast_app_dtget(chan, context, exten, exten_len, exten_len - 1, digit_timeout);
+ ast_test_suite_event_notify("TRANSFER_DIALLED",
+ "Channel: %s\r\n"
+ "Attempt: %d\r\n"
+ "Dialled: %s\r\n"
+ "Result: %s",
+ ast_channel_name(chan), attempts, exten, res > 0 ? "Success" : "Failure");
+ if (res < 0) {
+ /* Hangup or error */
+ res = -1;
+ } else if (!res) {
+ /* 0 for invalid extension dialed. */
+ if (ast_strlen_zero(exten)) {
+ ast_verb(3, "Channel %s: Dialed no digits.\n", ast_channel_name(chan));
+ } else {
+ ast_verb(3, "Channel %s: Dialed '%s@%s' does not exist.\n",
+ ast_channel_name(chan), exten, context);
+ }
+ if (attempts < max_attempts) {
+ ast_stream_and_wait(chan, retry_sound, AST_DIGIT_NONE);
+ } else {
+ ast_stream_and_wait(chan, invalid_sound, AST_DIGIT_NONE);
+ }
+ memset(exten, 0, exten_len);
+ res = 1;
} else {
- ast_debug(1, "%s dialed '%s@%s' does not exist.\n",
- ast_channel_name(chan), exten, context);
+ /* Dialed extension is valid. */
+ res = 0;
}
- ast_stream_and_wait(chan, "pbx-invalid", AST_DIGIT_NONE);
- res = -1;
- } else {
- /* Dialed extension is valid. */
- res = 0;
- }
- return res;
+ } while (res > 0 && attempts < max_attempts);
+
+ ast_test_suite_event_notify("TRANSFER_DIAL_FINAL",
+ "Channel: %s\r\n"
+ "Result: %s",
+ ast_channel_name(chan), res == 0 ? "Success" : "Failure");
+
+ return res ? -1 : 0;
}
static void copy_caller_data(struct ast_channel *dest, struct ast_channel *caller)
int cause;
/* Now we request a local channel to prepare to call the destination */
- chan = ast_request("Local", ast_channel_nativeformats(caller), caller, destination,
+ chan = ast_request("Local", ast_channel_nativeformats(caller), NULL, caller, destination,
&cause);
if (!chan) {
return NULL;
}
+ ast_channel_lock_both(chan, caller);
+
+ ast_channel_req_accountcodes(chan, caller, AST_CHANNEL_REQUESTOR_BRIDGE_PEER);
+
/* Who is transferring the call. */
pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", ast_channel_name(caller));
ast_bridge_set_transfer_variables(chan, ast_channel_name(caller), 1);
+ ast_channel_unlock(chan);
+ ast_channel_unlock(caller);
+
/* Before we actually dial out let's inherit appropriate information. */
copy_caller_data(chan, caller);
/* Inhibit the bridge before we do anything else. */
bridge = ast_bridge_channel_merge_inhibit(bridge_channel, +1);
+ ast_verb(3, "Channel %s: Started DTMF attended transfer.\n",
+ ast_channel_name(bridge_channel->chan));
+
if (strcmp(bridge->v_table->name, "basic")) {
- ast_log(LOG_ERROR, "Attended transfer attempted on unsupported bridge type '%s'.\n",
- bridge->v_table->name);
+ ast_log(LOG_ERROR, "Channel %s: Attended transfer attempted on unsupported bridge type '%s'.\n",
+ ast_channel_name(bridge_channel->chan), bridge->v_table->name);
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
return 0;
props = attended_transfer_properties_alloc(bridge_channel->chan,
attended_transfer ? attended_transfer->context : NULL);
if (!props) {
- ast_log(LOG_ERROR, "Unable to allocate control structure for performing attended transfer.\n");
+ ast_log(LOG_ERROR, "Channel %s: Unable to allocate control structure for performing attended transfer.\n",
+ ast_channel_name(bridge_channel->chan));
ast_bridge_merge_inhibit(bridge, -1);
ao2_ref(bridge, -1);
return 0;
props->transferee_bridge = bridge;
if (add_transferer_role(props->transferer, attended_transfer)) {
- ast_log(LOG_ERROR, "Unable to set transferrer bridge role.\n");
+ ast_log(LOG_ERROR, "Channel %s: Unable to set transferrer bridge role.\n",
+ ast_channel_name(bridge_channel->chan));
attended_transfer_properties_shutdown(props);
return 0;
}
/* Grab the extension to transfer to */
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), props->context)) {
- ast_log(LOG_WARNING, "Unable to acquire target extension for attended transfer.\n");
+ /*
+ * XXX The warning here really should be removed. While the
+ * message is accurate, this is a normal exit for when the user
+ * fails to specify a valid transfer target. e.g., The user
+ * hungup, didn't dial any digits, or dialed an invalid
+ * extension.
+ */
+ ast_log(LOG_WARNING, "Channel %s: Unable to acquire target extension for attended transfer.\n",
+ ast_channel_name(bridge_channel->chan));
ast_bridge_channel_write_unhold(bridge_channel);
attended_transfer_properties_shutdown(props);
return 0;
/* Fill the variable with the extension and context we want to call */
snprintf(destination, sizeof(destination), "%s@%s", props->exten, props->context);
- ast_debug(1, "Attended transfer to '%s'\n", destination);
+ ast_debug(1, "Channel %s: Attended transfer target '%s'\n",
+ ast_channel_name(bridge_channel->chan), destination);
/* Get a channel that is the destination we wish to call */
props->transfer_target = dial_transfer(bridge_channel->chan, destination);
if (!props->transfer_target) {
- ast_log(LOG_ERROR, "Unable to request outbound channel for attended transfer target.\n");
- ast_stream_and_wait(props->transferer, props->failsound, AST_DIGIT_NONE);
+ ast_log(LOG_ERROR, "Channel %s: Unable to request outbound channel for attended transfer target.\n",
+ ast_channel_name(bridge_channel->chan));
+ stream_failsound(props->transferer);
ast_bridge_channel_write_unhold(bridge_channel);
attended_transfer_properties_shutdown(props);
return 0;
/* Create a bridge to use to talk to the person we are calling */
props->target_bridge = ast_bridge_basic_new();
if (!props->target_bridge) {
- ast_log(LOG_ERROR, "Unable to create bridge for attended transfer target.\n");
- ast_stream_and_wait(props->transferer, props->failsound, AST_DIGIT_NONE);
+ ast_log(LOG_ERROR, "Channel %s: Unable to create bridge for attended transfer target.\n",
+ ast_channel_name(bridge_channel->chan));
+ stream_failsound(props->transferer);
ast_bridge_channel_write_unhold(bridge_channel);
ast_hangup(props->transfer_target);
props->transfer_target = NULL;
ast_bridge_merge_inhibit(props->target_bridge, +1);
if (attach_framehook(props, props->transfer_target)) {
- ast_log(LOG_ERROR, "Unable to attach framehook to transfer target.\n");
- ast_stream_and_wait(props->transferer, props->failsound, AST_DIGIT_NONE);
+ ast_log(LOG_ERROR, "Channel %s: Unable to attach framehook to transfer target.\n",
+ ast_channel_name(bridge_channel->chan));
+ stream_failsound(props->transferer);
ast_bridge_channel_write_unhold(bridge_channel);
ast_hangup(props->transfer_target);
props->transfer_target = NULL;
BRIDGE_BASIC_PERSONALITY_ATXFER, props);
if (ast_call(props->transfer_target, destination, 0)) {
- ast_log(LOG_ERROR, "Unable to place outbound call to transfer target.\n");
- ast_stream_and_wait(bridge_channel->chan, props->failsound, AST_DIGIT_NONE);
+ ast_log(LOG_ERROR, "Channel %s: Unable to place outbound call to transfer target.\n",
+ ast_channel_name(bridge_channel->chan));
+ stream_failsound(props->transferer);
ast_bridge_channel_write_unhold(bridge_channel);
ast_hangup(props->transfer_target);
props->transfer_target = NULL;
ast_channel_ref(props->transfer_target);
if (ast_bridge_impart(props->target_bridge, props->transfer_target, NULL, NULL,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
- ast_log(LOG_ERROR, "Unable to place transfer target into bridge.\n");
- ast_stream_and_wait(bridge_channel->chan, props->failsound, AST_DIGIT_NONE);
+ ast_log(LOG_ERROR, "Channel %s: Unable to place transfer target into bridge.\n",
+ ast_channel_name(bridge_channel->chan));
+ stream_failsound(props->transferer);
ast_bridge_channel_write_unhold(bridge_channel);
ast_hangup(props->transfer_target);
props->transfer_target = NULL;
}
if (ast_pthread_create_detached(&thread, NULL, attended_transfer_monitor_thread, props)) {
- ast_log(LOG_ERROR, "Unable to create monitoring thread for attended transfer.\n");
- ast_stream_and_wait(bridge_channel->chan, props->failsound, AST_DIGIT_NONE);
+ ast_log(LOG_ERROR, "Channel %s: Unable to create monitoring thread for attended transfer.\n",
+ ast_channel_name(bridge_channel->chan));
+ stream_failsound(props->transferer);
ast_bridge_channel_write_unhold(bridge_channel);
attended_transfer_properties_shutdown(props);
return 0;
return 0;
}
-static void blind_transfer_cb(struct ast_channel *new_channel, void *user_data,
+static void blind_transfer_cb(struct ast_channel *new_channel, struct transfer_channel_data *user_data_wrapper,
enum ast_transfer_type transfer_type)
{
- struct ast_channel *transferer_channel = user_data;
+ struct ast_channel *transferer_channel = user_data_wrapper->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_channel *bridge_channel, void *hook_pvt)
{
- char exten[AST_MAX_EXTENSION] = "";
+ char xfer_exten[AST_MAX_EXTENSION] = "";
struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
- const char *context;
+ const char *xfer_context;
char *goto_on_blindxfr;
+ ast_verb(3, "Channel %s: Started DTMF blind transfer.\n",
+ ast_channel_name(bridge_channel->chan));
+
ast_bridge_channel_write_hold(bridge_channel, NULL);
ast_channel_lock(bridge_channel->chan);
- context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+ xfer_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)) {
+ if (grab_transfer(bridge_channel->chan, xfer_exten, sizeof(xfer_exten), xfer_context)) {
ast_bridge_channel_write_unhold(bridge_channel);
return 0;
}
+ ast_debug(1, "Channel %s: Blind transfer target '%s@%s'\n",
+ ast_channel_name(bridge_channel->chan), xfer_exten, xfer_context);
+
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_bridge_set_after_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr);
+ const char *chan_context;
+ const char *chan_exten;
+ int chan_priority;
+
+ ast_debug(1, "Channel %s: After transfer, transferrer goes to %s\n",
+ ast_channel_name(bridge_channel->chan), goto_on_blindxfr);
+
+ ast_channel_lock(bridge_channel->chan);
+ chan_context = ast_strdupa(ast_channel_context(bridge_channel->chan));
+ chan_exten = ast_strdupa(ast_channel_exten(bridge_channel->chan));
+ chan_priority = ast_channel_priority(bridge_channel->chan);
+ ast_channel_unlock(bridge_channel->chan);
+ ast_bridge_set_after_go_on(bridge_channel->chan,
+ chan_context, chan_exten, chan_priority, 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)) {
+ if (ast_bridge_transfer_blind(0, bridge_channel->chan, xfer_exten, xfer_context,
+ blind_transfer_cb, bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS
+ && !ast_strlen_zero(goto_on_blindxfr)) {
ast_bridge_discard_after_goto(bridge_channel->chan);
}
bridge = bridge_alloc(sizeof(struct ast_bridge), &ast_bridge_basic_v_table);
bridge = bridge_base_init(bridge,
AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX
- | AST_BRIDGE_CAPABILITY_MULTIMIX, NORMAL_FLAGS);
+ | AST_BRIDGE_CAPABILITY_MULTIMIX, NORMAL_FLAGS, NULL, NULL, NULL);
bridge = bridge_basic_personality_alloc(bridge);
bridge = bridge_register(bridge);
return bridge;
ast_bridge_basic_v_table.pull = bridge_basic_pull;
ast_bridge_basic_v_table.destroy = bridge_basic_destroy;
- personality_normal_v_table = ast_bridge_base_v_table;
+ /*
+ * Personality vtables don't have the same rules as
+ * normal bridge vtables. These vtable functions are
+ * used as alterations to the ast_bridge_basic_v_table
+ * method functionality and are checked for NULL before
+ * calling.
+ */
personality_normal_v_table.name = "normal";
personality_normal_v_table.push = bridge_personality_normal_push;
- personality_atxfer_v_table = ast_bridge_base_v_table;
personality_atxfer_v_table.name = "attended transfer";
personality_atxfer_v_table.push = bridge_personality_atxfer_push;
personality_atxfer_v_table.pull = bridge_personality_atxfer_pull;
ast_bridge_features_register(AST_BRIDGE_BUILTIN_ATTENDEDTRANSFER, feature_attended_transfer, NULL);
ast_bridge_features_register(AST_BRIDGE_BUILTIN_BLINDTRANSFER, feature_blind_transfer, NULL);
}
-