Skip to content
Snippets Groups Projects
bridge_basic.c 119 KiB
Newer Older
  • Learn to ignore specific revisions
  • 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
    	 * 2) Transferer is in either bridge.
    	 * 3) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_COMPLETE:
    	 * 1) TRANSFER_CONSULTING: transferer hangs up or presses the DTMF complete sequence.
    	 * 2) TRANSFER_DOUBLECHECKING: transferer hangs up or presses the DTMF complete sequence.
    	 *
    	 * State operation:
    	 * The transfer target bridge is merged into the transferee bridge. The transferer
    	 * channel is kicked out of the bridges as part of the merge.
    	 *
    	 * State operations:
    	 * 1) Merge the transfer target bridge into the transferee bridge,
    	 * excluding the transferer channel from the merge.
    	 * 2) Publish a stasis transfer message.
    	 *
    	 * Exit operations:
    	 * This is a terminal state, so there are no exit operations.
    	 */
    	TRANSFER_COMPLETE,
    	/*!
    	 * \brief Blond state
    	 *
    	 * This is a terminal state where a transferer has completed an attended transfer prior
    	 * to the transfer target answering. This state is only entered if atxferdropcall
    	 * is set to 'yes'. This is considered to be a successful attended transfer.
    	 *
    	 * Superstate: Transfer
    	 *
    	 * Preconditions:
    	 * 1) Transfer target is RINGING.
    	 * 2) Transferer is in either bridge.
    	 * 3) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_BLOND:
    	 * 1) TRANSFER_CALLING_TARGET: Transferer hangs up or presses the DTMF complete sequence.
    	 *    atxferdropcall is set to 'yes'.
    	 * 2) TRANSFER_HESITANT: Transferer hangs up or presses the DTMF complete sequence.
    	 *    atxferdropcall is set to 'yes'.
    	 *
    	 * State operations:
    	 * The transfer target bridge is merged into the transferee bridge. The transferer
    	 * channel is kicked out of the bridges as part of the merge. A stasis transfer
    	 * publication is sent indicating a successful transfer.
    	 *
    	 * Transitions from TRANSFER_BLOND:
    	 * None
    	 */
    	TRANSFER_BLOND,
    	/*!
    	 * \brief Blond non-final state
    	 *
    	 * This state is very similar to the TRANSFER_BLOND state, except that
    	 * this state is entered when atxferdropcall is set to 'no'. This is the
    	 * initial state of the Recall superstate, so state operations mainly involve
    	 * moving to the Recall superstate. This means that the transfer target, that
    	 * is currently ringing is now known as the recall target.
    	 *
    	 * Superstate: Recall
    	 *
    	 * Preconditions:
    	 * 1) Recall target is RINGING.
    	 * 2) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_BLOND_NONFINAL:
    	 * 1) TRANSFER_CALLING_TARGET: Transferer hangs up or presses the DTMF complete sequence.
    	 *    atxferdropcall is set to 'no'.
    	 * 2) TRANSFER_HESITANT: Transferer hangs up or presses the DTMF complete sequence.
    	 *    atxferdropcall is set to 'no'.
    	 *
    	 * State operation:
    	 * The superstate of the attended transfer is changed from Transfer to Recall.
    	 * The transfer target bridge is merged into the transferee bridge. The transferer
    	 * channel is kicked out of the bridges as part of the merge.
    	 *
    	 * Transitions from TRANSFER_BLOND_NONFINAL:
    	 * 1) TRANSFER_FAIL: Transferee hangs up
    	 * 2) TRANSFER_RESUME: Recall target answers
    	 * 3) TRANSFER_RECALLING: Recall target hangs up or time expires.
    	 */
    	TRANSFER_BLOND_NONFINAL,
    	/*!
    	 * \brief Recalling state
    	 *
    	 * This state is entered if the recall target from the TRANSFER_BLOND_NONFINAL
    	 * or TRANSFER_RETRANSFER states hangs up or does not answer. The goal of this
    	 * state is to call back the original transferer in an attempt to recover the
    	 * original call.
    	 *
    	 * Superstate: Recall
    	 *
    	 * Preconditions:
    	 * 1) Recall target is down.
    	 * 2) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_RECALLING:
    	 * 1) TRANSFER_BLOND_NONFINAL: Recall target hangs up or time expires.
    	 * 2) TRANSFER_RETRANSFER: Recall target hangs up or time expires.
    	 *    atxferloopdelay is non-zero.
    	 * 3) TRANSFER_WAIT_TO_RECALL: Time expires.
    	 *
    	 * State operation:
    	 * The original transferer becomes the recall target and is called using the Dialing API.
    	 * Ringing is indicated to the transferee.
    	 *
    	 * Transitions from TRANSFER_RECALLING:
    	 * 1) TRANSFER_FAIL:
    	 *    a) Transferee hangs up.
    	 *    b) Recall target hangs up or time expires, and number of recall attempts exceeds atxfercallbackretries
    	 * 2) TRANSFER_WAIT_TO_RETRANSFER: Recall target hangs up or time expires.
    	 *    atxferloopdelay is non-zero.
    	 * 3) TRANSFER_RETRANSFER: Recall target hangs up or time expires.
    	 *    atxferloopdelay is zero.
    	 * 4) TRANSFER_RESUME: Recall target answers.
    	 */
    	TRANSFER_RECALLING,
    	/*!
    	 * \brief Wait to Retransfer state
    	 *
    	 * This state is used simply to give a bit of breathing room between attempting
    	 * to call back the original transferer and attempting to call back the original
    	 * transfer target. The transferee hears music on hold during this state as an
    	 * auditory clue that no one is currently being dialed.
    	 *
    	 * Superstate: Recall
    	 *
    	 * Preconditions:
    	 * 1) Recall target is down.
    	 * 2) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_WAIT_TO_RETRANSFER:
    	 * 1) TRANSFER_RECALLING: Recall target hangs up or time expires.
    	 *    atxferloopdelay is non-zero.
    	 *
    	 * State operation:
    	 * The transferee is placed on hold.
    	 *
    	 * Transitions from TRANSFER_WAIT_TO_RETRANSFER:
    	 * 1) TRANSFER_FAIL: Transferee hangs up.
    	 * 2) TRANSFER_RETRANSFER: Time expires.
    	 */
    	TRANSFER_WAIT_TO_RETRANSFER,
    	/*!
    	 * \brief Retransfer state
    	 *
    	 * This state is used in order to attempt to call back the original
    	 * transfer target channel from the transfer. The transferee hears
    	 * ringing during this state as an auditory cue that a party is being
    	 * dialed.
    	 *
    	 * Superstate: Recall
    	 *
    	 * Preconditions:
    	 * 1) Recall target is down.
    	 * 2) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_RETRANSFER:
    	 * 1) TRANSFER_RECALLING: Recall target hangs up or time expires.
    	 *    atxferloopdelay is zero.
    	 * 2) TRANSFER_WAIT_TO_RETRANSFER: Time expires.
    	 *
    	 * State operation:
    	 * The original transfer target is requested and is set as the recall target.
    	 * The recall target is called and placed into the transferee bridge.
    	 *
    	 * Transitions from TRANSFER_RETRANSFER:
    	 * 1) TRANSFER_FAIL: Transferee hangs up.
    	 * 2) TRANSFER_WAIT_TO_RECALL: Recall target hangs up or time expires.
    	 *    atxferloopdelay is non-zero.
    	 * 3) TRANSFER_RECALLING: Recall target hangs up or time expires.
    	 *    atxferloopdelay is zero.
    	 */
    	TRANSFER_RETRANSFER,
    	/*!
    	 * \brief Wait to recall state
    	 *
    	 * This state is used simply to give a bit of breathing room between attempting
    	 * to call back the original transfer target and attempting to call back the
    	 * original transferer. The transferee hears music on hold during this state as an
    	 * auditory clue that no one is currently being dialed.
    	 *
    	 * Superstate: Recall
    	 *
    	 * Preconditions:
    	 * 1) Recall target is down.
    	 * 2) Transferee is off hold.
    	 *
    	 * Transitions to TRANSFER_WAIT_TO_RECALL:
    	 * 1) TRANSFER_RETRANSFER: Recall target hangs up or time expires.
    	 *    atxferloopdelay is non-zero.
    	 *
    	 * State operation:
    	 * Transferee is placed on hold.
    	 *
    	 * Transitions from TRANSFER_WAIT_TO_RECALL:
    	 * 1) TRANSFER_FAIL: Transferee hangs up
    	 * 2) TRANSFER_RECALLING: Time expires
    	 */
    	TRANSFER_WAIT_TO_RECALL,
    	/*!
    	 * \brief Fail state
    	 *
    	 * This state indicates that something occurred during the transfer that
    	 * makes a graceful completion impossible. The most common stimulus for this
    	 * state is when the transferee hangs up.
    	 *
    	 * Superstate: Transfer and Recall
    	 *
    	 * Preconditions:
    	 * None
    	 *
    	 * Transitions to TRANSFER_FAIL:
    	 * 1) TRANSFER_CALLING_TARGET: Transferee hangs up.
    	 * 2) TRANSFER_HESITANT: Transferee hangs up.
    	 * 3) TRANSFER_DOUBLECHECKING: Transferee hangs up.
    	 * 4) TRANSFER_BLOND_NONFINAL: Transferee hangs up.
    	 * 5) TRANSFER_RECALLING:
    	 *    a) Transferee hangs up.
    	 *    b) Recall target hangs up or time expires, and number of
    	 *       recall attempts exceeds atxfercallbackretries.
    	 * 6) TRANSFER_WAIT_TO_RETRANSFER: Transferee hangs up.
    	 * 7) TRANSFER_RETRANSFER: Transferee hangs up.
    	 * 8) TRANSFER_WAIT_TO_RECALL: Transferee hangs up.
    	 *
    	 * State operation:
    	 * A transfer stasis publication is made indicating a failed transfer.
    	 * The transferee bridge is destroyed.
    	 *
    	 * Transitions from TRANSFER_FAIL:
    	 * None.
    	 */
    	TRANSFER_FAIL,
    };
    
    /*!
     * \brief Stimuli that can cause transfer state changes
     */
    enum attended_transfer_stimulus {
    	/*! No stimulus. This literally can never happen. */
    	STIMULUS_NONE,
    	/*! All of the transferee channels have been hung up. */
    	STIMULUS_TRANSFEREE_HANGUP,
    	/*! The transferer has hung up. */
    	STIMULUS_TRANSFERER_HANGUP,
    	/*! The transfer target channel has hung up. */
    	STIMULUS_TRANSFER_TARGET_HANGUP,
    	/*! The transfer target channel has answered. */
    	STIMULUS_TRANSFER_TARGET_ANSWER,
    	/*! The recall target channel has hung up. */
    	STIMULUS_RECALL_TARGET_HANGUP,
    	/*! The recall target channel has answered. */
    	STIMULUS_RECALL_TARGET_ANSWER,
    	/*! The current state's timer has expired. */
    	STIMULUS_TIMEOUT,
    	/*! The transferer pressed the abort DTMF sequence. */
    	STIMULUS_DTMF_ATXFER_ABORT,
    	/*! The transferer pressed the complete DTMF sequence. */
    	STIMULUS_DTMF_ATXFER_COMPLETE,
    	/*! The transferer pressed the three-way DTMF sequence. */
    	STIMULUS_DTMF_ATXFER_THREEWAY,
    	/*! The transferer pressed the swap DTMF sequence. */
    	STIMULUS_DTMF_ATXFER_SWAP,
    };
    
    /*!
     * \brief String representations of the various stimuli
     *
     * Used for debugging purposes
     */
    const char *stimulus_strs[] = {
    	[STIMULUS_NONE] = "None",
    	[STIMULUS_TRANSFEREE_HANGUP] = "Transferee Hangup",
    	[STIMULUS_TRANSFERER_HANGUP] = "Transferer Hangup",
    	[STIMULUS_TRANSFER_TARGET_HANGUP] = "Transfer Target Hangup",
    	[STIMULUS_TRANSFER_TARGET_ANSWER] = "Transfer Target Answer",
    	[STIMULUS_RECALL_TARGET_HANGUP] = "Recall Target Hangup",
    	[STIMULUS_RECALL_TARGET_ANSWER] = "Recall Target Answer",
    	[STIMULUS_TIMEOUT] = "Timeout",
    	[STIMULUS_DTMF_ATXFER_ABORT] = "DTMF Abort",
    	[STIMULUS_DTMF_ATXFER_COMPLETE] = "DTMF Complete",
    	[STIMULUS_DTMF_ATXFER_THREEWAY] = "DTMF Threeway",
    	[STIMULUS_DTMF_ATXFER_SWAP] = "DTMF Swap",
    };
    
    struct stimulus_list {
    	enum attended_transfer_stimulus stimulus;
    	AST_LIST_ENTRY(stimulus_list) next;
    };
    
    /*!
     * \brief Collection of data related to an attended transfer attempt
     */
    struct attended_transfer_properties {
    	AST_DECLARE_STRING_FIELDS (
    		/*! Extension of transfer target */
    		AST_STRING_FIELD(exten);
    		/*! Context of transfer target */
    		AST_STRING_FIELD(context);
    		/*! Sound to play when transfer completes */
    		AST_STRING_FIELD(xfersound);
    		/*! The channel technology of the transferer channel */
    		AST_STRING_FIELD(transferer_type);
    		/*! The transferer channel address */
    		AST_STRING_FIELD(transferer_addr);
    	);
    	/*! Condition used to synchronize when stimuli are reported to the monitor thread */
    	ast_cond_t cond;
    	/*! The bridge where the transferee resides. This bridge is also the bridge that
    	 * survives a successful attended transfer.
    	 */
    	struct ast_bridge *transferee_bridge;
    	/*! The bridge used to place an outbound call to the transfer target. This
    	 * bridge is merged with the transferee_bridge on a successful transfer.
    	 */
    	struct ast_bridge *target_bridge;
    	/*! The party that performs the attended transfer. */
    	struct ast_channel *transferer;
    	/*! The local channel dialed to reach the transfer target. */
    	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.  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 timeval start;
    	AST_LIST_HEAD_NOLOCK(,stimulus_list) stimulus_queue;
    	/*! The current state of the attended transfer */
    	enum attended_transfer_state state;
    	/*! The current superstate of the attended transfer */
    	enum attended_transfer_superstate superstate;
    	/*! Configured atxferdropcall from features.conf */
    	int atxferdropcall;
    	/*! Configured atxfercallbackretries from features.conf */
    	int atxfercallbackretries;
    	/*! Configured atxferloopdelay from features.conf */
    	int atxferloopdelay;
    	/*! Configured atxfernoanswertimeout from features.conf */
    	int atxfernoanswertimeout;
    	/*! Count of the number of times that recalls have been attempted */
    	int retry_attempts;
    	/*! Framehook ID for outbounc call to transfer target or recall target */
    	int target_framehook_id;
    	/*! Dial structure used when recalling transferer channel */
    	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)
    {
    	struct attended_transfer_properties *props = obj;
    
    	ast_debug(1, "Destroy attended transfer properties %p\n", props);
    
    	ao2_cleanup(props->target_bridge);
    	ao2_cleanup(props->transferee_bridge);
    	/* Use ast_channel_cleanup() instead of ast_channel_unref() for channels since they may be NULL */
    	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);
    }
    
    /*!
     * \internal
     * \brief Determine the transfer context to use.
     * \since 12.0.0
     *
     * \param transferer Channel initiating the transfer.
     * \param context User supplied context if available.  May be NULL.
     *
     * \return The context to use for the transfer.
     */
    static const char *get_transfer_context(struct ast_channel *transferer, const char *context)
    {
    	if (!ast_strlen_zero(context)) {
    		return context;
    	}
    	context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
    	if (!ast_strlen_zero(context)) {
    		return context;
    	}
    	context = ast_channel_macrocontext(transferer);
    	if (!ast_strlen_zero(context)) {
    		return context;
    	}
    	context = ast_channel_context(transferer);
    	if (!ast_strlen_zero(context)) {
    		return context;
    	}
    	return "default";
    }
    
    /*!
     * \brief Allocate and initialize attended transfer properties
     *
     * \param transferer The channel performing the attended transfer
     * \param context Suggestion for what context the transfer target extension can be found in
     *
     * \retval NULL Failure to allocate or initialize
     * \retval non-NULL Newly allocated properties
     */
    static struct attended_transfer_properties *attended_transfer_properties_alloc(
    
    	struct ast_channel *transferer, const char *context)
    
    {
    	struct attended_transfer_properties *props;
    	char *tech;
    	char *addr;
    	char *serial;
    
    	struct ast_features_xfer_config *xfer_cfg;
    
    	struct ast_flags *transferer_features;
    
    	props = ao2_alloc(sizeof(*props), attended_transfer_properties_destructor);
    
    	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);
    
    	ast_channel_lock(props->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));
    
    		ao2_ref(props, -1);
    		return NULL;
    	}
    	transferer_features = ast_bridge_features_ds_get(props->transferer);
    	if (transferer_features) {
    		props->transferer_features = *transferer_features;
    	}
    	props->atxferdropcall = xfer_cfg->atxferdropcall;
    	props->atxfercallbackretries = xfer_cfg->atxfercallbackretries;
    	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, xfersound, xfer_cfg->xfersound);
    
    	/*
    	 * 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_unlock(props->transferer);
    		ao2_ref(props, -1);
    
    		return NULL;
    	}
    	*addr++ = '\0';
    	serial = strrchr(addr, '-');
    	if (serial) {
    		*serial = '\0';
    	}
    	ast_string_field_set(props, transferer_type, tech);
    	ast_string_field_set(props, transferer_addr, addr);
    
    	ast_channel_unlock(props->transferer);
    
    	ast_debug(1, "Allocated attended transfer properties %p for transfer from %s\n",
    			props, ast_channel_name(props->transferer));
    	return props;
    }
    
    /*!
     * \brief Free backlog of stimuli in the queue
     */
    static void clear_stimulus_queue(struct attended_transfer_properties *props)
    {
    	struct stimulus_list *list;
    	SCOPED_AO2LOCK(lock, props);
    
    	while ((list = AST_LIST_REMOVE_HEAD(&props->stimulus_queue, next))) {
    		ast_free(list);
    	}
    }
    
    /*!
     * \brief Initiate shutdown of attended transfer properties
     *
     * Calling this indicates that the attended transfer properties are no longer needed
     * because the transfer operation has concluded.
     */
    static void attended_transfer_properties_shutdown(struct attended_transfer_properties *props)
    {
    	ast_debug(1, "Shutting down attended transfer %p\n", props);
    
    	if (props->transferee_bridge) {
    		bridge_basic_change_personality(props->transferee_bridge,
    				BRIDGE_BASIC_PERSONALITY_NORMAL, NULL);
    
    		ast_bridge_merge_inhibit(props->transferee_bridge, -1);
    
    		ast_bridge_destroy(props->target_bridge, 0);
    
    		props->target_bridge = NULL;
    	}
    
    	if (props->transferer) {
    
    		ast_channel_remove_bridge_role(props->transferer, AST_TRANSFERER_ROLE_NAME);
    
    	}
    
    	clear_stimulus_queue(props);
    
    	ao2_cleanup(props);
    }
    
    static void stimulate_attended_transfer(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus)
    {
    	struct stimulus_list *list;
    
    	list = ast_calloc(1, sizeof(*list));
    	if (!list) {
    		ast_log(LOG_ERROR, "Unable to push event to attended transfer queue. Expect transfer to fail\n");
    		return;
    	}
    
    	list->stimulus = stimulus;
    	ao2_lock(props);
    	AST_LIST_INSERT_TAIL(&props->stimulus_queue, list, next);
    	ast_cond_signal(&props->cond);
    	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,
    		struct ast_channel *transferee_channel, struct ast_channel *target_channel)
    
    	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);
    
    	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,
    		struct ast_channel *transferee_channel, struct ast_channel *target_channel)
    
    	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);
    
    	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);
    
    }
    
    /*!
     * \brief Send a stasis publication for a failed attended transfer
     */
    static void publish_transfer_fail(struct attended_transfer_properties *props)
    {
    
    	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);
    
    	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);
    
    }
    
    /*!
     * \brief Helper method to play a sound on a channel in a bridge
     *
     * \param chan The channel to play the sound to
     * \param sound The sound to play
     */
    static void play_sound(struct ast_channel *chan, const char *sound)
    {
    
    	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) {
    		ast_bridge_channel_queue_playfile(bridge_channel, NULL, sound, NULL);
    		ao2_ref(bridge_channel, -1);
    
    /*!
     * \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);
    	}
    }
    
    
    /*!
     * \brief Helper method to place a channel in a bridge on hold
     */
    static void hold(struct ast_channel *chan)
    {
    
    	struct ast_bridge_channel *bridge_channel;
    
    	ast_channel_lock(chan);
    	bridge_channel = ast_channel_get_bridge_channel(chan);
    	ast_channel_unlock(chan);
    
    		ast_bridge_channel_write_hold(bridge_channel, NULL);
    
    	}
    }
    
    /*!
     * \brief Helper method to take a channel in a bridge off hold
     */
    static void unhold(struct ast_channel *chan)
    {
    
    	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);
    
    
    	if (bridge_channel) {
    		ast_bridge_channel_write_unhold(bridge_channel);
    		ao2_ref(bridge_channel, -1);
    	}
    
    }
    
    /*!
     * \brief Helper method to send a ringing indication to a channel in a bridge
     */
    static void ringing(struct ast_channel *chan)
    {
    
    	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) {
    		ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_RINGING, NULL, 0);
    		ao2_ref(bridge_channel, -1);
    	}
    
    }
    
    /*!
     * \brief Helper method to send a ringing indication to all channels in a bridge
     */
    static void bridge_ringing(struct ast_bridge *bridge)
    {
    	struct ast_frame ringing = {
    		.frametype = AST_FRAME_CONTROL,
    		.subclass.integer = AST_CONTROL_RINGING,
    	};
    
    	ast_bridge_queue_everyone_else(bridge, NULL, &ringing);
    }
    
    /*!
     * \brief Helper method to send a hold frame to all channels in a bridge
     */
    static void bridge_hold(struct ast_bridge *bridge)
    {
    	struct ast_frame hold = {
    		.frametype = AST_FRAME_CONTROL,
    		.subclass.integer = AST_CONTROL_HOLD,
    	};
    
    	ast_bridge_queue_everyone_else(bridge, NULL, &hold);
    }
    
    /*!
     * \brief Helper method to send an unhold frame to all channels in a bridge
     */
    static void bridge_unhold(struct ast_bridge *bridge)
    {
    	struct ast_frame unhold = {
    		.frametype = AST_FRAME_CONTROL,
    		.subclass.integer = AST_CONTROL_UNHOLD,
    	};
    
    	ast_bridge_queue_everyone_else(bridge, NULL, &unhold);
    }
    
    /*!
    
     * \brief Wrapper for \ref bridge_do_move
    
    static void bridge_move(struct ast_bridge *dest, struct ast_bridge *src, struct ast_channel *channel, struct ast_channel *swap)
    
    	struct ast_bridge_channel *bridge_channel;
    
    
    	ast_bridge_lock_both(src, dest);
    
    	ast_channel_lock(channel);
    	bridge_channel = ast_channel_get_bridge_channel(channel);
    	ast_channel_unlock(channel);
    
    
    	if (bridge_channel) {
    		ao2_lock(bridge_channel);
    		bridge_channel->swap = swap;
    		ao2_unlock(bridge_channel);
    
    		bridge_do_move(dest, bridge_channel, 1, 0);
    	}
    
    
    	ast_bridge_unlock(dest);
    	ast_bridge_unlock(src);
    
    
     * \brief Wrapper for \ref bridge_do_merge
    
     */
    static void bridge_merge(struct ast_bridge *dest, struct ast_bridge *src, struct ast_channel **kick_channels, unsigned int num_channels)
    {
    	struct ast_bridge_channel **kick_bridge_channels = num_channels ?
    		ast_alloca(num_channels * sizeof(*kick_bridge_channels)) : NULL;
    	int i;
    	int num_bridge_channels = 0;
    
    	ast_bridge_lock_both(dest, src);
    
    	for (i = 0; i < num_channels; ++i) {
    		struct ast_bridge_channel *kick_bridge_channel;
    
    
    		kick_bridge_channel = bridge_find_channel(src, kick_channels[i]);
    
    		if (!kick_bridge_channel) {
    
    			kick_bridge_channel = bridge_find_channel(dest, kick_channels[i]);
    
    		}
    
    		/* It's possible (and fine) for the bridge channel to be NULL at this point if the
    		 * channel has hung up already. If that happens, we can just remove it from the list
    		 * of bridge channels to kick from the bridge
    		 */
    		if (!kick_bridge_channel) {
    			continue;
    		}
    
    		kick_bridge_channels[num_bridge_channels++] = kick_bridge_channel;
    	}
    
    
    	bridge_do_merge(dest, src, kick_bridge_channels, num_bridge_channels, 0);
    
    	ast_bridge_unlock(dest);
    	ast_bridge_unlock(src);
    
    
    /*!
     * \brief Flags that indicate properties of attended transfer states
     */
    enum attended_transfer_state_flags {
    	/*! This state requires that the timer be reset when entering the state */
    	TRANSFER_STATE_FLAG_TIMER_RESET = (1 << 0),
    	/*! This state's timer uses atxferloopdelay */
    	TRANSFER_STATE_FLAG_TIMER_LOOP_DELAY = (1 << 1),
    	/*! This state's timer uses atxfernoanswertimeout */
    	TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER = (1 << 2),
    	/*! This state has a time limit associated with it */
    	TRANSFER_STATE_FLAG_TIMED = (TRANSFER_STATE_FLAG_TIMER_RESET |
    			TRANSFER_STATE_FLAG_TIMER_LOOP_DELAY | TRANSFER_STATE_FLAG_ATXFER_NO_ANSWER),
    	/*! This state does not transition to any other states */
    	TRANSFER_STATE_FLAG_TERMINAL = (1 << 3),
    };
    
    static int calling_target_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state calling_target_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int hesitant_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state hesitant_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int rebridge_enter(struct attended_transfer_properties *props);
    
    static int resume_enter(struct attended_transfer_properties *props);
    
    static int threeway_enter(struct attended_transfer_properties *props);
    
    static int consulting_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state consulting_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int double_checking_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state double_checking_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int complete_enter(struct attended_transfer_properties *props);
    
    static int blond_enter(struct attended_transfer_properties *props);
    
    static int blond_nonfinal_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state blond_nonfinal_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int recalling_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state recalling_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int wait_to_retransfer_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state wait_to_retransfer_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int retransfer_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state retransfer_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int wait_to_recall_enter(struct attended_transfer_properties *props);
    static enum attended_transfer_state wait_to_recall_exit(struct attended_transfer_properties *props,
    		enum attended_transfer_stimulus stimulus);
    
    static int fail_enter(struct attended_transfer_properties *props);
    
    /*!
     * \brief Properties of an attended transfer state
     */
    struct attended_transfer_state_properties {