Skip to content
Snippets Groups Projects
bridge_basic.c 119 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	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 {
    
    			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) {
    
    				relative_timeout.tv_sec = props->atxferloopdelay;
    
    			} else {
    				/* Implied TRANSFER_STATE_FLAG_TIMER_ATXFER_NO_ANSWER */
    
    				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;
    			}
    		}
    	}
    
    	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;
    
    	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);
    	}
    
    
    	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);
    
    
    	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,
    
    		.consume_cb = transfer_target_framehook_consume,
    
    	};
    
    	ao2_ref(props, +1);
    	target_interface.data = props;
    
    
    	props->target_framehook_id = ast_framehook_attach(channel, &target_interface);
    
    	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;
    
    	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);
    	}
    
    	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;
    
    	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;
    	}
    
    	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);
    
    	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 */
    
    		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));
    
    				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");
    
    
    }
    
    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,
    
    	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;
    
    	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);
    
    	/* 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);
    
    	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);
    
    	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);
    
    		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;
    
    	const char *xfer_context;
    
    	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);
    
    	}
    
    	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);
    
    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);
    }