[asterisk-commits] mmichelson: branch mmichelson/sip_attended_transfer r387621 - in /team/mmiche...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri May 3 15:29:48 CDT 2013


Author: mmichelson
Date: Fri May  3 15:29:46 2013
New Revision: 387621

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=387621
Log:
First sweep over chan_sip's attended transfer code.

This mostly changes local_attended_transfer(), but there
was some seepage into the INVITE with Replaces handling
code since I changed get_sip_pvt_byid_locked() from locking
the  pvt and owner to just upping their refcounts.

I have this feeling that probably about 75% of the locking
done in chan_sip is leftover from the days were locks were
being used as faux references.


Modified:
    team/mmichelson/sip_attended_transfer/channels/chan_sip.c
    team/mmichelson/sip_attended_transfer/channels/sip/include/sip.h

Modified: team/mmichelson/sip_attended_transfer/channels/chan_sip.c
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/sip_attended_transfer/channels/chan_sip.c?view=diff&rev=387621&r1=387620&r2=387621
==============================================================================
--- team/mmichelson/sip_attended_transfer/channels/chan_sip.c (original)
+++ team/mmichelson/sip_attended_transfer/channels/chan_sip.c Fri May  3 15:29:46 2013
@@ -295,6 +295,7 @@
 #include "sip/include/security_events.h"
 #include "asterisk/sip_api.h"
 #include "asterisk/app.h"
+#include "asterisk/bridging.h"
 
 /*** DOCUMENTATION
 	<application name="SIPDtmfMode" language="en_US">
@@ -1199,7 +1200,8 @@
 static int copy_route(struct sip_route **dst, const struct sip_route *src);
 static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr,
 					      struct sip_request *req, const char *uri);
-static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag);
+static int get_sip_pvt_from_replaces(const char *callid, const char *totag, const char *fromtag,
+		struct sip_pvt **out_pvt, struct ast_channel **out_chan);
 static void check_pendings(struct sip_pvt *p);
 static void *sip_park_thread(void *stuff);
 static int sip_park(struct ast_channel *chan1, struct ast_channel *chan2, struct sip_request *req, uint32_t seqno, const char *park_exten, const char *park_context);
@@ -1273,7 +1275,6 @@
 static int sip_refer_alloc(struct sip_pvt *p);
 static int sip_notify_alloc(struct sip_pvt *p);
 static void ast_quiet_chan(struct ast_channel *chan);
-static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target);
 static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
 static void set_peer_nat(const struct sip_pvt *p, struct sip_peer *peer);
 static void check_for_nat(const struct ast_sockaddr *them, struct sip_pvt *p);
@@ -1476,9 +1477,11 @@
 static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e);
 static void handle_request_info(struct sip_pvt *p, struct sip_request *req);
 static int handle_request_options(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e);
-static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *nounlock);
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req,
+		uint32_t seqno, int *nounlock, struct sip_pvt *replaces_pvt,
+		struct ast_channel *replaces_chan);
 static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e);
-static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, uint32_t seqno, int *nounlock);
+static int local_attended_transfer(struct sip_pvt *transferer, struct ast_channel *transferer_chan, uint32_t seqno, int *nounlock);
 
 /*------Response handling functions */
 static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno);
@@ -6653,9 +6656,6 @@
 		p->udptl = NULL;
 	}
 	if (p->refer) {
-		if (p->refer->refer_call) {
-			p->refer->refer_call = dialog_unref(p->refer->refer_call, "unref dialog p->refer->refer_call");
-		}
 		ast_string_field_free_memory(p->refer);
 		ast_free(p->refer);
 		p->refer = NULL;
@@ -17802,10 +17802,25 @@
 	return SIP_GET_DEST_EXTEN_NOT_FOUND;
 }
 
-/*! \brief Lock dialog lock and find matching pvt lock
-	\return a reference, remember to release it when done
-*/
-static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *totag, const char *fromtag)
+/*! \brief Find a companion dialog based on Replaces information
+ *
+ * This information may come from a Refer-To header in a REFER or from
+ * a Replaces header in an INVITE.
+ *
+ * This function will find the appropriate sip_pvt and increment the refcount
+ * of both the sip_pvt and its owner channel. These two references are returned
+ * in the out parameters
+ *
+ * \param callid Callid to search for
+ * \param totag to-tag parameter from Replaces
+ * \param fromtag from-tag parameter from Replaces
+ * \param[out] out_pvt The found sip_pvt.
+ * \param[out] out_chan The found sip_pvt's owner channel.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+static int get_sip_pvt_from_replaces(const char *callid, const char *totag,
+		const char *fromtag, struct sip_pvt **out_pvt, struct ast_channel **out_chan)
 {
 	struct sip_pvt *sip_pvt_ptr;
 	struct sip_pvt tmp_dialog = {
@@ -17821,22 +17836,20 @@
 	sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find of dialog in dialogs table");
 	if (sip_pvt_ptr) {
 		/* Go ahead and lock it (and its owner) before returning */
-		sip_pvt_lock(sip_pvt_ptr);
+		SCOPED_LOCK(lock, sip_pvt_ptr, sip_pvt_lock, sip_pvt_unlock);
 		if (sip_cfg.pedanticsipchecking) {
 			unsigned char frommismatch = 0, tomismatch = 0;
 
 			if (ast_strlen_zero(fromtag)) {
-				sip_pvt_unlock(sip_pvt_ptr);
 				ast_debug(4, "Matched %s call for callid=%s - no from tag specified, pedantic check fails\n",
 					  sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid);
-				return NULL;
+				return -1;
 			}
 
 			if (ast_strlen_zero(totag)) {
-				sip_pvt_unlock(sip_pvt_ptr);
 				ast_debug(4, "Matched %s call for callid=%s - no to tag specified, pedantic check fails\n",
 					  sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid);
-				return NULL;
+				return -1;
 			}
 			/* RFC 3891
 			 * > 3.  User Agent Server Behavior: Receiving a Replaces Header
@@ -17855,11 +17868,10 @@
 			frommismatch = !!strcmp(fromtag, sip_pvt_ptr->theirtag);
 			tomismatch = !!strcmp(totag, sip_pvt_ptr->tag);
 
-                        /* Don't check from if the dialog is not established, due to multi forking the from
-                         * can change when the call is not answered yet.
-                         */
+			/* Don't check from if the dialog is not established, due to multi forking the from
+			 * can change when the call is not answered yet.
+			 */
 			if ((frommismatch && ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) || tomismatch) {
-				sip_pvt_unlock(sip_pvt_ptr);
 				if (frommismatch) {
 					ast_debug(4, "Matched %s call for callid=%s - pedantic from tag check fails; their tag is %s our tag is %s\n",
 						  sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid,
@@ -17870,7 +17882,7 @@
 						  sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid,
 						  totag, sip_pvt_ptr->tag);
 				}
-				return NULL;
+				return -1;
 			}
 		}
 
@@ -17879,15 +17891,13 @@
 					  sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING",
 					  sip_pvt_ptr->theirtag, sip_pvt_ptr->tag);
 
-		/* deadlock avoidance... */
-		while (sip_pvt_ptr->owner && ast_channel_trylock(sip_pvt_ptr->owner)) {
-			sip_pvt_unlock(sip_pvt_ptr);
-			usleep(1);
-			sip_pvt_lock(sip_pvt_ptr);
-		}
-	}
-
-	return sip_pvt_ptr;
+		*out_pvt = sip_pvt_ptr;
+		if (*out_chan) {
+			*out_chan = sip_pvt_ptr->owner ? ast_channel_ref(sip_pvt_ptr->owner) : NULL;
+		}
+	}
+
+	return 0;
 }
 
 /*! \brief Call transfer support (the REFER method)
@@ -24596,7 +24606,6 @@
 	return 0;
 }
 
-
 /*! \brief Turn off generator data
 	XXX Does this function belong in the SIP channel?
 */
@@ -24608,76 +24617,6 @@
 		else if (ast_channel_generatordata(chan))
 			ast_deactivate_generator(chan);
 	}
-}
-
-/*! \brief Attempt transfer of SIP call
-	This fix for attended transfers on a local PBX */
-static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target)
-{
-	int res = 0;
-	struct ast_channel *peera = NULL,
-		*peerb = NULL,
-		*peerc = NULL,
-		*peerd = NULL;
-
-
-	/* We will try to connect the transferee with the target and hangup
-	   all channels to the transferer */
-	ast_debug(4, "Sip transfer:--------------------\n");
-	if (transferer->chan1)
-		ast_debug(4, "-- Transferer to PBX channel: %s State %s\n", ast_channel_name(transferer->chan1), ast_state2str(ast_channel_state(transferer->chan1)));
-	else
-		ast_debug(4, "-- No transferer first channel - odd??? \n");
-	if (target->chan1)
-		ast_debug(4, "-- Transferer to PBX second channel (target): %s State %s\n", ast_channel_name(target->chan1), ast_state2str(ast_channel_state(target->chan1)));
-	else
-		ast_debug(4, "-- No target first channel ---\n");
-	if (transferer->chan2)
-		ast_debug(4, "-- Bridged call to transferee: %s State %s\n", ast_channel_name(transferer->chan2), ast_state2str(ast_channel_state(transferer->chan2)));
-	else
-		ast_debug(4, "-- No bridged call to transferee\n");
-	if (target->chan2)
-		ast_debug(4, "-- Bridged call to transfer target: %s State %s\n", target->chan2 ? ast_channel_name(target->chan2) : "<none>", target->chan2 ? ast_state2str(ast_channel_state(target->chan2)) : "(none)");
-	else
-		ast_debug(4, "-- No target second channel ---\n");
-	ast_debug(4, "-- END Sip transfer:--------------------\n");
-	if (transferer->chan2) { /* We have a bridge on the transferer's channel */
-		peera = transferer->chan1;	/* Transferer - PBX -> transferee channel * the one we hangup */
-		peerb = target->chan1;		/* Transferer - PBX -> target channel - This will get lost in masq */
-		peerc = transferer->chan2;	/* Asterisk to Transferee */
-		peerd = target->chan2;		/* Asterisk to Target */
-		ast_debug(3, "SIP transfer: Four channels to handle\n");
-	} else if (target->chan2) {	/* Transferer has no bridge (IVR), but transferee */
-		peera = target->chan1;		/* Transferer to PBX -> target channel */
-		peerb = transferer->chan1;	/* Transferer to IVR*/
-		peerc = target->chan2;		/* Asterisk to Target */
-		peerd = transferer->chan2;	/* Nothing */
-		ast_debug(3, "SIP transfer: Three channels to handle\n");
-	}
-
-	if (peera && peerb && peerc && (peerb != peerc)) {
-		ast_quiet_chan(peera);		/* Stop generators */
-		ast_quiet_chan(peerb);
-		ast_quiet_chan(peerc);
-		if (peerd)
-			ast_quiet_chan(peerd);
-
-		ast_debug(4, "SIP transfer: trying to masquerade %s into %s\n", ast_channel_name(peerc), ast_channel_name(peerb));
-		if (ast_channel_masquerade(peerb, peerc)) {
-			ast_log(LOG_WARNING, "Failed to masquerade %s into %s\n", ast_channel_name(peerb), ast_channel_name(peerc));
-			res = -1;
-		} else
-			ast_debug(4, "SIP transfer: Succeeded to masquerade channels.\n");
-		return res;
-	} else {
-		ast_log(LOG_NOTICE, "SIP Transfer attempted with no appropriate bridged calls to transfer\n");
-		if (transferer->chan1)
-			ast_softhangup_nolock(transferer->chan1, AST_SOFTHANGUP_DEV);
-		if (target->chan1)
-			ast_softhangup_nolock(target->chan1, AST_SOFTHANGUP_DEV);
-		return -1;
-	}
-	return 0;
 }
 
 /*! \brief Get tag from packet
@@ -24995,23 +24934,32 @@
 }
 
 /*! \brief Handle the transfer part of INVITE with a replaces: header,
-    meaning a target pickup or an attended transfer.
-    Used only once.
-	XXX 'ignore' is unused.
-
-	\note this function is called by handle_request_invite(). Four locks
-	held at the beginning of this function, p, p->owner, p->refer->refer_call and
-	p->refere->refer_call->owner.  only p's lock should remain at the end of this
-	function.  p's lock as well as the channel p->owner's lock are held by
-	handle_request_do(), we unlock p->owner before the masq.  By setting nounlock
-	we are indicating to handle_request_do() that we have already unlocked the owner.
+ * 
+ * This is used for call-pickup and for attended transfers initiated on
+ * remote endpoints (i.e. a REFER received on a remote server).
+ *
+ * \note p and p->owner are locked upon entering this function. If the
+ * call pickup or attended transfer is successful, then p->owner will
+ * be unlocked upon exiting this function. This is communicated to the
+ * caller through the nounlock parameter.
+ *
+ * \param p The sip_pvt where the INVITE with Replaces was received
+ * \param req The incoming INVITE
+ * \param seqno CSeq of INVITE
+ * \param[out] nounlock Indicator if p->owner should remained locked. On successful transfer, this will be set true.
+ * \param replaces_pvt sip_pvt referenced by Replaces header
+ * \param replaces_chan replaces_pvt's owner channel
+ * \retval 0 Success
+ * \retval non-zero Failure
  */
-static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *nounlock)
+static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req,
+		uint32_t seqno, int *nounlock, struct sip_pvt *replaces_pvt,
+		struct ast_channel *replaces_chan)
 {
 	int earlyreplace = 0;
 	int oneleggedreplace = 0;		/* Call with no bridge, propably IVR or voice message */
 	struct ast_channel *c = p->owner;	/* Our incoming call */
-	struct ast_channel *replacecall = p->refer->refer_call->owner;	/* The channel we're about to take over */
+	struct ast_channel *replacecall = replaces_chan;	/* The channel we're about to take over */
 	struct ast_channel *targetcall;		/* The bridge to the take-over target */
 
 	/* Check if we're in ring state */
@@ -25045,8 +24993,6 @@
 			*nounlock = 1;
 			ast_channel_unlock(c);
 		}
-		ast_channel_unlock(replacecall);
-		sip_pvt_unlock(p->refer->refer_call);
 		return 1;
 	}
 	if (!c) {
@@ -25055,15 +25001,13 @@
 		transmit_response_reliable(p, "503 Service Unavailable", req);
 		append_history(p, "Xfer", "INVITE/Replace Failed. No new channel.");
 		sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
-		ast_channel_unlock(replacecall);
-		sip_pvt_unlock(p->refer->refer_call);
 		return 1;
 	}
 	append_history(p, "Xfer", "INVITE/Replace received");
 	/* We have three channels to play with
 		channel c: New incoming call
 		targetcall: Call from PBX to target
-		p->refer->refer_call: SIP pvt dialog from transferer to pbx.
+		replaces_pvt: SIP pvt dialog from transferer to pbx.
 		replacecall: The owner of the previous
 		We need to masq C into refer_call to connect to
 		targetcall;
@@ -25089,7 +25033,7 @@
 
 	/* Make sure that the masq does not free our PVT for the old call */
 	if (! earlyreplace && ! oneleggedreplace )
-		ast_set_flag(&p->refer->refer_call->flags[0], SIP_DEFER_BYE_ON_TRANSFER);	/* Delay hangup */
+		ast_set_flag(&replaces_pvt->flags[0], SIP_DEFER_BYE_ON_TRANSFER);	/* Delay hangup */
 
 	/* Prepare the masquerade - if this does not happen, we will be gone */
 	if(ast_channel_masquerade(replacecall, c))
@@ -25102,9 +25046,7 @@
 	 * it is possible for channel c to be destroyed on us.  To prevent this, we must give c a reference
 	 * before any unlocking takes place and remove it only once we are completely done with it */
 	ast_channel_ref(c);
-	ast_channel_unlock(replacecall);
 	ast_channel_unlock(c);
-	sip_pvt_unlock(p->refer->refer_call);
 	sip_pvt_unlock(p);
 	ast_do_masquerade(replacecall);
 	ast_channel_lock(c);
@@ -25230,7 +25172,6 @@
 	int gotdest;
 	const char *p_replaces;
 	char *replace_id = NULL;
-	int refer_locked = 0;
 	const char *required;
 	unsigned int required_profile = 0;
 	struct ast_channel *c = NULL;		/* New channel */
@@ -25255,6 +25196,8 @@
 	} pickup = {
 			.exten = "",
 	};
+	RAII_VAR(struct sip_pvt *, replaces_pvt, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, replaces_chan, NULL, ao2_cleanup);
 
 	/* Find out what they support */
 	if (!p->sipoptions) {
@@ -25432,45 +25375,41 @@
 		   First we cheat a little and look for a magic call-id from phones that support
 		   dialog-info+xml so we can do technology independent pickup... */
 		if (strncmp(replace_id, "pickup-", 7) == 0) {
-			struct sip_pvt *subscription = NULL;
+			RAII_VAR(struct sip_pvt *, subscription, NULL, ao2_cleanup);
+			RAII_VAR(struct ast_channel *, subscription_chan, NULL, ao2_cleanup);
+
 			replace_id += 7; /* Worst case we are looking at \0 */
 
-			if ((subscription = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
+			if (get_sip_pvt_from_replaces(replace_id, totag, fromtag, &subscription, &subscription_chan)) {
 				ast_log(LOG_NOTICE, "Unable to find subscription with call-id: %s\n", replace_id);
 				transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
 				error = 1;
 			} else {
+				SCOPED_LOCK(lock, subscription, sip_pvt_lock, sip_pvt_unlock);
 				ast_log(LOG_NOTICE, "Trying to pick up %s@%s\n", subscription->exten, subscription->context);
 				ast_copy_string(pickup.exten, subscription->exten, sizeof(pickup.exten));
 				ast_copy_string(pickup.context, subscription->context, sizeof(pickup.context));
-				sip_pvt_unlock(subscription);
-				if (subscription->owner) {
-					ast_channel_unlock(subscription->owner);
-				}
-				subscription = dialog_unref(subscription, "unref dialog subscription");
-			}
-		}
-
-		/* This locks both refer_call pvt and refer_call pvt's owner!!!*/
-		if (!error && ast_strlen_zero(pickup.exten) && (p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
+			}
+		}
+
+		if (!error && ast_strlen_zero(pickup.exten) && get_sip_pvt_from_replaces(replace_id,
+					totag, fromtag, &replaces_pvt, &replaces_chan)) {
 			ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id);
 			transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
 			error = 1;
-		} else {
-			refer_locked = 1;
 		}
 
 		/* The matched call is the call from the transferer to Asterisk .
 			We want to bridge the bridged part of the call to the
 			incoming invite, thus taking over the refered call */
 
-		if (p->refer->refer_call == p) {
+		if (replaces_pvt == p) {
 			ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", replace_id, p->callid);
 			transmit_response_reliable(p, "400 Bad request", req);	/* The best way to not not accept the transfer */
 			error = 1;
 		}
 
-		if (!error && ast_strlen_zero(pickup.exten) && !p->refer->refer_call->owner) {
+		if (!error && ast_strlen_zero(pickup.exten) && !replaces_chan) {
 			/* Oops, someting wrong anyway, no owner, no call */
 			ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", replace_id);
 			/* Check for better return code */
@@ -25478,7 +25417,10 @@
 			error = 1;
 		}
 
-		if (!error && ast_strlen_zero(pickup.exten) && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RINGING && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RING && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_UP) {
+		if (!error && ast_strlen_zero(pickup.exten) &&
+				ast_channel_state(replaces_chan) != AST_STATE_RINGING &&
+				ast_channel_state(replaces_chan) != AST_STATE_RING &&
+				ast_channel_state(replaces_chan) != AST_STATE_UP) {
 			ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id);
 			transmit_response_reliable(p, "603 Declined (Replaces)", req);
 			error = 1;
@@ -25487,15 +25429,6 @@
 		if (error) {	/* Give up this dialog */
 			append_history(p, "Xfer", "INVITE/Replace Failed.");
 			sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
-			sip_pvt_unlock(p);
-			if (p->refer->refer_call) {
-				sip_pvt_unlock(p->refer->refer_call);
-				if (p->refer->refer_call->owner) {
-					ast_channel_unlock(p->refer->refer_call->owner);
-				}
-				p->refer->refer_call = dialog_unref(p->refer->refer_call, "unref dialog p->refer->refer_call");
-			}
-			refer_locked = 0;
 			p->invitestate = INV_COMPLETED;
 			res = INV_REQ_ERROR;
 			check_via(p, req);
@@ -25936,9 +25869,8 @@
 		} else {
 			/* Go and take over the target call */
 			if (sipdebug)
-				ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
-			res = handle_invite_replaces(p, req, addr, seqno, nounlock);
-			refer_locked = 0;
+				ast_debug(4, "Sending this call to the invite/replaces handler %s\n", p->callid);
+			res = handle_invite_replaces(p, req, seqno, nounlock, replaces_pvt, replaces_chan);
 			goto request_invite_cleanup;
 		}
 	}
@@ -26077,13 +26009,6 @@
 
 request_invite_cleanup:
 
-	if (refer_locked && p->refer && p->refer->refer_call) {
-		sip_pvt_unlock(p->refer->refer_call);
-		if (p->refer->refer_call->owner) {
-			ast_channel_unlock(p->refer->refer_call->owner);
-		}
-		p->refer->refer_call = dialog_unref(p->refer->refer_call, "unref dialog p->refer->refer_call");
-	}
 	if (authpeer) {
 		authpeer = sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_invite authpeer");
 	}
@@ -26149,20 +26074,17 @@
  *	If this function is successful, only the transferer pvt lock will remain on return.  Setting nounlock indicates
  *	to handle_request_do() that the pvt's owner it locked does not require an unlock.
  */
-static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, uint32_t seqno, int *nounlock)
-{
-	struct sip_dual target;		/* Chan 1: Call from tranferer to Asterisk */
-					/* Chan 2: Call from Asterisk to target */
-	int res = 0;
-	struct sip_pvt *targetcall_pvt;
-	struct ast_party_connected_line connected_to_transferee;
-	struct ast_party_connected_line connected_to_target;
-	char transferer_linkedid[32];
-	struct ast_channel *chans[2];
+static int local_attended_transfer(struct sip_pvt *transferer, struct ast_channel *transferer_chan, uint32_t seqno, int *nounlock)
+{
+	RAII_VAR(struct sip_pvt *, targetcall_pvt, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel *, targetcall_chan, NULL, ao2_cleanup);
+	enum ast_transfer_result transfer_res;
 
 	/* Check if the call ID of the replaces header does exist locally */
-	if (!(targetcall_pvt = get_sip_pvt_byid_locked(transferer->refer->replaces_callid, transferer->refer->replaces_callid_totag,
-		transferer->refer->replaces_callid_fromtag))) {
+	if (get_sip_pvt_from_replaces(transferer->refer->replaces_callid,
+				transferer->refer->replaces_callid_totag,
+				transferer->refer->replaces_callid_fromtag,
+				&targetcall_pvt, &targetcall_chan)) {
 		if (transferer->refer->localtransfer) {
 			/* We did not find the refered call. Sorry, can't accept then */
 			/* Let's fake a response from someone else in order
@@ -26178,174 +26100,41 @@
 		return 0;
 	}
 
-	/* Ok, we can accept this transfer */
-	append_history(transferer, "Xfer", "Refer accepted");
-	if (!targetcall_pvt->owner) {	/* No active channel */
+	if (!targetcall_chan) {	/* No active channel */
 		ast_debug(4, "SIP attended transfer: Error: No owner of target call\n");
 		/* Cancel transfer */
 		transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE);
 		append_history(transferer, "Xfer", "Refer failed");
 		ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
 		transferer->refer->status = REFER_FAILED;
-		sip_pvt_unlock(targetcall_pvt);
-		if (targetcall_pvt)
-			ao2_t_ref(targetcall_pvt, -1, "Drop targetcall_pvt pointer");
 		return -1;
 	}
 
-	/* We have a channel, find the bridge */
-	target.chan1 = ast_channel_ref(targetcall_pvt->owner);				/* Transferer to Asterisk */
-	target.chan2 = ast_bridged_channel(targetcall_pvt->owner);	/* Asterisk to target */
-	if (target.chan2) {
-		ast_channel_ref(target.chan2);
-	}
-
-	if (!target.chan2 || !(ast_channel_state(target.chan2) == AST_STATE_UP || ast_channel_state(target.chan2) == AST_STATE_RINGING) ) {
-		/* Wrong state of new channel */
-		if (target.chan2)
-			ast_debug(4, "SIP attended transfer: Error: Wrong state of target call: %s\n", ast_state2str(ast_channel_state(target.chan2)));
-		else if (ast_channel_state(target.chan1) != AST_STATE_RING)
-			ast_debug(4, "SIP attended transfer: Error: No target channel\n");
-		else
-			ast_debug(4, "SIP attended transfer: Attempting transfer in ringing state\n");
-	}
-
-	/* Transfer */
-	if (sipdebug) {
-		if (current->chan2)	/* We have two bridges */
-			ast_debug(4, "SIP attended transfer: trying to bridge %s and %s\n", ast_channel_name(target.chan1), ast_channel_name(current->chan2));
-		else			/* One bridge, propably transfer of IVR/voicemail etc */
-			ast_debug(4, "SIP attended transfer: trying to make %s take over (masq) %s\n", ast_channel_name(target.chan1), ast_channel_name(current->chan1));
-	}
-
 	ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER);	/* Delay hangup */
-
-	ast_copy_string(transferer_linkedid, ast_channel_linkedid(transferer->owner), sizeof(transferer_linkedid));
-
-	/* Perform the transfer */
-	chans[0] = transferer->owner;
-	chans[1] = target.chan1;
-	ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans,
-		"TransferMethod: SIP\r\n"
-		"TransferType: Attended\r\n"
-		"Channel: %s\r\n"
-		"Uniqueid: %s\r\n"
-		"SIP-Callid: %s\r\n"
-		"TargetChannel: %s\r\n"
-		"TargetUniqueid: %s\r\n",
-		ast_channel_name(transferer->owner),
-		ast_channel_uniqueid(transferer->owner),
-		transferer->callid,
-		ast_channel_name(target.chan1),
-		ast_channel_uniqueid(target.chan1));
-	ast_party_connected_line_init(&connected_to_transferee);
-	ast_party_connected_line_init(&connected_to_target);
-	/* No need to lock current->chan1 here since it was locked in sipsock_read */
-	ast_party_connected_line_copy(&connected_to_transferee, ast_channel_connected(current->chan1));
-	/* No need to lock target.chan1 here since it was locked in get_sip_pvt_byid_locked */
-	ast_party_connected_line_copy(&connected_to_target, ast_channel_connected(target.chan1));
-	/* Reset any earlier private connected id representation */
-	ast_party_id_reset(&connected_to_transferee.priv);
-	ast_party_id_reset(&connected_to_target.priv);
-	connected_to_target.source = connected_to_transferee.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER;
-	res = attempt_transfer(current, &target);
-	if (res) {
-		/* Failed transfer */
-		transmit_notify_with_sipfrag(transferer, seqno, "486 Busy Here", TRUE);
-		append_history(transferer, "Xfer", "Refer failed");
-		ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER);
-		/* if transfer failed, go ahead and unlock targetcall_pvt and it's owner channel */
-		sip_pvt_unlock(targetcall_pvt);
-		ast_channel_unlock(target.chan1);
-	} else {
-		/* Transfer succeeded! */
-		const char *xfersound = pbx_builtin_getvar_helper(target.chan1, "ATTENDED_TRANSFER_COMPLETE_SOUND");
-
-		/* target.chan1 was locked in get_sip_pvt_byid_locked, do not unlock target.chan1 before this */
-		ast_cel_report_event(target.chan1, AST_CEL_ATTENDEDTRANSFER, NULL, transferer_linkedid, target.chan2);
-
-		/* Tell transferer that we're done. */
+	
+	sip_pvt_unlock(transferer);
+	ast_channel_unlock(transferer_chan);
+	*nounlock = 1;
+
+	transfer_res = ast_bridge_transfer_attended(transferer_chan, targetcall_chan, NULL);
+
+	sip_pvt_lock(transferer);
+
+	switch (transfer_res) {
+	case AST_BRIDGE_TRANSFER_SUCCESS:
 		transmit_notify_with_sipfrag(transferer, seqno, "200 OK", TRUE);
 		append_history(transferer, "Xfer", "Refer succeeded");
 		transferer->refer->status = REFER_200OK;
-		if (target.chan2 && !ast_strlen_zero(xfersound) && ast_streamfile(target.chan2, xfersound, ast_channel_language(target.chan2)) >= 0) {
-			ast_waitstream(target.chan2, "");
-		}
-
-		/* By forcing the masquerade, we know that target.chan1 and target.chan2 are bridged. We then
-		 * can queue connected line updates where they need to go.
-		 *
-		 * before a masquerade, all channel and pvt locks must be unlocked.  Any recursive
-		 * channel locks held before this function invalidates channel container locking order.
-		 * Since we are unlocking both the pvt (transferer) and its owner channel (current.chan1)
-		 * it is possible for current.chan1 to be destroyed in the pbx thread.  To prevent this
-		 * we must give c a reference before any unlocking takes place.
-		 */
-
-		ast_channel_ref(current->chan1);
-		ast_channel_unlock(current->chan1); /* current.chan1 is p->owner before the masq, it was locked by socket_read()*/
-		ast_channel_unlock(target.chan1);
-		*nounlock = 1;  /* we just unlocked the dialog's channel and have no plans of locking it again. */
-		sip_pvt_unlock(targetcall_pvt);
-		sip_pvt_unlock(transferer);
-
-		ast_do_masquerade(target.chan1);
-
-		ast_indicate(target.chan1, AST_CONTROL_UNHOLD);
-		if (target.chan2) {
-			ast_indicate(target.chan2, AST_CONTROL_UNHOLD);
-		}
-
-		if (current->chan2 && ast_channel_state(current->chan2) == AST_STATE_RING) {
-			ast_indicate(target.chan1, AST_CONTROL_RINGING);
-		}
-
-		if (target.chan2) {
-			ast_channel_queue_connected_line_update(target.chan1, &connected_to_transferee, NULL);
-			ast_channel_queue_connected_line_update(target.chan2, &connected_to_target, NULL);
-		} else {
-			/* Since target.chan1 isn't actually connected to another channel, there is no way for us
-			 * to queue a frame so that its connected line status will be updated.
-			 *
-			 * Instead, we use the somewhat hackish approach of using a special control frame type that
-			 * instructs ast_read to perform a specific action. In this case, the frame we queue tells
-			 * ast_read to call the connected line interception macro configured for target.chan1.
-			 */
-			struct ast_control_read_action_payload *frame_payload;
-			int payload_size;
-			int frame_size;
-			unsigned char connected_line_data[1024];
-			payload_size = ast_connected_line_build_data(connected_line_data,
-				sizeof(connected_line_data), &connected_to_target, NULL);
-			frame_size = payload_size + sizeof(*frame_payload);
-			if (payload_size != -1) {
-				frame_payload = ast_alloca(frame_size);
-				frame_payload->payload_size = payload_size;
-				memcpy(frame_payload->payload, connected_line_data, payload_size);
-				frame_payload->action = AST_FRAME_READ_ACTION_CONNECTED_LINE_MACRO;
-				ast_queue_control_data(target.chan1, AST_CONTROL_READ_ACTION, frame_payload, frame_size);
-			}
-			/* In addition to queueing the read action frame so that target.chan1's connected line info
-			 * will be updated, we also are going to queue a plain old connected line update on target.chan1. This
-			 * way, either Dial or Queue can apply this connected line update to the outgoing ringing channel.
-			 */
-			ast_channel_queue_connected_line_update(target.chan1, &connected_to_transferee, NULL);
-
-		}
-		sip_pvt_lock(transferer); /* the transferer pvt is expected to remain locked on return */
-
-		ast_channel_unref(current->chan1);
-	}
+		break;
+	case AST_BRIDGE_TRANSFER_FAIL:
+	case AST_BRIDGE_TRANSFER_INVALID:
+	case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+	default:
+		break;
+	}
+	/* Tell transferer that we're done. */
 
 	/* at this point if the transfer is successful only the transferer pvt should be locked. */
-	ast_party_connected_line_free(&connected_to_target);
-	ast_party_connected_line_free(&connected_to_transferee);
-	ast_channel_unref(target.chan1);
-	if (target.chan2) {
-		ast_channel_unref(target.chan2);
-	}
-	if (targetcall_pvt)
-		ao2_t_ref(targetcall_pvt, -1, "drop targetcall_pvt");
 	return 1;
 }
 
@@ -26606,7 +26395,7 @@
 	/* Attended transfer: Find all call legs and bridge transferee with target*/
 	if (p->refer->attendedtransfer) {
 		/* both p and p->owner _MUST_ be locked while calling local_attended_transfer */
-		if ((res = local_attended_transfer(p, &current, req, seqno, nounlock))) {
+		if ((res = local_attended_transfer(p, current.chan1, seqno, nounlock))) {
 			goto handle_refer_cleanup; /* We're done with the transfer */
 		}
 		/* Fall through for remote transfers that we did not find locally */

Modified: team/mmichelson/sip_attended_transfer/channels/sip/include/sip.h
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/sip_attended_transfer/channels/sip/include/sip.h?view=diff&rev=387621&r1=387620&r2=387621
==============================================================================
--- team/mmichelson/sip_attended_transfer/channels/sip/include/sip.h (original)
+++ team/mmichelson/sip_attended_transfer/channels/sip/include/sip.h Fri May  3 15:29:46 2013
@@ -945,10 +945,6 @@
 		AST_STRING_FIELD(replaces_callid_totag);   /*!< Replace info: to-tag */
 		AST_STRING_FIELD(replaces_callid_fromtag); /*!< Replace info: from-tag */
 	);
-	struct sip_pvt *refer_call;                     /*!< Call we are referring. This is just a reference to a
-							 * dialog owned by someone else, so we should not destroy
-							 * it when the sip_refer object goes.
-							 */
 	int attendedtransfer;                           /*!< Attended or blind transfer? */
 	int localtransfer;                              /*!< Transfer to local domain? */
 	enum referstatus status;                        /*!< REFER status */




More information about the asterisk-commits mailing list