Skip to content
Snippets Groups Projects
bridge_basic.c 119 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	/*! The name of the state. Used for debugging */
    	const char *state_name;
    	/*! Function used to enter a state */
    	int (*enter)(struct attended_transfer_properties *props);
    	/*!
    	 * Function used to exit a state
    	 * This is used both to determine what the next state
    	 * to transition to will be and to perform any cleanup
    	 * necessary before exiting the current state.
    	 */
    	enum attended_transfer_state (*exit)(struct attended_transfer_properties *props,
    			enum attended_transfer_stimulus stimulus);
    	/*! Flags associated with this state */
    	enum attended_transfer_state_flags flags;
    };
    
    static const struct attended_transfer_state_properties state_properties[] = {
    	[TRANSFER_CALLING_TARGET] = {
    		.state_name = "Calling Target",
    		.enter = calling_target_enter,
    		.exit = calling_target_exit,
    		.flags = TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER | TRANSFER_STATE_FLAG_TIMER_RESET,
    	},
    	[TRANSFER_HESITANT] = {
    		.state_name = "Hesitant",
    		.enter = hesitant_enter,
    		.exit = hesitant_exit,
    		.flags = TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER,
    	},
    	[TRANSFER_REBRIDGE] = {
    		.state_name = "Rebridge",
    		.enter = rebridge_enter,
    		.flags = TRANSFER_STATE_FLAG_TERMINAL,
    	},
    	[TRANSFER_RESUME] = {
    		.state_name = "Resume",
    		.enter = resume_enter,
    		.flags = TRANSFER_STATE_FLAG_TERMINAL,
    	},
    	[TRANSFER_THREEWAY] = {
    		.state_name = "Threeway",
    		.enter = threeway_enter,
    		.flags = TRANSFER_STATE_FLAG_TERMINAL,
    	},
    	[TRANSFER_CONSULTING] = {
    		.state_name = "Consulting",
    		.enter = consulting_enter,
    		.exit = consulting_exit,
    	},
    	[TRANSFER_DOUBLECHECKING] = {
    		.state_name = "Double Checking",
    		.enter = double_checking_enter,
    		.exit = double_checking_exit,
    	},
    	[TRANSFER_COMPLETE] = {
    		.state_name = "Complete",
    		.enter = complete_enter,
    		.flags = TRANSFER_STATE_FLAG_TERMINAL,
    	},
    	[TRANSFER_BLOND] = {
    		.state_name = "Blond",
    		.enter = blond_enter,
    		.flags = TRANSFER_STATE_FLAG_TERMINAL,
    	},
    	[TRANSFER_BLOND_NONFINAL] = {
    		.state_name = "Blond Non-Final",
    		.enter = blond_nonfinal_enter,
    		.exit = blond_nonfinal_exit,
    		.flags = TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER,
    	},
    	[TRANSFER_RECALLING] = {
    		.state_name = "Recalling",
    		.enter = recalling_enter,
    		.exit = recalling_exit,
    		.flags = TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER | TRANSFER_STATE_FLAG_TIMER_RESET,
    	},
    	[TRANSFER_WAIT_TO_RETRANSFER] = {
    		.state_name = "Wait to Retransfer",
    		.enter = wait_to_retransfer_enter,
    		.exit = wait_to_retransfer_exit,
    		.flags = TRANSFER_STATE_FLAG_TIMER_RESET | TRANSFER_STATE_FLAG_TIMER_LOOP_DELAY,
    	},
    	[TRANSFER_RETRANSFER] = {
    		.state_name = "Retransfer",
    		.enter = retransfer_enter,
    		.exit = retransfer_exit,
    		.flags = TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER | TRANSFER_STATE_FLAG_TIMER_RESET,
    	},
    	[TRANSFER_WAIT_TO_RECALL] = {
    		.state_name = "Wait to Recall",
    		.enter = wait_to_recall_enter,
    		.exit = wait_to_recall_exit,
    		.flags = TRANSFER_STATE_FLAG_TIMER_RESET | TRANSFER_STATE_FLAG_TIMER_LOOP_DELAY,
    	},
    	[TRANSFER_FAIL] = {
    		.state_name = "Fail",
    		.enter = fail_enter,
    		.flags = TRANSFER_STATE_FLAG_TERMINAL,
    	},
    };
    
    static int calling_target_enter(struct attended_transfer_properties *props)
    {
    
    	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,
    		enum attended_transfer_stimulus stimulus)
    {
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    
    		play_failsound(props->transferer);
    
    		publish_transfer_fail(props);
    		return TRANSFER_FAIL;
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_TRANSFERER_HANGUP:
    		bridge_unhold(props->transferee_bridge);
    		return props->atxferdropcall ? TRANSFER_BLOND : TRANSFER_BLOND_NONFINAL;
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    		return TRANSFER_CONSULTING;
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TIMEOUT:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    
    		play_failsound(props->transferer);
    
    		return TRANSFER_REBRIDGE;
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    		bridge_unhold(props->transferee_bridge);
    		return TRANSFER_THREEWAY;
    	case STIMULUS_DTMF_ATXFER_SWAP:
    		return TRANSFER_HESITANT;
    	case STIMULUS_NONE:
    	case STIMULUS_RECALL_TARGET_ANSWER:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int hesitant_enter(struct attended_transfer_properties *props)
    {
    
    	bridge_move(props->transferee_bridge, props->target_bridge, props->transferer, NULL);
    
    	unhold(props->transferer);
    	return 0;
    }
    
    static enum attended_transfer_state hesitant_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    
    		play_failsound(props->transferer);
    
    		publish_transfer_fail(props);
    		return TRANSFER_FAIL;
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_TRANSFERER_HANGUP:
    		return props->atxferdropcall ? TRANSFER_BLOND : TRANSFER_BLOND_NONFINAL;
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    		return TRANSFER_DOUBLECHECKING;
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TIMEOUT:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    
    		play_failsound(props->transferer);
    
    		return TRANSFER_RESUME;
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    		return TRANSFER_THREEWAY;
    	case STIMULUS_DTMF_ATXFER_SWAP:
    		hold(props->transferer);
    		return TRANSFER_CALLING_TARGET;
    	case STIMULUS_NONE:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    	case STIMULUS_RECALL_TARGET_ANSWER:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int rebridge_enter(struct attended_transfer_properties *props)
    {
    
    	bridge_move(props->transferee_bridge, props->target_bridge, props->transferer, NULL);
    
    	unhold(props->transferer);
    	return 0;
    }
    
    static int resume_enter(struct attended_transfer_properties *props)
    {
    	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, transferee_channel, target_channel);
    
    	ast_channel_cleanup(transferee_channel);
    	ast_channel_cleanup(target_channel);
    
    	return 0;
    }
    
    static int consulting_enter(struct attended_transfer_properties *props)
    {
    	return 0;
    }
    
    static enum attended_transfer_state consulting_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    		/* This is a one-of-a-kind event. The transferer and transfer target are talking in
    		 * one bridge, and the transferee has hung up in a separate bridge. In this case, we
    		 * will change the personality of the transfer target bridge back to normal, and play
    		 * a sound to the transferer to indicate the transferee is gone.
    		 */
    		bridge_basic_change_personality(props->target_bridge, BRIDGE_BASIC_PERSONALITY_NORMAL, NULL);
    
    		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
    		 * monitor thread exits
    		 */
    		ao2_ref(props->target_bridge, -1);
    		props->target_bridge = NULL;
    		return TRANSFER_FAIL;
    	case STIMULUS_TRANSFERER_HANGUP:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    		/* We know the transferer is in the target_bridge, so take the other bridge off hold */
    		bridge_unhold(props->transferee_bridge);
    		return TRANSFER_COMPLETE;
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    
    	case STIMULUS_DTMF_ATXFER_ABORT:
    
    		play_failsound(props->transferer);
    
    		return TRANSFER_REBRIDGE;
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    		bridge_unhold(props->transferee_bridge);
    		return TRANSFER_THREEWAY;
    	case STIMULUS_DTMF_ATXFER_SWAP:
    		hold(props->transferer);
    		bridge_move(props->transferee_bridge, props->target_bridge, props->transferer, NULL);
    		unhold(props->transferer);
    		return TRANSFER_DOUBLECHECKING;
    	case STIMULUS_NONE:
    	case STIMULUS_TIMEOUT:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    	case STIMULUS_RECALL_TARGET_ANSWER:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int double_checking_enter(struct attended_transfer_properties *props)
    {
    	return 0;
    }
    
    static enum attended_transfer_state double_checking_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    
    		play_failsound(props->transferer);
    
    		publish_transfer_fail(props);
    		return TRANSFER_FAIL;
    	case STIMULUS_TRANSFERER_HANGUP:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    		/* We know the transferer is in the transferee, so take the other bridge off hold */
    		bridge_unhold(props->target_bridge);
    		return TRANSFER_COMPLETE;
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    
    		play_failsound(props->transferer);
    
    		return TRANSFER_RESUME;
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    		bridge_unhold(props->target_bridge);
    		return TRANSFER_THREEWAY;
    	case STIMULUS_DTMF_ATXFER_SWAP:
    		hold(props->transferer);
    		bridge_move(props->target_bridge, props->transferee_bridge, props->transferer, NULL);
    		unhold(props->transferer);
    		return TRANSFER_CONSULTING;
    	case STIMULUS_NONE:
    	case STIMULUS_TIMEOUT:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    	case STIMULUS_RECALL_TARGET_ANSWER:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    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, 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, transferee_channel, target_channel);
    
    	ast_channel_cleanup(transferee_channel);
    	ast_channel_cleanup(target_channel);
    
    	return 0;
    }
    
    static int blond_nonfinal_enter(struct attended_transfer_properties *props)
    {
    	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 res;
    }
    
    static enum attended_transfer_state blond_nonfinal_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    		return TRANSFER_FAIL;
    	case STIMULUS_RECALL_TARGET_ANSWER:
    		return TRANSFER_RESUME;
    	case STIMULUS_TIMEOUT:
    		ast_softhangup(props->recall_target, AST_SOFTHANGUP_EXPLICIT);
    
    		/* 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:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    	case STIMULUS_DTMF_ATXFER_SWAP:
    	case STIMULUS_TRANSFERER_HANGUP:
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    /*!
     * \brief Dial callback when attempting to recall the original transferer channel
     *
     * This is how we can monitor if the recall target has answered or has hung up.
     * If one of the two is detected, then an appropriate stimulus is sent to the
     * attended transfer monitor thread.
     */
    static void recall_callback(struct ast_dial *dial)
    {
    	struct attended_transfer_properties *props = ast_dial_get_user_data(dial);
    
    	switch (ast_dial_state(dial)) {
    	default:
    	case AST_DIAL_RESULT_INVALID:
    	case AST_DIAL_RESULT_FAILED:
    	case AST_DIAL_RESULT_TIMEOUT:
    	case AST_DIAL_RESULT_HANGUP:
    	case AST_DIAL_RESULT_UNANSWERED:
    		/* Failure cases */
    		stimulate_attended_transfer(props, STIMULUS_RECALL_TARGET_HANGUP);
    		break;
    	case AST_DIAL_RESULT_RINGING:
    	case AST_DIAL_RESULT_PROGRESS:
    	case AST_DIAL_RESULT_PROCEEDING:
    	case AST_DIAL_RESULT_TRYING:
    		/* Don't care about these cases */
    		break;
    	case AST_DIAL_RESULT_ANSWERED:
    		/* We struck gold! */
    		props->recall_target = ast_dial_answered_steal(dial);
    		stimulate_attended_transfer(props, STIMULUS_RECALL_TARGET_ANSWER);
    		break;
    	}
    }
    
    
    /*!
     * \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)
    {
    	struct ast_callid *callid;
    
    	callid = ast_read_threadstorage_callid();
    	if (callid) {
    		ast_channel_callid_set(recall, callid);
    		ast_callid_unref(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_DEFAULT), ao2_cleanup);
    
    	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
    	 * and then call out to it. When recalling the transferer, though, we
    	 * have to use the dialing API because the channel is not local.
    	 */
    	props->dial = ast_dial_create();
    	if (!props->dial) {
    		return -1;
    	}
    
    
    	if (ast_dial_append(props->dial, props->transferer_type, props->transferer_addr, NULL)) {
    
    		return -1;
    	}
    
    	if (ast_dial_prerun(props->dial, NULL, cap)) {
    		return -1;
    	}
    
    
    	/*
    	 * 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_dial_run(props->dial, NULL, 1) == AST_DIAL_RESULT_FAILED) {
    		ao2_ref(props, -1);
    		return -1;
    	}
    
    	bridge_ringing(props->transferee_bridge);
    	return 0;
    }
    
    static enum attended_transfer_state recalling_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	/* No matter what the outcome was, we need to kill off the dial */
    	ast_dial_join(props->dial);
    	ast_dial_destroy(props->dial);
    	props->dial = NULL;
    	/* This reference is the one we incremented for the dial state callback (recall_callback) to use */
    	ao2_ref(props, -1);
    
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    		return TRANSFER_FAIL;
    	case STIMULUS_TIMEOUT:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    		++props->retry_attempts;
    		if (props->retry_attempts >= props->atxfercallbackretries) {
    			return TRANSFER_FAIL;
    		}
    		if (props->atxferloopdelay) {
    			return TRANSFER_WAIT_TO_RETRANSFER;
    		}
    		return TRANSFER_RETRANSFER;
    	case STIMULUS_RECALL_TARGET_ANSWER:
    		/* Setting this datastore up will allow the transferer to have all of his
    		 * call features set up automatically when the bridge changes back to a
    		 * normal personality
    		 */
    		ast_bridge_features_ds_set(props->recall_target, &props->transferer_features);
    		ast_channel_ref(props->recall_target);
    
    		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;
    	case STIMULUS_NONE:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    	case STIMULUS_DTMF_ATXFER_SWAP:
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	case STIMULUS_TRANSFERER_HANGUP:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int wait_to_retransfer_enter(struct attended_transfer_properties *props)
    {
    	bridge_hold(props->transferee_bridge);
    	return 0;
    }
    
    static enum attended_transfer_state wait_to_retransfer_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	bridge_unhold(props->transferee_bridge);
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    		return TRANSFER_FAIL;
    	case STIMULUS_TIMEOUT:
    		return TRANSFER_RETRANSFER;
    	case STIMULUS_NONE:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    	case STIMULUS_DTMF_ATXFER_SWAP:
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	case STIMULUS_TRANSFERER_HANGUP:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    	case STIMULUS_RECALL_TARGET_ANSWER:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int attach_framehook(struct attended_transfer_properties *props, struct ast_channel *channel);
    
    static int retransfer_enter(struct attended_transfer_properties *props)
    {
    
    	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;
    
    	if (!cap) {
    		return -1;
    	}
    
    	snprintf(destination, sizeof(destination), "%s@%s", props->exten, props->context);
    
    
    	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, NULL, destination, &cause);
    
    	if (!props->recall_target) {
    		ast_log(LOG_ERROR, "Unable to request outbound channel for recall target\n");
    		return -1;
    	}
    
    	if (attach_framehook(props, props->recall_target)) {
    		ast_log(LOG_ERROR, "Unable to attach framehook to recall target\n");
    		ast_hangup(props->recall_target);
    		props->recall_target = NULL;
    		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);
    		props->recall_target = NULL;
    		return -1;
    	}
    
    	ast_channel_ref(props->recall_target);
    
    	if (ast_bridge_impart(props->transferee_bridge, props->recall_target, NULL, NULL,
    		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;
    	}
    
    	return 0;
    }
    
    static enum attended_transfer_state retransfer_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    		return TRANSFER_FAIL;
    	case STIMULUS_TIMEOUT:
    		ast_softhangup(props->recall_target, AST_SOFTHANGUP_EXPLICIT);
    	case STIMULUS_RECALL_TARGET_HANGUP:
    		props->recall_target = ast_channel_unref(props->recall_target);
    		if (props->atxferloopdelay) {
    			return TRANSFER_WAIT_TO_RECALL;
    		}
    		return TRANSFER_RECALLING;
    	case STIMULUS_RECALL_TARGET_ANSWER:
    		return TRANSFER_RESUME;
    	case STIMULUS_NONE:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    	case STIMULUS_DTMF_ATXFER_SWAP:
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	case STIMULUS_TRANSFERER_HANGUP:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int wait_to_recall_enter(struct attended_transfer_properties *props)
    {
    	bridge_hold(props->transferee_bridge);
    	return 0;
    }
    
    static enum attended_transfer_state wait_to_recall_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	bridge_unhold(props->transferee_bridge);
    	switch (stimulus) {
    	case STIMULUS_TRANSFEREE_HANGUP:
    		return TRANSFER_FAIL;
    	case STIMULUS_TIMEOUT:
    		return TRANSFER_RECALLING;
    	case STIMULUS_NONE:
    	case STIMULUS_DTMF_ATXFER_ABORT:
    	case STIMULUS_DTMF_ATXFER_COMPLETE:
    	case STIMULUS_DTMF_ATXFER_THREEWAY:
    	case STIMULUS_DTMF_ATXFER_SWAP:
    	case STIMULUS_TRANSFER_TARGET_HANGUP:
    	case STIMULUS_TRANSFER_TARGET_ANSWER:
    	case STIMULUS_TRANSFERER_HANGUP:
    	case STIMULUS_RECALL_TARGET_HANGUP:
    	case STIMULUS_RECALL_TARGET_ANSWER:
    	default:
    		ast_log(LOG_WARNING, "Unexpected stimulus '%s' received in attended transfer state '%s'\n",
    				stimulus_strs[stimulus], state_properties[props->state].state_name);
    		return props->state;
    	}
    }
    
    static int fail_enter(struct attended_transfer_properties *props)
    {
    	if (props->transferee_bridge) {
    
    		ast_bridge_destroy(props->transferee_bridge, 0);
    
    		props->transferee_bridge = NULL;
    	}
    	return 0;
    }
    
    /*!
     * \brief DTMF hook when transferer presses abort sequence.
     *
     * Sends a stimulus to the attended transfer monitor thread that the abort sequence has been pressed
     */
    
    static int atxfer_abort(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct attended_transfer_properties *props = hook_pvt;
    
    	ast_debug(1, "Transferer on attended transfer %p pressed abort sequence\n", props);
    	stimulate_attended_transfer(props, STIMULUS_DTMF_ATXFER_ABORT);
    	return 0;
    }
    
    /*!
     * \brief DTMF hook when transferer presses complete sequence.
     *
     * Sends a stimulus to the attended transfer monitor thread that the complete sequence has been pressed
     */
    
    static int atxfer_complete(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct attended_transfer_properties *props = hook_pvt;
    
    	ast_debug(1, "Transferer on attended transfer %p pressed complete sequence\n", props);
    	stimulate_attended_transfer(props, STIMULUS_DTMF_ATXFER_COMPLETE);
    	return 0;
    }
    
    /*!
     * \brief DTMF hook when transferer presses threeway sequence.
     *
     * Sends a stimulus to the attended transfer monitor thread that the threeway sequence has been pressed
     */
    
    static int atxfer_threeway(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct attended_transfer_properties *props = hook_pvt;
    
    	ast_debug(1, "Transferer on attended transfer %p pressed threeway sequence\n", props);
    	stimulate_attended_transfer(props, STIMULUS_DTMF_ATXFER_THREEWAY);
    	return 0;
    }
    
    /*!
     * \brief DTMF hook when transferer presses swap sequence.
     *
     * Sends a stimulus to the attended transfer monitor thread that the swap sequence has been pressed
     */
    
    static int atxfer_swap(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct attended_transfer_properties *props = hook_pvt;
    
    	ast_debug(1, "Transferer on attended transfer %p pressed swap sequence\n", props);
    	stimulate_attended_transfer(props, STIMULUS_DTMF_ATXFER_SWAP);
    	return 0;
    }
    
    /*!
     * \brief Hangup hook for transferer channel.
     *
     * Sends a stimulus to the attended transfer monitor thread that the transferer has hung up.
     */
    
    static int atxfer_transferer_hangup(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
    
    {
    	struct attended_transfer_properties *props = hook_pvt;
    
    	ast_debug(1, "Transferer on attended transfer %p hung up\n", props);
    	stimulate_attended_transfer(props, STIMULUS_TRANSFERER_HANGUP);
    	return 0;
    }
    
    /*!
     * \brief Frame hook for transfer target channel
     *
     * This is used to determine if the transfer target or recall target has answered
     * the outgoing call.
     *
     * When an answer is detected, a stimulus is sent to the attended transfer monitor
     * thread to indicate that the transfer target or recall target has answered.
     *
     * \param chan The channel the framehook is attached to.
     * \param frame The frame being read or written.
     * \param event What is being done with the frame.
     * \param data The attended transfer properties.
     */
    static struct ast_frame *transfer_target_framehook_cb(struct ast_channel *chan,
    		struct ast_frame *frame, enum ast_framehook_event event, void *data)
    {
    	struct attended_transfer_properties *props = data;
    
    	if (event == AST_FRAMEHOOK_EVENT_READ &&
    			frame && frame->frametype == AST_FRAME_CONTROL &&
    
    			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) {
    			stimulate_attended_transfer(props, STIMULUS_TRANSFER_TARGET_ANSWER);
    		} else {
    			stimulate_attended_transfer(props, STIMULUS_RECALL_TARGET_ANSWER);
    		}
    		ast_framehook_detach(chan, props->target_framehook_id);
    		props->target_framehook_id = -1;
    	}
    
    	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;
    	ao2_cleanup(props);
    }
    
    static int bridge_personality_atxfer_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
    {
    	const char *abort_dtmf;
    	const char *complete_dtmf;
    	const char *threeway_dtmf;
    	const char *swap_dtmf;
    	struct bridge_basic_personality *personality = self->personality;
    
    
    	if (!ast_channel_has_role(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME)) {
    
    	abort_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "abort");
    	complete_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "complete");
    	threeway_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "threeway");
    	swap_dtmf = ast_channel_get_role_option(bridge_channel->chan, AST_TRANSFERER_ROLE_NAME, "swap");
    
    
    	if (!ast_strlen_zero(abort_dtmf) && ast_bridge_dtmf_hook(bridge_channel->features,
    			abort_dtmf, atxfer_abort, personality->details[personality->current].pvt, NULL,
    			AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE | AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		return -1;
    	}
    	if (!ast_strlen_zero(complete_dtmf) && ast_bridge_dtmf_hook(bridge_channel->features,
    			complete_dtmf, atxfer_complete, personality->details[personality->current].pvt, NULL,
    			AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE | AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		return -1;
    	}
    	if (!ast_strlen_zero(threeway_dtmf) && ast_bridge_dtmf_hook(bridge_channel->features,
    			threeway_dtmf, atxfer_threeway, personality->details[personality->current].pvt, NULL,
    			AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE | AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		return -1;
    	}
    	if (!ast_strlen_zero(swap_dtmf) && ast_bridge_dtmf_hook(bridge_channel->features,
    			swap_dtmf, atxfer_swap, personality->details[personality->current].pvt, NULL,
    			AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE | AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		return -1;
    	}
    	if (ast_bridge_hangup_hook(bridge_channel->features, atxfer_transferer_hangup,
    			personality->details[personality->current].pvt, NULL,
    			AST_BRIDGE_HOOK_REMOVE_ON_PERSONALITY_CHANGE | AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
    		return -1;
    	}
    
    	return 0;
    }
    
    static void transfer_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct attended_transfer_properties *props)
    {
    
    	if (self->num_channels > 1 || bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) {
    
    		return;
    	}
    
    	if (self->num_channels == 1) {
    
    		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);
    		ast_channel_unlock(props->transferer);
    
    		if (!transferer_bridge_channel) {
    			return;
    		}
    
    
    		not_transferer = AST_LIST_FIRST(&self->channels) != transferer_bridge_channel;
    		ao2_ref(transferer_bridge_channel, -1);
    		if (not_transferer) {
    
    			return;
    		}
    	}
    
    	/* Reaching this point means that either
    	 * 1) The bridge has no channels in it
    	 * 2) The bridge has one channel, and it's the transferer
    	 * In either case, it indicates that the non-transferer parties
    	 * are no longer in the bridge.
    	 */
    	if (self == props->transferee_bridge) {
    		stimulate_attended_transfer(props, STIMULUS_TRANSFEREE_HANGUP);
    	} else {
    		stimulate_attended_transfer(props, STIMULUS_TRANSFER_TARGET_HANGUP);
    	}
    }
    
    static void recall_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct attended_transfer_properties *props)
    {
    	if (self == props->target_bridge) {
    		/* Once we're in the recall superstate, we no longer care about this bridge */
    		return;
    	}
    
    	if (bridge_channel->chan == props->recall_target) {
    		stimulate_attended_transfer(props, STIMULUS_RECALL_TARGET_HANGUP);
    		return;
    	}
    
    	if (self->num_channels == 0) {
    		/* Empty bridge means all transferees are gone for sure */
    		stimulate_attended_transfer(props, STIMULUS_TRANSFEREE_HANGUP);
    		return;
    	}
    
    	if (self->num_channels == 1) {
    
    		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
    			 */
    			return;
    		}
    
    		ast_channel_lock(props->recall_target);
    		target_bridge_channel = ast_channel_get_bridge_channel(props->recall_target);
    		ast_channel_unlock(props->recall_target);
    
    
    		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 void bridge_personality_atxfer_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel)
    {
    	struct bridge_basic_personality *personality = self->personality;
    	struct attended_transfer_properties *props = personality->details[personality->current].pvt;
    
    	switch (props->superstate) {
    	case SUPERSTATE_TRANSFER:
    		transfer_pull(self, bridge_channel, props);
    		break;
    	case SUPERSTATE_RECALL:
    		recall_pull(self, bridge_channel, props);
    		break;
    	}
    }
    
    static enum attended_transfer_stimulus wait_for_stimulus(struct attended_transfer_properties *props)
    {
    
    	enum attended_transfer_stimulus stimulus;