[asterisk-commits] rmudgett: branch 1.8 r302174 - in /branches/1.8: ./ main/features.c
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Tue Jan 18 12:11:53 CST 2011
Author: rmudgett
Date: Tue Jan 18 12:11:43 2011
New Revision: 302174
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=302174
Log:
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/
........
................
Modified:
branches/1.8/ (props changed)
branches/1.8/main/features.c
Propchange: branches/1.8/
------------------------------------------------------------------------------
Binary property 'branch-1.6.2-merged' - no diff available.
Modified: branches/1.8/main/features.c
URL: http://svnview.digium.com/svn/asterisk/branches/1.8/main/features.c?view=diff&rev=302174&r1=302173&r2=302174
==============================================================================
--- branches/1.8/main/features.c (original)
+++ branches/1.8/main/features.c Tue Jan 18 12:11:43 2011
@@ -58,6 +58,56 @@
#include "asterisk/astobj2.h"
#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
<application name="Bridge" language="en_US">
@@ -412,6 +462,124 @@
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<XFER_%x>", chan->name, seq_num) + 1;
+ chan_name = alloca(len);
+ snprintf(chan_name, len, "%s<XFER_%x>", 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 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
@@ -1801,15 +1972,44 @@
}
/*!
+ * \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
* \param peer person transfering call
* \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 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 @@
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");
+ 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;
- }
-
- /* 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);
- finishup(transferee);
- if (ast_stream_and_wait(transferer, "beeperr", ""))
- return -1;
return AST_FEATURE_RETURN_SUCCESS;
}
@@ -1886,8 +2085,8 @@
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 @@
}
}
- 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 @@
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);
-
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_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) {
+ } else if (!ast_check_hangup(transferee)) {
+ /* Transferer (party B) has hung up at this point. Doing blonde transfer. */
+ ast_debug(1, "Actually doing a blonde transfer.\n");
+
+ if (!newchan && !atxferdropcall) {
+ /* Party C is not available, try to call party B back. */
+ unsigned int tries = 0;
+
+ if (ast_strlen_zero(transferer_name) || ast_strlen_zero(transferer_tech)) {
+ 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;
+ }
+
+ 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;
+ }
+
+ ++tries;
+ if (atxfercallbackretries <= tries) {
+ /* No more callback tries remaining. */
+ break;
+ }
+
+ if (atxferloopdelay) {
+ /* Transfer failed, sleeping */
+ ast_debug(1, "Sleeping for %d ms before retrying atxfer.\n",
+ atxferloopdelay);
+ ast_safe_sleep(transferee, atxferloopdelay);
+ if (ast_check_hangup(transferee)) {
+ ast_party_connected_line_free(&connected_line);
+ 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 (!newchan) {
+ /* No party C or could not callback party B. */
+ ast_party_connected_line_free(&connected_line);
+ return -1;
+ }
+
+ /* 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;
}
-
- 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.
+ } else {
+ /*
+ * Both the transferer and transferee have hungup. If newchan
+ * is up, hang it up as it has no one to talk to.
*/
-
- /* 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;
- }
-
- if (!newchan) {
- 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_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;
- }
- if (!newchan) {
- /* Transfer failed, sleeping */
- ast_debug(1, "Sleeping for %d ms before callback.\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);
- }
- 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;
- }
- 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);
- return -1;
- }
-
- xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", transferee->linkedid, 0, "Transfered/%s", transferee->name);
- if (!xferchan) {
- ast_hangup(newchan);
- 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);
- 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 */
+ 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 @@
}
}
-/*!
- * \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.
[... 351 lines stripped ...]
More information about the asterisk-commits
mailing list