From a05aeff312b233122090eed6a42cee984b6c1e27 Mon Sep 17 00:00:00 2001 From: Richard Mudgett Date: Tue, 18 Jan 2011 18:17:01 +0000 Subject: Merged revisions 302174 via svnmerge from https://origsvn.digium.com/svn/asterisk/branches/1.8 ................ r302174 | rmudgett | 2011-01-18 12:11:43 -0600 (Tue, 18 Jan 2011) | 102 lines Merged revisions 302173 via svnmerge from https://origsvn.digium.com/svn/asterisk/branches/1.6.2 ................ r302173 | rmudgett | 2011-01-18 12:07:15 -0600 (Tue, 18 Jan 2011) | 95 lines Merged revisions 302172 via svnmerge from https://origsvn.digium.com/svn/asterisk/branches/1.4 ........ r302172 | rmudgett | 2011-01-18 12:04:36 -0600 (Tue, 18 Jan 2011) | 88 lines Issues with DTMF triggered attended transfers. Issue #17999 1) A calls B. B answers. 2) B using DTMF dial *2 (code in features.conf for attended transfer). 3) A hears MOH. B dial number C 4) C ringing. A hears MOH. 5) B hangup. A still hears MOH. C ringing. 6) A hangup. C still ringing until "atxfernoanswertimeout" expires. For v1.4 C will ring forever until C answers the dead line. (Issue #17096) Problem: When A and B hangup, C is still ringing. Issue #18395 SIP call limit of B is 1 1. A call B, B answered 2. B *2(atxfer) call C 3. B hangup, C ringing 4. Timeout waiting for C to answer 5. Recall to B fails because B has reached its call limit. Because B reached its call limit, it cannot do anything until the transfer it started completes. Issue #17273 Same scenario as issue 18395 but party B is an FXS port. Party B cannot do anything until the transfer it started completes. If B goes back off hook before C answers, B hears ringback instead of the expected dialtone. ********** Note for the issue #17273 and #18395 fix: DTMF attended transfer works within the channel bridge. Unfortunately, when either party A or B in the channel bridge hangs up, that channel is not completely hung up until the transfer completes. This is a real problem depending upon the channel technology involved. For chan_dahdi, the channel is crippled until the hangup is complete. Either the channel is not useable (analog) or the protocol disconnect messages are held up (PRI/BRI/SS7) and the media is not released. For chan_sip, a call limit of one is going to block that endpoint from any further calls until the hangup is complete. For party A this is a minor problem. The party A channel will only be in this condition while party B is dialing and when party B and C are conferring. The conversation between party B and C is expected to be a short one. Party B is either asking a question of party C or announcing party A. Also party A does not have much incentive to hangup at this point. For party B this can be a major problem during a blonde transfer. (A blonde transfer is our term for an attended transfer that is converted into a blind transfer. :)) Party B could be the operator. When party B hangs up, he assumes that he is out of the original call entirely. The party B channel will be in this condition while party C is ringing, while attempting to recall party B, and while waiting between call attempts. WARNING: The ATXFER_NULL_TECH conditional is a hack to fix the problem. It will replace the party B channel technology with a NULL channel driver to complete hanging up the party B channel technology. The consequences of this code is that the 'h' extension will not be able to access any channel technology specific information like SIP statistics for the call. ATXFER_NULL_TECH is not defined by default. ********** (closes issue #17999) Reported by: iskatel Tested by: rmudgett JIRA SWP-2246 (closes issue #17096) Reported by: gelo Tested by: rmudgett JIRA SWP-1192 (closes issue #18395) Reported by: shihchuan Tested by: rmudgett (closes issue #17273) Reported by: grecco Tested by: rmudgett Review: https://reviewboard.asterisk.org/r/1047/ ........ ................ ................ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@302178 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- main/features.c | 1014 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 661 insertions(+), 353 deletions(-) (limited to 'main/features.c') diff --git a/main/features.c b/main/features.c index 32e739aac..0cf42166d 100644 --- a/main/features.c +++ b/main/features.c @@ -59,6 +59,56 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cel.h" #include "asterisk/test.h" +/* + * Party A - transferee + * Party B - transferer + * Party C - target of transfer + * + * DTMF attended transfer works within the channel bridge. + * Unfortunately, when either party A or B in the channel bridge + * hangs up, that channel is not completely hung up until the + * transfer completes. This is a real problem depending upon + * the channel technology involved. + * + * For chan_dahdi, the channel is crippled until the hangup is + * complete. Either the channel is not useable (analog) or the + * protocol disconnect messages are held up (PRI/BRI/SS7) and + * the media is not released. + * + * For chan_sip, a call limit of one is going to block that + * endpoint from any further calls until the hangup is complete. + * + * For party A this is a minor problem. The party A channel + * will only be in this condition while party B is dialing and + * when party B and C are conferring. The conversation between + * party B and C is expected to be a short one. Party B is + * either asking a question of party C or announcing party A. + * Also party A does not have much incentive to hangup at this + * point. + * + * For party B this can be a major problem during a blonde + * transfer. (A blonde transfer is our term for an attended + * transfer that is converted into a blind transfer. :)) Party + * B could be the operator. When party B hangs up, he assumes + * that he is out of the original call entirely. The party B + * channel will be in this condition while party C is ringing, + * while attempting to recall party B, and while waiting between + * call attempts. + * + * WARNING: + * The ATXFER_NULL_TECH conditional is a hack to fix the + * problem. It will replace the party B channel technology with + * a NULL channel driver. The consequences of this code is that + * the 'h' extension will not be able to access any channel + * technology specific information like SIP statistics for the + * call. + * + * Uncomment the ATXFER_NULL_TECH define below to replace the + * party B channel technology in the channel bridge to complete + * hanging up the channel technology. + */ +//#define ATXFER_NULL_TECH 1 + /*** DOCUMENTATION @@ -412,6 +462,124 @@ struct ast_dial_features { int is_caller; }; +#if defined(ATXFER_NULL_TECH) +static struct ast_frame *null_read(struct ast_channel *chan) +{ + /* Hangup channel. */ + return NULL; +} + +static struct ast_frame *null_exception(struct ast_channel *chan) +{ + /* Hangup channel. */ + return NULL; +} + +static int null_write(struct ast_channel *chan, struct ast_frame *frame) +{ + /* Hangup channel. */ + return -1; +} + +static int null_fixup(struct ast_channel *oldchan, struct ast_channel *newchan) +{ + /* No problem fixing up the channel. */ + return 0; +} + +static int null_hangup(struct ast_channel *chan) +{ + chan->tech_pvt = NULL; + return 0; +} + +static const struct ast_channel_tech null_tech = { + .type = "NULL", + .description = "NULL channel driver for atxfer", + .capabilities = -1, + .read = null_read, + .exception = null_exception, + .write = null_write, + .fixup = null_fixup, + .hangup = null_hangup, +}; +#endif /* defined(ATXFER_NULL_TECH) */ + +#if defined(ATXFER_NULL_TECH) +/*! + * \internal + * \brief Set the channel technology to the NULL technology. + * + * \param chan Channel to change technology. + * + * \return Nothing + */ +static void set_null_chan_tech(struct ast_channel *chan) +{ + int idx; + + ast_channel_lock(chan); + + /* Hangup the channel's physical side */ + if (chan->tech->hangup) { + chan->tech->hangup(chan); + } + if (chan->tech_pvt) { + ast_log(LOG_WARNING, "Channel '%s' may not have been hung up properly\n", + chan->name); + ast_free(chan->tech_pvt); + chan->tech_pvt = NULL; + } + + /* Install the NULL technology and wake up anyone waiting on it. */ + chan->tech = &null_tech; + for (idx = 0; idx < AST_MAX_FDS; ++idx) { + switch (idx) { + case AST_ALERT_FD: + case AST_TIMING_FD: + case AST_GENERATOR_FD: + /* Don't clear these fd's. */ + break; + default: + ast_channel_set_fd(chan, idx, -1); + break; + } + } + ast_queue_frame(chan, &ast_null_frame); + + ast_channel_unlock(chan); +} +#endif /* defined(ATXFER_NULL_TECH) */ + +#if defined(ATXFER_NULL_TECH) +/*! + * \internal + * \brief Set the channel name to something unique. + * + * \param chan Channel to change name. + * + * \return Nothing + */ +static void set_new_chan_name(struct ast_channel *chan) +{ + static int seq_num_last; + int seq_num; + int len; + char *chan_name; + char dummy[1]; + + /* Create the new channel name string. */ + ast_channel_lock(chan); + seq_num = ast_atomic_fetchadd_int(&seq_num_last, +1); + len = snprintf(dummy, sizeof(dummy), "%s", chan->name, seq_num) + 1; + chan_name = alloca(len); + snprintf(chan_name, len, "%s", chan->name, seq_num); + ast_channel_unlock(chan); + + ast_change_name(chan, chan_name); +} +#endif /* defined(ATXFER_NULL_TECH) */ + static void *dial_features_duplicate(void *data) { struct ast_dial_features *df = data, *df_copy; @@ -573,7 +741,10 @@ static void check_goto_on_transfer(struct ast_channel *chan) } } -static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate, const char *language); +static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, + const char *caller_name, struct ast_channel *requestor, + struct ast_channel *transferee, const char *type, int format, void *data, + int timeout, int *outstate, const char *language); /*! * \brief bridge the call @@ -1800,6 +1971,35 @@ static int check_compat(struct ast_channel *c, struct ast_channel *newchan) return 0; } +/*! + * \internal + * \brief Builtin attended transfer failed cleanup. + * \since 1.10 + * + * \param transferee Party A in the transfer. + * \param transferer Party B in the transfer. + * \param connected_line Saved connected line info about party A. + * + * \note The connected_line data is freed. + * + * \return Nothing + */ +static void atxfer_fail_cleanup(struct ast_channel *transferee, struct ast_channel *transferer, struct ast_party_connected_line *connected_line) +{ + finishup(transferee); + + /* + * Restore party B connected line info about party A. + * + * Party B was the caller to party C and is the last known mode + * for party B. + */ + if (ast_channel_connected_line_macro(transferee, transferer, connected_line, 1, 0)) { + ast_channel_update_connected_line(transferer, connected_line, NULL); + } + ast_party_connected_line_free(connected_line); +} + /*! * \brief Attended transfer * \param chan transfered user @@ -1807,9 +2007,9 @@ static int check_compat(struct ast_channel *c, struct ast_channel *newchan) * \param config * \param code * \param sense feature options - * + * * \param data - * Get extension to transfer to, if you cannot generate channel (or find extension) + * Get extension to transfer to, if you cannot generate channel (or find extension) * return to host channel. After called channel answered wait for hangup of transferer, * bridge call between transfer peer (taking them off hold) to attended transfer channel. * @@ -1817,8 +2017,8 @@ static int check_compat(struct ast_channel *c, struct ast_channel *newchan) */ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, const char *code, int sense, void *data) { - struct ast_channel *transferer; - struct ast_channel *transferee; + struct ast_channel *transferer;/* Party B */ + struct ast_channel *transferee;/* Party A */ const char *transferer_real_context; char xferto[256] = ""; int res; @@ -1827,51 +2027,50 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st struct ast_channel *xferchan; struct ast_bridge_thread_obj *tobj; struct ast_bridge_config bconfig; - struct ast_frame *f; int l; struct ast_party_connected_line connected_line; struct ast_datastore *features_datastore; struct ast_dial_features *dialfeatures = NULL; struct ast_parkinglot *parkinglot; + char *transferer_tech; + char *transferer_name; + char *transferer_name_orig; + char *dash; ast_debug(1, "Executing Attended Transfer %s, %s (sense=%d) \n", chan->name, peer->name, sense); set_peers(&transferer, &transferee, peer, chan, sense); transferer_real_context = real_ctx(transferer, transferee); - /* Start autoservice on chan while we talk to the originator */ + + /* Start autoservice on transferee while we talk to the transferer */ ast_autoservice_start(transferee); - ast_autoservice_ignore(transferee, AST_FRAME_DTMF_END); ast_indicate(transferee, AST_CONTROL_HOLD); - + /* Transfer */ res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY); if (res < 0) { finishup(transferee); - return res; + return -1; } if (res > 0) /* If they've typed a digit already, handle it */ xferto[0] = (char) res; /* this is specific of atxfer */ res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout); - if (res < 0) { /* hangup, would be 0 for invalid and 1 for valid */ + if (res < 0) { /* hangup or error, (would be 0 for invalid and 1 for valid) */ finishup(transferee); - return res; + return -1; } + l = strlen(xferto); if (res == 0) { - ast_log(LOG_WARNING, "Did not read data.\n"); - finishup(transferee); - if (ast_stream_and_wait(transferer, "beeperr", "")) - return -1; - return AST_FEATURE_RETURN_SUCCESS; - } - - /* valid extension, res == 1 */ - if (!ast_exists_extension(transferer, transferer_real_context, xferto, 1, - S_COR(transferer->caller.id.number.valid, transferer->caller.id.number.str, NULL))) { - ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context); + if (l) { + ast_log(LOG_WARNING, "Extension '%s' does not exist in context '%s'\n", + xferto, transferer_real_context); + } else { + /* Does anyone care about this case? */ + ast_log(LOG_WARNING, "No digits dialed for atxfer.\n"); + } + ast_stream_and_wait(transferer, "pbx-invalid", ""); finishup(transferee); - if (ast_stream_and_wait(transferer, "beeperr", "")) - return -1; return AST_FEATURE_RETURN_SUCCESS; } @@ -1886,8 +2085,8 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st return parkcall_helper(chan, peer, config, code, sense, &args); } - l = strlen(xferto); - snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context); /* append context */ + /* Append context to dialed transfer number. */ + snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context); /* If we are performing an attended transfer and we have two channels involved then copy sound file information to play upon attended transfer completion */ @@ -1903,49 +2102,75 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st } } - newchan = feature_request_and_dial(transferer, transferee, "Local", - ast_best_codec(transferer->nativeformats), - xferto, atxfernoanswertimeout, &outstate, - transferer->caller.id.number.valid ? transferer->caller.id.number.str : NULL, - transferer->caller.id.name.valid ? transferer->caller.id.name.str : NULL, - 1, transferer->language); + /* Extract redial transferer information from the channel name. */ + transferer_name_orig = ast_strdupa(transferer->name); + transferer_name = ast_strdupa(transferer_name_orig); + transferer_tech = strsep(&transferer_name, "/"); + dash = strrchr(transferer_name, '-'); + if (dash) { + /* Trim off channel name sequence/serial number. */ + *dash = '\0'; + } + + /* Stop autoservice so we can monitor all parties involved in the transfer. */ + if (ast_autoservice_stop(transferee) < 0) { + ast_indicate(transferee, AST_CONTROL_UNHOLD); + return -1; + } + /* Save connected line info for party B about party A in case transfer fails. */ ast_party_connected_line_init(&connected_line); + ast_channel_lock(transferer); + ast_party_connected_line_copy(&connected_line, &transferer->connected); + ast_channel_unlock(transferer); + connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; + + /* Dial party C */ + newchan = feature_request_and_dial(transferer, transferer_name_orig, transferer, + transferee, "Local", ast_best_codec(transferer->nativeformats), xferto, + atxfernoanswertimeout, &outstate, transferer->language); + ast_debug(2, "Dial party C result: newchan:%d, outstate:%d\n", !!newchan, outstate); + if (!ast_check_hangup(transferer)) { int hangup_dont = 0; - /* Transferer is up - old behaviour */ + /* Transferer (party B) is up */ + ast_debug(1, "Actually doing an attended transfer.\n"); + + /* Start autoservice on transferee while the transferer deals with party C. */ + ast_autoservice_start(transferee); + ast_indicate(transferer, -1); if (!newchan) { - finishup(transferee); /* any reason besides user requested cancel and busy triggers the failed sound */ - if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY && - ast_stream_and_wait(transferer, xferfailsound, "")) - return -1; - if (ast_stream_and_wait(transferer, xfersound, "")) - ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + switch (outstate) { + case AST_CONTROL_UNHOLD:/* Caller requested cancel or party C answer timeout. */ + case AST_CONTROL_BUSY: + case AST_CONTROL_CONGESTION: + if (ast_stream_and_wait(transferer, xfersound, "")) { + ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + } + break; + default: + if (ast_stream_and_wait(transferer, xferfailsound, "")) { + ast_log(LOG_WARNING, "Failed to play transfer failed sound!\n"); + } + break; + } + atxfer_fail_cleanup(transferee, transferer, &connected_line); return AST_FEATURE_RETURN_SUCCESS; } if (check_compat(transferer, newchan)) { - /* we do mean transferee here, NOT transferer */ - finishup(transferee); - return -1; + if (ast_stream_and_wait(transferer, xferfailsound, "")) { + ast_log(LOG_WARNING, "Failed to play transfer failed sound!\n"); + } + atxfer_fail_cleanup(transferee, transferer, &connected_line); + return AST_FEATURE_RETURN_SUCCESS; } memset(&bconfig,0,sizeof(struct ast_bridge_config)); ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT); ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT); - /* We need to get the transferer's connected line information copied - * at this point because he is likely to hang up during the bridge with - * newchan. This info will be used down below before bridging the - * transferee and newchan - * - * As a result, we need to be sure to free this data before returning - * or overwriting it. - */ - ast_channel_lock(transferer); - ast_party_connected_line_copy(&connected_line, &transferer->connected); - ast_channel_unlock(transferer); /* ast_bridge_call clears AST_FLAG_BRIDGE_HANGUP_DONT, but we don't want that to happen here because we're also in another bridge already @@ -1953,286 +2178,253 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st if (ast_test_flag(chan, AST_FLAG_BRIDGE_HANGUP_DONT)) { hangup_dont = 1; } - res = ast_bridge_call(transferer, newchan, &bconfig); + /* Let party B and party C talk as long as they want. */ + ast_bridge_call(transferer, newchan, &bconfig); if (hangup_dont) { ast_set_flag(chan, AST_FLAG_BRIDGE_HANGUP_DONT); } if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) { ast_hangup(newchan); - if (ast_stream_and_wait(transferer, xfersound, "")) + if (ast_stream_and_wait(transferer, xfersound, "")) { ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); - finishup(transferee); - transferer->_softhangup = 0; - ast_party_connected_line_free(&connected_line); + } + atxfer_fail_cleanup(transferee, transferer, &connected_line); return AST_FEATURE_RETURN_SUCCESS; } - ast_cel_report_event(transferee, AST_CEL_ATTENDEDTRANSFER, NULL, NULL, newchan); - + /* Transferer (party B) is confirmed hung up at this point. */ if (check_compat(transferee, newchan)) { finishup(transferee); ast_party_connected_line_free(&connected_line); return -1; } - ast_indicate(transferee, AST_CONTROL_UNHOLD); + ast_indicate(transferee, AST_CONTROL_UNHOLD); if ((ast_autoservice_stop(transferee) < 0) - || (ast_waitfordigit(transferee, 100) < 0) - || (ast_waitfordigit(newchan, 100) < 0) - || ast_check_hangup(transferee) - || ast_check_hangup(newchan)) { - ast_hangup(newchan); - ast_party_connected_line_free(&connected_line); - return -1; - } - xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", transferee->linkedid, 0, "Transfered/%s", transferee->name); - if (!xferchan) { - ast_hangup(newchan); - ast_party_connected_line_free(&connected_line); - return -1; - } - /* Make formats okay */ - xferchan->visible_indication = transferer->visible_indication; - xferchan->readformat = transferee->readformat; - xferchan->writeformat = transferee->writeformat; - ast_channel_masquerade(xferchan, transferee); - ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority); - xferchan->_state = AST_STATE_UP; - ast_clear_flag(xferchan, AST_FLAGS_ALL); - xferchan->_softhangup = 0; - if ((f = ast_read(xferchan))) - ast_frfree(f); - newchan->_state = AST_STATE_UP; - ast_clear_flag(newchan, AST_FLAGS_ALL); - newchan->_softhangup = 0; - if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { - ast_hangup(xferchan); + || (ast_waitfordigit(transferee, 100) < 0) + || (ast_waitfordigit(newchan, 100) < 0) + || ast_check_hangup(transferee) + || ast_check_hangup(newchan)) { ast_hangup(newchan); ast_party_connected_line_free(&connected_line); return -1; } - - ast_channel_lock(newchan); - if ((features_datastore = ast_channel_datastore_find(newchan, &dial_features_info, NULL))) { - dialfeatures = features_datastore->data; - } - ast_channel_unlock(newchan); - - if (dialfeatures) { - /* newchan should always be the callee and shows up as callee in dialfeatures, but for some reason - I don't currently understand, the abilities of newchan seem to be stored on the caller side */ - ast_copy_flags(&(config->features_callee), &(dialfeatures->features_caller), AST_FLAGS_ALL); - dialfeatures = NULL; - } - - ast_channel_lock(xferchan); - if ((features_datastore = ast_channel_datastore_find(xferchan, &dial_features_info, NULL))) { - dialfeatures = features_datastore->data; - } - ast_channel_unlock(xferchan); - - if (dialfeatures) { - ast_copy_flags(&(config->features_caller), &(dialfeatures->features_caller), AST_FLAGS_ALL); - } - - tobj->chan = newchan; - tobj->peer = xferchan; - tobj->bconfig = *config; - - if (tobj->bconfig.end_bridge_callback_data_fixup) { - tobj->bconfig.end_bridge_callback_data_fixup(&tobj->bconfig, tobj->peer, tobj->chan); - } - - /* Due to a limitation regarding when callerID is set on a Local channel, - * we use the transferer's connected line information here. - */ - - /* xferchan is transferee, and newchan is the transfer target - * So...in a transfer, who is the caller and who is the callee? - * - * When the call is originally made, it is clear who is caller and callee. - * When a transfer occurs, it is my humble opinion that the transferee becomes - * the caller, and the transfer target is the callee. - * - * The problem is that these macros were set with the intention of the original - * caller and callee taking those roles. A transfer can totally mess things up, - * to be technical. What sucks even more is that you can't effectively change - * the macros in the dialplan during the call from the transferer to the transfer - * target because the transferee is stuck with whatever role he originally had. - * - * I think the answer here is just to make sure that it is well documented that - * during a transfer, the transferee is the "caller" and the transfer target - * is the "callee." - * - * This means that if party A calls party B, and party A transfers party B to - * party C, then B has switched roles for the call. Now party B will have the - * caller macro called on his channel instead of the callee macro. - * - * Luckily, the method by which the bridge is launched here ensures that the - * transferee is the "chan" on the bridge and the transfer target is the "peer," - * so my idea for the roles post-transfer does not require extensive code changes. - */ - connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; - if (ast_channel_connected_line_macro(newchan, xferchan, &connected_line, 1, 0)) { - ast_channel_update_connected_line(xferchan, &connected_line, NULL); - } - ast_channel_lock(xferchan); - ast_connected_line_copy_from_caller(&connected_line, &xferchan->caller); - ast_channel_unlock(xferchan); - connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; - if (ast_channel_connected_line_macro(xferchan, newchan, &connected_line, 0, 0)) { - ast_channel_update_connected_line(newchan, &connected_line, NULL); - } - ast_party_connected_line_free(&connected_line); - - if (ast_stream_and_wait(newchan, xfersound, "")) - ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); - bridge_call_thread_launch(tobj); - return -1; /* XXX meaning the channel is bridged ? */ } else if (!ast_check_hangup(transferee)) { - /* act as blind transfer */ - if (ast_autoservice_stop(transferee) < 0) { - ast_hangup(newchan); - return -1; - } + /* Transferer (party B) has hung up at this point. Doing blonde transfer. */ + ast_debug(1, "Actually doing a blonde transfer.\n"); - if (!newchan) { + if (!newchan && !atxferdropcall) { + /* Party C is not available, try to call party B back. */ unsigned int tries = 0; - char *transferer_tech, *transferer_name = ast_strdupa(transferer->name); - - transferer_tech = strsep(&transferer_name, "/"); - transferer_name = strsep(&transferer_name, "-"); if (ast_strlen_zero(transferer_name) || ast_strlen_zero(transferer_tech)) { - ast_log(LOG_WARNING, "Transferer has invalid channel name: '%s'\n", transferer->name); - if (ast_stream_and_wait(transferee, "beeperr", "")) - return -1; - return AST_FEATURE_RETURN_SUCCESS; + ast_log(LOG_WARNING, + "Transferer channel name: '%s' cannot be used for callback.\n", + transferer_name_orig); + ast_indicate(transferee, AST_CONTROL_UNHOLD); + ast_party_connected_line_free(&connected_line); + return -1; } - ast_log(LOG_NOTICE, "We're trying to call %s/%s\n", transferer_tech, transferer_name); - newchan = feature_request_and_dial(transferee, NULL, transferer_tech, - ast_best_codec(transferee->nativeformats), - transferer_name, atxfernoanswertimeout, &outstate, - transferee->caller.id.number.valid ? transferee->caller.id.number.str : NULL, - transferee->caller.id.name.valid ? transferee->caller.id.name.str : NULL, - 0, transferer->language); - while (!newchan && !atxferdropcall && tries < atxfercallbackretries) { - /* Trying to transfer again */ - ast_autoservice_start(transferee); - ast_autoservice_ignore(transferee, AST_FRAME_DTMF_END); - ast_indicate(transferee, AST_CONTROL_HOLD); - - newchan = feature_request_and_dial(transferer, transferee, "Local", - ast_best_codec(transferer->nativeformats), - xferto, atxfernoanswertimeout, &outstate, - transferer->caller.id.number.valid ? transferer->caller.id.number.str : NULL, - transferer->caller.id.name.valid ? transferer->caller.id.name.str : NULL, - 1, transferer->language); - if (ast_autoservice_stop(transferee) < 0) { - if (newchan) - ast_hangup(newchan); - return -1; + tries = 0; + for (;;) { + /* Try to get party B back. */ + ast_debug(1, "We're trying to callback %s/%s\n", + transferer_tech, transferer_name); + newchan = feature_request_and_dial(transferer, transferer_name_orig, + transferee, transferee, transferer_tech, + ast_best_codec(transferee->nativeformats), transferer_name, + atxfernoanswertimeout, &outstate, transferer->language); + ast_debug(2, "Dial party B result: newchan:%d, outstate:%d\n", + !!newchan, outstate); + if (newchan || ast_check_hangup(transferee)) { + break; } - if (!newchan) { + + ++tries; + if (atxfercallbackretries <= tries) { + /* No more callback tries remaining. */ + break; + } + + if (atxferloopdelay) { /* Transfer failed, sleeping */ - ast_debug(1, "Sleeping for %d ms before callback.\n", atxferloopdelay); + ast_debug(1, "Sleeping for %d ms before retrying atxfer.\n", + atxferloopdelay); ast_safe_sleep(transferee, atxferloopdelay); - ast_debug(1, "Trying to callback...\n"); - newchan = feature_request_and_dial(transferee, NULL, transferer_tech, - ast_best_codec(transferee->nativeformats), - transferer_name, atxfernoanswertimeout, &outstate, - transferee->caller.id.number.valid ? transferee->caller.id.number.str : NULL, - transferee->caller.id.name.valid ? transferee->caller.id.name.str : NULL, - 0, transferer->language); + if (ast_check_hangup(transferee)) { + ast_party_connected_line_free(&connected_line); + return -1; + } } - tries++; - } - } - if (!newchan) - return -1; - ast_cel_report_event(transferee, AST_CEL_ATTENDEDTRANSFER, NULL, NULL, newchan); - - /* newchan is up, we should prepare transferee and bridge them */ - if (check_compat(transferee, newchan)) { - finishup(transferee); - return -1; + /* Retry dialing party C. */ + ast_debug(1, "We're retrying to call %s/%s\n", "Local", xferto); + newchan = feature_request_and_dial(transferer, transferer_name_orig, + transferer, transferee, "Local", + ast_best_codec(transferee->nativeformats), xferto, + atxfernoanswertimeout, &outstate, transferer->language); + ast_debug(2, "Redial party C result: newchan:%d, outstate:%d\n", + !!newchan, outstate); + if (newchan || ast_check_hangup(transferee)) { + break; + } + } } ast_indicate(transferee, AST_CONTROL_UNHOLD); - - if ((ast_waitfordigit(transferee, 100) < 0) - || (ast_waitfordigit(newchan, 100) < 0) - || ast_check_hangup(transferee) - || ast_check_hangup(newchan)) { - ast_hangup(newchan); + if (!newchan) { + /* No party C or could not callback party B. */ + ast_party_connected_line_free(&connected_line); return -1; } - xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", transferee->linkedid, 0, "Transfered/%s", transferee->name); - if (!xferchan) { + /* newchan is up, we should prepare transferee and bridge them */ + if (ast_check_hangup(newchan)) { ast_hangup(newchan); + ast_party_connected_line_free(&connected_line); return -1; } - /* Make formats okay */ - xferchan->visible_indication = transferer->visible_indication; - xferchan->readformat = transferee->readformat; - xferchan->writeformat = transferee->writeformat; - ast_channel_masquerade(xferchan, transferee); - ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority); - xferchan->_state = AST_STATE_UP; - ast_clear_flag(xferchan, AST_FLAGS_ALL); - xferchan->_softhangup = 0; - if ((f = ast_read(xferchan))) - ast_frfree(f); - newchan->_state = AST_STATE_UP; - ast_clear_flag(newchan, AST_FLAGS_ALL); - newchan->_softhangup = 0; - if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { - ast_hangup(xferchan); - ast_hangup(newchan); + if (check_compat(transferee, newchan)) { + ast_party_connected_line_free(&connected_line); return -1; } - tobj->chan = newchan; - tobj->peer = xferchan; - tobj->bconfig = *config; - - if (tobj->bconfig.end_bridge_callback_data_fixup) { - tobj->bconfig.end_bridge_callback_data_fixup(&tobj->bconfig, tobj->peer, tobj->chan); - } - - ast_channel_lock(newchan); - ast_connected_line_copy_from_caller(&connected_line, &newchan->caller); - ast_channel_unlock(newchan); - connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; - if (ast_channel_connected_line_macro(newchan, xferchan, &connected_line, 1, 0)) { - ast_channel_update_connected_line(xferchan, &connected_line, NULL); - } - ast_channel_lock(xferchan); - ast_connected_line_copy_from_caller(&connected_line, &xferchan->caller); - ast_channel_unlock(xferchan); - connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; - if (ast_channel_connected_line_macro(xferchan, newchan, &connected_line, 0, 0)) { - ast_channel_update_connected_line(newchan, &connected_line, NULL); - } - - ast_party_connected_line_free(&connected_line); - - if (ast_stream_and_wait(newchan, xfersound, "")) - ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); - bridge_call_thread_launch(tobj); - return -1; /* XXX meaning the channel is bridged ? */ } else { - /* Transferee hung up */ - finishup(transferee); - /* At this point both the transferer transferee have hungup, - * so if newchan is up, hang it up as it has no one to talk to */ + /* + * Both the transferer and transferee have hungup. If newchan + * is up, hang it up as it has no one to talk to. + */ + ast_debug(1, "Everyone is hungup.\n"); if (newchan) { ast_hangup(newchan); } + ast_party_connected_line_free(&connected_line); + return -1; + } + + /* Initiate the channel transfer of party A to party C (or recalled party B). */ + ast_cel_report_event(transferee, AST_CEL_ATTENDEDTRANSFER, NULL, NULL, newchan); + + xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", transferee->linkedid, 0, "Transfered/%s", transferee->name); + if (!xferchan) { + ast_hangup(newchan); + ast_party_connected_line_free(&connected_line); + return -1; + } + + /* Give party A a momentary ringback tone during transfer. */ + xferchan->visible_indication = AST_CONTROL_RINGING; + + /* Make formats okay */ + xferchan->readformat = transferee->readformat; + xferchan->writeformat = transferee->writeformat; + + ast_channel_masquerade(xferchan, transferee); + ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority); + xferchan->_state = AST_STATE_UP; + ast_clear_flag(xferchan, AST_FLAGS_ALL); + + /* Do the masquerade manually to make sure that is is completed. */ + ast_do_masquerade(xferchan); + + newchan->_state = AST_STATE_UP; + ast_clear_flag(newchan, AST_FLAGS_ALL); + tobj = ast_calloc(1, sizeof(*tobj)); + if (!tobj) { + ast_hangup(xferchan); + ast_hangup(newchan); + ast_party_connected_line_free(&connected_line); return -1; } + + ast_channel_lock(newchan); + if ((features_datastore = ast_channel_datastore_find(newchan, &dial_features_info, NULL))) { + dialfeatures = features_datastore->data; + } + ast_channel_unlock(newchan); + + if (dialfeatures) { + /* newchan should always be the callee and shows up as callee in dialfeatures, but for some reason + I don't currently understand, the abilities of newchan seem to be stored on the caller side */ + ast_copy_flags(&(config->features_callee), &(dialfeatures->features_caller), AST_FLAGS_ALL); + dialfeatures = NULL; + } + + ast_channel_lock(xferchan); + if ((features_datastore = ast_channel_datastore_find(xferchan, &dial_features_info, NULL))) { + dialfeatures = features_datastore->data; + } + ast_channel_unlock(xferchan); + + if (dialfeatures) { + ast_copy_flags(&(config->features_caller), &(dialfeatures->features_caller), AST_FLAGS_ALL); + } + + tobj->chan = newchan; + tobj->peer = xferchan; + tobj->bconfig = *config; + + if (tobj->bconfig.end_bridge_callback_data_fixup) { + tobj->bconfig.end_bridge_callback_data_fixup(&tobj->bconfig, tobj->peer, tobj->chan); + } + + /* + * xferchan is transferee, and newchan is the transfer target + * So...in a transfer, who is the caller and who is the callee? + * + * When the call is originally made, it is clear who is caller and callee. + * When a transfer occurs, it is my humble opinion that the transferee becomes + * the caller, and the transfer target is the callee. + * + * The problem is that these macros were set with the intention of the original + * caller and callee taking those roles. A transfer can totally mess things up, + * to be technical. What sucks even more is that you can't effectively change + * the macros in the dialplan during the call from the transferer to the transfer + * target because the transferee is stuck with whatever role he originally had. + * + * I think the answer here is just to make sure that it is well documented that + * during a transfer, the transferee is the "caller" and the transfer target + * is the "callee." + * + * This means that if party B calls party A, and party B transfers party A to + * party C, then A has switched roles for the call. Now party A will have the + * caller macro called on his channel instead of the callee macro. + * + * Luckily, the method by which the party B to party C bridge is + * launched above ensures that the transferee is the "chan" on + * the bridge and the transfer target is the "peer," so my idea + * for the roles post-transfer does not require extensive code + * changes. + */ + + /* Transfer party C connected line to party A */ + ast_channel_lock(transferer); + /* + * Due to a limitation regarding when callerID is set on a Local channel, + * we use the transferer's connected line information here. + */ + ast_party_connected_line_copy(&connected_line, &transferer->connected); + ast_channel_unlock(transferer); + connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; + if (ast_channel_connected_line_macro(newchan, xferchan, &connected_line, 1, 0)) { + ast_channel_update_connected_line(xferchan, &connected_line, NULL); + } + + /* Transfer party A connected line to party C */ + ast_channel_lock(xferchan); + ast_connected_line_copy_from_caller(&connected_line, &xferchan->caller); + ast_channel_unlock(xferchan); + connected_line.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER; + if (ast_channel_connected_line_macro(xferchan, newchan, &connected_line, 0, 0)) { + ast_channel_update_connected_line(newchan, &connected_line, NULL); + } + + if (ast_stream_and_wait(newchan, xfersound, "")) + ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + bridge_call_thread_launch(tobj); + + ast_party_connected_line_free(&connected_line); + return -1;/* The transferee is masqueraded and the original bridged channels can be hungup. */ } /* add atxfer and automon as undefined so you can only use em if you configure them */ @@ -2743,58 +2935,103 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer, } } -/*! - * \brief Get feature and dial - * \param caller,transferee,type,format,data,timeout,outstate,cid_num,cid_name,igncallerstate,language +/*! + * \internal + * \brief Get feature and dial. * - * Request channel, set channel variables, initiate call,check if they want to disconnect - * go into loop, check if timeout has elapsed, check if person to be transfered hung up, - * check for answer break loop, set cdr return channel. + * \param caller Channel to represent as the calling channel for the dialed channel. + * \param caller_name Original caller channel name. + * \param requestor Channel to say is requesting the dial (usually the caller). + * \param transferee Channel that the dialed channel will be transferred to. + * \param type Channel technology type to dial. + * \param format Codec formats for dialed channel. + * \param data Dialed channel extra parameters for ast_request() and ast_call(). + * \param timeout Time limit for dialed channel to answer in ms. Must be greater than zero. + * \param outstate Status of dialed channel if unsuccessful. + * \param language Language of the caller. * - * \todo XXX Check - this is very similar to the code in channel.c - * \return always a channel -*/ -static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate, const char *language) + * \note + * outstate can be: + * 0, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION, + * AST_CONTROL_ANSWER, or AST_CONTROL_UNHOLD. If + * AST_CONTROL_UNHOLD then the caller channel cancelled the + * transfer or the dialed channel did not answer before the + * timeout. + * + * \details + * Request channel, set channel variables, initiate call, + * check if they want to disconnect, go into loop, check if timeout has elapsed, + * check if person to be transfered hung up, check for answer break loop, + * set cdr return channel. + * + * \retval Channel Connected channel for transfer. + * \retval NULL on failure to get third party connected. + * + * \note This is similar to __ast_request_and_dial() in channel.c + */ +static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, + const char *caller_name, struct ast_channel *requestor, + struct ast_channel *transferee, const char *type, int format, void *data, + int timeout, int *outstate, const char *language) { int state = 0; int cause = 0; int to; + int caller_hungup; + int transferee_hungup; struct ast_channel *chan; - struct ast_channel *monitor_chans[2]; + struct ast_channel *monitor_chans[3]; struct ast_channel *active_channel; - int res = 0, ready = 0; + int res; + int ready = 0; struct timeval started; int x, len = 0; char *disconnect_code = NULL, *dialed_code = NULL; + struct ast_frame *f; + AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames; + + caller_hungup = ast_check_hangup(caller); - if (!(chan = ast_request(type, format, caller, data, &cause))) { + if (!(chan = ast_request(type, format, requestor, data, &cause))) { ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data); - switch(cause) { + switch (cause) { case AST_CAUSE_BUSY: state = AST_CONTROL_BUSY; break; case AST_CAUSE_CONGESTION: state = AST_CONTROL_CONGESTION; break; + default: + state = 0; + break; } goto done; } - ast_set_callerid(chan, cid_num, cid_name, cid_num); ast_string_field_set(chan, language, language); - ast_channel_inherit_variables(caller, chan); - pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller->name); - + ast_channel_inherit_variables(caller, chan); + pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller_name); + ast_channel_lock(chan); - ast_connected_line_copy_from_caller(&chan->connected, &caller->caller); + ast_connected_line_copy_from_caller(&chan->connected, &requestor->caller); ast_channel_unlock(chan); - + if (ast_call(chan, data, timeout)) { ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data); + switch (chan->hangupcause) { + case AST_CAUSE_BUSY: + state = AST_CONTROL_BUSY; + break; + case AST_CAUSE_CONGESTION: + state = AST_CONTROL_CONGESTION; + break; + default: + state = 0; + break; + } goto done; } - - ast_indicate(caller, AST_CONTROL_RINGING); + /* support dialing of the featuremap disconnect code while performing an attended tranfer */ ast_rwlock_rdlock(&features_lock); for (x = 0; x < FEATURES_COUNT; x++) { @@ -2811,66 +3048,141 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, x = 0; started = ast_tvnow(); to = timeout; + AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames); ast_poll_channel_add(caller, chan); - while (!((transferee && ast_check_hangup(transferee)) && (!igncallerstate && ast_check_hangup(caller))) && timeout && (chan->_state != AST_STATE_UP)) { - struct ast_frame *f = NULL; - - monitor_chans[0] = caller; - monitor_chans[1] = chan; - active_channel = ast_waitfor_n(monitor_chans, 2, &to); + transferee_hungup = 0; + while (!ast_check_hangup(transferee) && (chan->_state != AST_STATE_UP)) { + int num_chans = 0; + + monitor_chans[num_chans++] = transferee; + monitor_chans[num_chans++] = chan; + if (!caller_hungup) { + if (ast_check_hangup(caller)) { + caller_hungup = 1; + +#if defined(ATXFER_NULL_TECH) + /* Change caller's name to ensure that it will remain unique. */ + set_new_chan_name(caller); + + /* + * Get rid of caller's physical technology so it is free for + * other calls. + */ + set_null_chan_tech(caller); +#endif /* defined(ATXFER_NULL_TECH) */ + } else { + /* caller is not hungup so monitor it. */ + monitor_chans[num_chans++] = caller; + } + } /* see if the timeout has been violated */ - if(ast_tvdiff_ms(ast_tvnow(), started) > timeout) { + if (ast_tvdiff_ms(ast_tvnow(), started) > timeout) { state = AST_CONTROL_UNHOLD; - ast_log(LOG_NOTICE, "We exceeded our AT-timeout\n"); + ast_log(LOG_NOTICE, "We exceeded our AT-timeout for %s\n", chan->name); break; /*doh! timeout*/ } + active_channel = ast_waitfor_n(monitor_chans, num_chans, &to); if (!active_channel) continue; - if (chan && (chan == active_channel)){ + f = NULL; + if (transferee == active_channel) { + struct ast_frame *dup_f; + + f = ast_read(transferee); + if (f == NULL) { /*doh! where'd he go?*/ + transferee_hungup = 1; + state = 0; + break; + } + if (ast_is_deferrable_frame(f)) { + dup_f = ast_frisolate(f); + if (dup_f) { + if (dup_f == f) { + f = NULL; + } + AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list); + } + } + } else if (chan == active_channel) { if (!ast_strlen_zero(chan->call_forward)) { - if (!(chan = ast_call_forward(caller, chan, NULL, format, NULL, outstate))) { - return NULL; + state = 0; + chan = ast_call_forward(caller, chan, NULL, format, NULL, &state); + if (!chan) { + break; } continue; } f = ast_read(chan); if (f == NULL) { /*doh! where'd he go?*/ - state = AST_CONTROL_HANGUP; - res = 0; + switch (chan->hangupcause) { + case AST_CAUSE_BUSY: + state = AST_CONTROL_BUSY; + break; + case AST_CAUSE_CONGESTION: + state = AST_CONTROL_CONGESTION; + break; + default: + state = 0; + break; + } break; } - - if (f->frametype == AST_FRAME_CONTROL || f->frametype == AST_FRAME_DTMF || f->frametype == AST_FRAME_TEXT) { + + if (f->frametype == AST_FRAME_CONTROL) { if (f->subclass.integer == AST_CONTROL_RINGING) { - state = f->subclass.integer; ast_verb(3, "%s is ringing\n", chan->name); ast_indicate(caller, AST_CONTROL_RINGING); - } else if ((f->subclass.integer == AST_CONTROL_BUSY) || (f->subclass.integer == AST_CONTROL_CONGESTION)) { + } else if (f->subclass.integer == AST_CONTROL_BUSY) { state = f->subclass.integer; ast_verb(3, "%s is busy\n", chan->name); ast_indicate(caller, AST_CONTROL_BUSY); ast_frfree(f); - f = NULL; + break; + } else if (f->subclass.integer == AST_CONTROL_CONGESTION) { + state = f->subclass.integer; + ast_verb(3, "%s is congested\n", chan->name); + ast_indicate(caller, AST_CONTROL_CONGESTION); + ast_frfree(f); break; } else if (f->subclass.integer == AST_CONTROL_ANSWER) { /* This is what we are hoping for */ state = f->subclass.integer; ast_frfree(f); - f = NULL; ready=1; break; } else if (f->subclass.integer == AST_CONTROL_CONNECTED_LINE) { - if (ast_channel_connected_line_macro(chan, caller, f, 1, 1)) { - ast_indicate_data(caller, AST_CONTROL_CONNECTED_LINE, f->data.ptr, f->datalen); + if (caller_hungup) { + struct ast_party_connected_line connected; + + /* Just save it for the transfer. */ + ast_party_connected_line_set_init(&connected, &caller->connected); + res = ast_connected_line_parse_data(f->data.ptr, f->datalen, + &connected); + if (!res) { + ast_channel_set_connected_line(caller, &connected, NULL); + } + ast_party_connected_line_free(&connected); + } else { + ast_autoservice_start(transferee); + if (ast_channel_connected_line_macro(chan, caller, f, 1, 1)) { + ast_indicate_data(caller, AST_CONTROL_CONNECTED_LINE, + f->data.ptr, f->datalen); + } + ast_autoservice_stop(transferee); } } else if (f->subclass.integer == AST_CONTROL_REDIRECTING) { - if (ast_channel_redirecting_macro(chan, caller, f, 1, 1)) { - ast_indicate_data(caller, AST_CONTROL_REDIRECTING, f->data.ptr, f->datalen); + if (!caller_hungup) { + ast_autoservice_start(transferee); + if (ast_channel_redirecting_macro(chan, caller, f, 1, 1)) { + ast_indicate_data(caller, AST_CONTROL_REDIRECTING, + f->data.ptr, f->datalen); + } + ast_autoservice_stop(transferee); } } else if (f->subclass.integer != -1 && f->subclass.integer != AST_CONTROL_PROGRESS) { ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass.integer); @@ -2879,22 +3191,9 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) { ast_write(caller, f); } - - } else if (caller && (active_channel == caller)) { + } else if (caller == active_channel) { f = ast_read(caller); - if (f == NULL) { /*doh! where'd he go?*/ - if (!igncallerstate) { - if (ast_check_hangup(caller) && !ast_check_hangup(chan)) { - /* make this a blind transfer */ - ready = 1; - break; - } - state = AST_CONTROL_HANGUP; - res = 0; - break; - } - } else { - + if (f) { if (f->frametype == AST_FRAME_DTMF) { dialed_code[x++] = f->subclass.integer; dialed_code[x] = '\0'; @@ -2908,7 +3207,6 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, /* Caller Canceled the call */ state = AST_CONTROL_UNHOLD; ast_frfree(f); - f = NULL; break; } } else if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO) { @@ -2921,21 +3219,31 @@ static struct ast_channel *feature_request_and_dial(struct ast_channel *caller, } /* end while */ ast_poll_channel_del(caller, chan); - + + /* + * We need to free all the deferred frames, but we only need to + * queue the deferred frames if no hangup was received. + */ + ast_channel_lock(transferee); + transferee_hungup = (transferee_hungup || ast_check_hangup(transferee)); + while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) { + if (!transferee_hungup) { + ast_queue_frame_head(transferee, f); + } + ast_frfree(f); + } + ast_channel_unlock(transferee); + done: ast_indicate(caller, -1); if (chan && ready) { - if (chan->_state == AST_STATE_UP) + if (chan->_state == AST_STATE_UP) state = AST_CONTROL_ANSWER; - res = 0; } else if (chan) { - res = -1; ast_hangup(chan); chan = NULL; - } else { - res = -1; } - + if (outstate) *outstate = state; -- cgit v1.2.3