Add attended transfer support for chan_sip.c
authorMark Michelson <mmichelson@digium.com>
Tue, 28 May 2013 15:26:15 +0000 (15:26 +0000)
committerMark Michelson <mmichelson@digium.com>
Tue, 28 May 2013 15:26:15 +0000 (15:26 +0000)
This now uses the core API for performing attended transfers.

Review https://reviewboard.asterisk.org/r/2513

(Closes issue ASTERISK-21520)
reported by Matt Jordan

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389869 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_sip.c
channels/sip/include/sip.h

index 5a4dec2..fe67d28 100644 (file)
@@ -1200,7 +1200,8 @@ static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_reque
 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_pickup_thread(void *stuff);
@@ -1271,8 +1272,6 @@ static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt);
 /* static int sip_addrcmp(char *name, struct sockaddr_in *sin);        Support for peer matching */
 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);
@@ -1475,9 +1474,10 @@ static int handle_request_message(struct sip_pvt *p, struct sip_request *req, st
 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,
+               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);
@@ -6654,9 +6654,6 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
                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;
@@ -17811,10 +17808,25 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_re
        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 = {
@@ -17830,22 +17842,20 @@ static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *t
        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
@@ -17864,11 +17874,10 @@ static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *t
                        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,
@@ -17879,7 +17888,7 @@ static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *t
                                                  sip_pvt_ptr->outgoing_call == TRUE ? "OUTGOING": "INCOMING", sip_pvt_ptr->callid,
                                                  totag, sip_pvt_ptr->tag);
                                }
-                               return NULL;
+                               return -1;
                        }
                }
 
@@ -17888,15 +17897,13 @@ static struct sip_pvt *get_sip_pvt_byid_locked(const char *callid, const char *t
                                          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);
+               *out_pvt = sip_pvt_ptr;
+               if (out_chan) {
+                       *out_chan = sip_pvt_ptr->owner ? ast_channel_ref(sip_pvt_ptr->owner) : NULL;
                }
        }
 
-       return sip_pvt_ptr;
+       return 0;
 }
 
 /*! \brief Call transfer support (the REFER method)
@@ -24451,90 +24458,6 @@ static int sip_pickup(struct ast_channel *chan)
        return 0;
 }
 
-
-/*! \brief Turn off generator data
-       XXX Does this function belong in the SIP channel?
-*/
-static void ast_quiet_chan(struct ast_channel *chan)
-{
-       if (chan && ast_channel_state(chan) == AST_STATE_UP) {
-               if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_MOH))
-                       ast_moh_stop(chan);
-               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
  *
  * \return Returns the pointer to the provided tag buffer,
@@ -24850,132 +24773,68 @@ static int handle_request_options(struct sip_pvt *p, struct sip_request *req, st
 }
 
 /*! \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[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,
+               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 *targetcall;         /* The bridge to the take-over target */
-
-       /* Check if we're in ring state */
-       if (ast_channel_state(replacecall) == AST_STATE_RING)
-               earlyreplace = 1;
-
-       /* Check if we have a bridge */
-       if (!(targetcall = ast_bridged_channel(replacecall))) {
-               /* We have no bridge */
-               if (!earlyreplace) {
-                       ast_debug(2, "  Attended transfer attempted to replace call with no bridge (maybe ringing). Channel %s!\n", ast_channel_name(replacecall));
-                       oneleggedreplace = 1;
-               }
-       }
-       if (targetcall && ast_channel_state(targetcall) == AST_STATE_RINGING)
-               ast_debug(4, "SIP transfer: Target channel is in ringing state\n");
-
-       if (targetcall)
-               ast_debug(4, "SIP transfer: Invite Replace incoming channel should bridge to channel %s while hanging up channel %s\n", ast_channel_name(targetcall), ast_channel_name(replacecall));
-       else
-               ast_debug(4, "SIP transfer: Invite Replace incoming channel should replace and hang up channel %s (one call leg)\n", ast_channel_name(replacecall));
+       RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel *, c, NULL, ao2_cleanup);
 
        if (req->ignore) {
-               ast_log(LOG_NOTICE, "Ignoring this INVITE with replaces in a stupid way.\n");
-               /* We should answer something here. If we are here, the
-                       call we are replacing exists, so an accepted
-                       can't harm */
-               transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE, FALSE);
-               /* Do something more clever here */
-               if (c) {
-                       *nounlock = 1;
-                       ast_channel_unlock(c);
-               }
-               ast_channel_unlock(replacecall);
-               sip_pvt_unlock(p->refer->refer_call);
-               return 1;
+               return 0;
        }
-       if (!c) {
+
+       if (!p->owner) {
                /* What to do if no channel ??? */
                ast_log(LOG_ERROR, "Unable to create new channel.  Invite/replace failed.\n");
                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.
-               replacecall: The owner of the previous
-               We need to masq C into refer_call to connect to
-               targetcall;
-               If we are talking to internal audio stream, target call is null.
-       */
+
+       c = ast_channel_ref(p->owner);
 
        /* Fake call progress */
        transmit_response(p, "100 Trying", req);
        ast_setstate(c, AST_STATE_RING);
 
-       /* Masquerade the new call into the referred call to connect to target call
-          Targetcall is not touched by the masq */
+       ast_debug(4, "Invite/Replaces: preparing to replace %s with %s\n", ast_channel_name(replaces_chan), ast_channel_name(c));
 
-       /* Answer the incoming call and set channel to UP state */
-       transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE, FALSE);
-
-       ast_setstate(c, AST_STATE_UP);
+       *nounlock = 1;
+       ast_channel_unlock(c);
+       sip_pvt_unlock(p);
 
-       /* Stop music on hold and other generators */
-       ast_quiet_chan(replacecall);
-       ast_quiet_chan(targetcall);
-       ast_debug(4, "Invite/Replaces: preparing to masquerade %s into %s\n", ast_channel_name(c), ast_channel_name(replacecall));
+       ast_raw_answer(c, 1);
 
-       /* 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_channel_lock(replaces_chan);
+       bridge = ast_channel_get_bridge(replaces_chan);
+       ast_channel_unlock(replaces_chan);
 
-       /* Prepare the masquerade - if this does not happen, we will be gone */
-       if(ast_channel_masquerade(replacecall, c))
-               ast_log(LOG_ERROR, "Failed to masquerade C into Replacecall\n");
-       else
-               ast_debug(4, "Invite/Replaces: Going to masquerade %s into %s\n", ast_channel_name(c), ast_channel_name(replacecall));
-
-       /* C should now be in place of replacecall. all channel locks and pvt locks should be removed
-        * before issuing the masq.  Since we are unlocking both the pvt (p) and its owner channel (c)
-        * 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);
-       if (earlyreplace || oneleggedreplace ) {
-               ast_channel_hangupcause_set(c, AST_CAUSE_SWITCH_CONGESTION);
+       if (bridge) {
+               ast_bridge_impart(bridge, c, replaces_chan, NULL, 1);
+       } else {
+               ast_channel_move(replaces_chan, c);
+               ast_hangup(c);
        }
-       ast_setstate(c, AST_STATE_DOWN);
-       ast_channel_unlock(c);
-
-       /* c and c's tech pvt must be unlocked at this point for ast_hangup */
-       ast_hangup(c);
-       /* this indicates to handle_request_do that the owner channel has already been unlocked */
-       *nounlock = 1;
-       /* lock PVT structure again after hangup */
        sip_pvt_lock(p);
-       ast_channel_unref(c);
        return 0;
 }
 
@@ -25085,7 +24944,6 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
        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 */
@@ -25110,6 +24968,8 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
        } 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) {
@@ -25287,45 +25147,41 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
                   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 */
@@ -25333,7 +25189,10 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
                        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;
@@ -25342,15 +25201,6 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
                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);
@@ -25791,9 +25641,8 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
                } 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, nounlock, replaces_pvt, replaces_chan);
                        goto request_invite_cleanup;
                }
        }
@@ -25932,13 +25781,6 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
 
 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");
        }
@@ -26004,24 +25846,17 @@ static void parse_oli(struct sip_request *req, struct ast_channel *chan)
  *     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.
  */
-
-/* XXX XXX XXX XXX XXX XXX
- * This function is COMPLETELY broken at the moment. It *will* crash if called
- */
-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)
 {
-       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];
+       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
@@ -26037,174 +25872,51 @@ static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *
                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");
+       sip_pvt_unlock(transferer);
+       ast_channel_unlock(transferer_chan);
+       *nounlock = 1;
 
-               /* 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);
+       transfer_res = ast_bridge_transfer_attended(transferer_chan, targetcall_chan);
 
-               /* Tell transferer that we're done. */
+       sip_pvt_lock(transferer);
+
+       switch (transfer_res) {
+       case AST_BRIDGE_TRANSFER_SUCCESS:
+               transferer->refer->status = REFER_200OK;
                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);
+               return 1;
+       case AST_BRIDGE_TRANSFER_FAIL:
+               transferer->refer->status = REFER_FAILED;
+               transmit_notify_with_sipfrag(transferer, seqno, "500 Internal Server Error", TRUE);
+               append_history(transferer, "Xfer", "Refer failed (internal error)");
+               return -1;
+       case AST_BRIDGE_TRANSFER_INVALID:
+               transferer->refer->status = REFER_FAILED;
+               transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE);
+               append_history(transferer, "Xfer", "Refer failed (invalid bridge state)");
+               return -1;
+       case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+               transferer->refer->status = REFER_FAILED;
+               transmit_notify_with_sipfrag(transferer, seqno, "403 Forbidden", TRUE);
+               append_history(transferer, "Xfer", "Refer failed (operation not permitted)");
+               return -1;
+       default:
+               break;
        }
 
-       /* 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;
 }
 
@@ -26438,7 +26150,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
        /* 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, NULL, req, seqno, nounlock))) {
+               if ((res = local_attended_transfer(p, transferer, seqno, nounlock))) {
                        ast_clear_flag(&p->flags[0], SIP_GOTREFER);
                        return res;
                }
index 07f233f..185f393 100644 (file)
@@ -834,16 +834,6 @@ struct sip_request {
  */
 #define REQ_OFFSET_TO_STR(req,offset) (ast_str_buffer((req)->data) + ((req)->offset))
 
-/*! \brief structure used in transfers */
-struct sip_dual {
-       struct ast_channel *chan1;   /*!< First channel involved */
-       struct ast_channel *chan2;   /*!< Second channel involved */
-       struct sip_request req;      /*!< Request that caused the transfer (REFER) */
-       uint32_t seqno;              /*!< Sequence number */
-       char *park_exten;
-       char *park_context;
-};
-
 /*! \brief Parameters to the transmit_invite function */
 struct sip_invite_param {
        int addsipheaders;          /*!< Add extra SIP headers */
@@ -935,10 +925,6 @@ struct sip_refer {
                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 */