Newer
Older
Richard Mudgett
committed
struct stimulus_list *list;
SCOPED_MUTEX(lock, ao2_object_get_lockaddr(props));
while (!(list = AST_LIST_REMOVE_HEAD(&props->stimulus_queue, next))) {
if (!(state_properties[props->state].flags & TRANSFER_STATE_FLAG_TIMED)) {
ast_cond_wait(&props->cond, lock);
} else {
Mark Michelson
committed
struct timeval relative_timeout = { 0, };
struct timeval absolute_timeout;
struct timespec timeout_arg;
if (state_properties[props->state].flags & TRANSFER_STATE_FLAG_TIMER_RESET) {
props->start = ast_tvnow();
}
if (state_properties[props->state].flags & TRANSFER_STATE_FLAG_TIMER_LOOP_DELAY) {
Mark Michelson
committed
relative_timeout.tv_sec = props->atxferloopdelay;
} else {
/* Implied TRANSFER_STATE_FLAG_TIMER_ATXFER_NO_ANSWER */
Mark Michelson
committed
relative_timeout.tv_sec = props->atxfernoanswertimeout;
}
absolute_timeout = ast_tvadd(props->start, relative_timeout);
timeout_arg.tv_sec = absolute_timeout.tv_sec;
timeout_arg.tv_nsec = absolute_timeout.tv_usec * 1000;
if (ast_cond_timedwait(&props->cond, lock, &timeout_arg) == ETIMEDOUT) {
return STIMULUS_TIMEOUT;
}
}
}
Richard Mudgett
committed
stimulus = list->stimulus;
ast_free(list);
return stimulus;
}
/*!
* \brief The main loop for the attended transfer monitor thread.
*
* This loop runs continuously until the attended transfer reaches
* a terminal state. Stimuli for changes in the attended transfer
* state are handled in this thread so that all factors in an
* attended transfer can be handled in an orderly fashion.
*
* \param data The attended transfer properties
*/
static void *attended_transfer_monitor_thread(void *data)
{
struct attended_transfer_properties *props = data;
Richard Mudgett
committed
struct 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);
}
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
for (;;) {
enum attended_transfer_stimulus stimulus;
ast_debug(1, "About to enter state %s for attended transfer %p\n", state_properties[props->state].state_name, props);
if (state_properties[props->state].enter &&
state_properties[props->state].enter(props)) {
ast_log(LOG_ERROR, "State %s enter function returned an error for attended transfer %p\n",
state_properties[props->state].state_name, props);
break;
}
if (state_properties[props->state].flags & TRANSFER_STATE_FLAG_TERMINAL) {
ast_debug(1, "State %s is a terminal state. Ending attended transfer %p\n",
state_properties[props->state].state_name, props);
break;
}
stimulus = wait_for_stimulus(props);
ast_debug(1, "Received stimulus %s on attended transfer %p\n", stimulus_strs[stimulus], props);
ast_assert(state_properties[props->state].exit != NULL);
props->state = state_properties[props->state].exit(props, stimulus);
ast_debug(1, "Told to enter state %s exit on attended transfer %p\n", state_properties[props->state].state_name, props);
}
attended_transfer_properties_shutdown(props);
Richard Mudgett
committed
if (callid) {
ast_callid_unref(callid);
ast_callid_threadassoc_remove();
}
return NULL;
}
static int attach_framehook(struct attended_transfer_properties *props, struct ast_channel *channel)
{
struct ast_framehook_interface target_interface = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = transfer_target_framehook_cb,
.destroy_cb = transfer_target_framehook_destroy_cb,
Joshua Colp
committed
.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;
}
return 0;
}
static int add_transferer_role(struct ast_channel *chan, struct ast_bridge_features_attended_transfer *attended_transfer)
{
const char *atxfer_abort;
const char *atxfer_threeway;
const char *atxfer_complete;
const char *atxfer_swap;
Richard Mudgett
committed
struct ast_features_xfer_config *xfer_cfg;
SCOPED_CHANNELLOCK(lock, chan);
xfer_cfg = ast_get_chan_features_xfer_config(chan);
if (!xfer_cfg) {
return -1;
}
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));
atxfer_swap = ast_strdupa(S_OR(attended_transfer->swap, xfer_cfg->atxferswap));
} else {
atxfer_abort = ast_strdupa(xfer_cfg->atxferabort);
atxfer_threeway = ast_strdupa(xfer_cfg->atxferthreeway);
atxfer_complete = ast_strdupa(xfer_cfg->atxfercomplete);
atxfer_swap = ast_strdupa(xfer_cfg->atxferswap);
}
Richard Mudgett
committed
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) ||
ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "complete", atxfer_complete) ||
ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "threeway", atxfer_threeway) ||
ast_channel_set_bridge_role_option(chan, AST_TRANSFERER_ROLE_NAME, "swap", atxfer_swap);
}
/*!
* \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;
int attempts = 0;
int max_attempts;
Richard Mudgett
committed
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, "Channel %s: Unable to get transfer configuration\n",
ast_channel_name(chan));
ast_channel_unlock(chan);
return -1;
}
Mark Michelson
committed
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);
Richard Mudgett
committed
ao2_ref(xfer_cfg, -1);
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 */
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;
/* Dialed extension is valid. */
res = 0;
} 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)
{
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 *destination)
{
struct ast_channel *chan;
int cause;
/* Now we request a local channel to prepare to call the 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);
return chan;
}
/*!
* \brief Internal built in feature for attended transfers
*
* This hook will set up a thread for monitoring the progress of
* an attended transfer. For more information about attended transfer
* progress, see documentation on the transfer state machine.
*
* \param bridge_channel The channel that pressed the attended transfer DTMF sequence
* \param hook_pvt Structure with further information about the attended transfer
*/
static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
struct ast_bridge_features_attended_transfer *attended_transfer = hook_pvt;
struct attended_transfer_properties *props;
struct ast_bridge *bridge;
char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
char exten[AST_MAX_EXTENSION] = "";
pthread_t thread;
/* 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, "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;
}
/* Was the bridge inhibited before we inhibited it? */
if (1 < bridge->inhibit_merge) {
/*
* The peer likely initiated attended transfer at the same time
* and we lost the race.
*/
ast_verb(3, "Channel %s: Bridge '%s' does not permit merging at this time.\n",
ast_channel_name(bridge_channel->chan), bridge->uniqueid);
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);
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, "Channel %s: Unable to set transferrer bridge role.\n",
ast_channel_name(bridge_channel->chan));
attended_transfer_properties_shutdown(props);
return 0;
}
ast_bridge_channel_write_hold(bridge_channel, NULL);
/* Grab the extension to transfer to */
if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), props->context)) {
/*
* 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;
}
ast_string_field_set(props, exten, exten);
/* 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, "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, "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, "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;
attended_transfer_properties_shutdown(props);
return 0;
}
ast_bridge_merge_inhibit(props->target_bridge, +1);
if (attach_framehook(props, props->transfer_target)) {
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;
attended_transfer_properties_shutdown(props);
return 0;
}
bridge_basic_change_personality(props->target_bridge,
BRIDGE_BASIC_PERSONALITY_ATXFER, props);
bridge_basic_change_personality(bridge,
BRIDGE_BASIC_PERSONALITY_ATXFER, props);
if (ast_call(props->transfer_target, destination, 0)) {
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;
attended_transfer_properties_shutdown(props);
return 0;
}
/* We increase the refcount of the transfer target because ast_bridge_impart() will
* steal the reference we already have. We need to keep a reference, so the only
* choice is to give it a bump
*/
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, "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;
attended_transfer_properties_shutdown(props);
return 0;
}
if (ast_pthread_create_detached(&thread, NULL, attended_transfer_monitor_thread, props)) {
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;
}
/* Once the monitoring thread has been created, it is responsible for destroying all
* of the necessary components.
*/
return 0;
}
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_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 xfer_exten[AST_MAX_EXTENSION] = "";
struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
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);
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, 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)) {
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, 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);
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
}
return 0;
}
struct ast_bridge_methods ast_bridge_basic_v_table;
struct ast_bridge_methods personality_normal_v_table;
struct ast_bridge_methods personality_atxfer_v_table;
static void bridge_basic_change_personality(struct ast_bridge *bridge,
enum bridge_basic_personality_type type, void *user_data)
{
struct bridge_basic_personality *personality = bridge->personality;
SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
remove_hooks_on_personality_change(bridge);
ao2_cleanup(personality->details[personality->current].pvt);
personality->details[personality->current].pvt = NULL;
ast_clear_flag(&bridge->feature_flags, AST_FLAGS_ALL);
personality->current = type;
if (user_data) {
ao2_ref(user_data, +1);
}
personality->details[personality->current].pvt = user_data;
ast_set_flag(&bridge->feature_flags, personality->details[personality->current].bridge_flags);
if (personality->details[personality->current].on_personality_change) {
personality->details[personality->current].on_personality_change(bridge);
}
}
static void personality_destructor(void *obj)
{
struct bridge_basic_personality *personality = obj;
int i;
for (i = 0; i < BRIDGE_BASIC_PERSONALITY_END; ++i) {
ao2_cleanup(personality->details[i].pvt);
}
}
static void on_personality_change_normal(struct ast_bridge *bridge)
{
struct ast_bridge_channel *iter;
AST_LIST_TRAVERSE(&bridge->channels, iter, entry) {
if (add_normal_hooks(bridge, iter)) {
ast_log(LOG_WARNING, "Unable to set up bridge hooks for channel %s. Features may not work properly\n",
ast_channel_name(iter->chan));
}
}
}
static void init_details(struct personality_details *details,
enum bridge_basic_personality_type type)
{
switch (type) {
case BRIDGE_BASIC_PERSONALITY_NORMAL:
details->v_table = &personality_normal_v_table;
details->bridge_flags = NORMAL_FLAGS;
details->on_personality_change = on_personality_change_normal;
break;
case BRIDGE_BASIC_PERSONALITY_ATXFER:
details->v_table = &personality_atxfer_v_table;
details->bridge_flags = TRANSFER_FLAGS;
break;
default:
ast_log(LOG_WARNING, "Asked to initialize unexpected basic bridge personality type.\n");
break;
}
}
static struct ast_bridge *bridge_basic_personality_alloc(struct ast_bridge *bridge)
{
struct bridge_basic_personality *personality;
int i;
if (!bridge) {
return NULL;
}
personality = ao2_alloc(sizeof(*personality), personality_destructor);
if (!personality) {
ao2_ref(bridge, -1);
return NULL;
}
for (i = 0; i < BRIDGE_BASIC_PERSONALITY_END; ++i) {
init_details(&personality->details[i], i);
}
personality->current = BRIDGE_BASIC_PERSONALITY_NORMAL;
bridge->personality = personality;
return bridge;
}
struct ast_bridge *ast_bridge_basic_new(void)
{
struct ast_bridge *bridge;
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, NULL, NULL, NULL);
bridge = bridge_basic_personality_alloc(bridge);
bridge = bridge_register(bridge);
return bridge;
}
void ast_bridge_basic_set_flags(struct ast_bridge *bridge, unsigned int flags)
{
SCOPED_LOCK(lock, bridge, ast_bridge_lock, ast_bridge_unlock);
struct bridge_basic_personality *personality = bridge->personality;
personality->details[personality->current].bridge_flags |= flags;
ast_set_flag(&bridge->feature_flags, flags);
}
void ast_bridging_init_basic(void)
{
/* Setup bridge basic subclass v_table. */
ast_bridge_basic_v_table = ast_bridge_base_v_table;
ast_bridge_basic_v_table.name = "basic";
ast_bridge_basic_v_table.push = bridge_basic_push;
ast_bridge_basic_v_table.pull = bridge_basic_pull;
ast_bridge_basic_v_table.destroy = bridge_basic_destroy;
/*
* 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.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);
}