core/AmSipDialog.cpp
7c964b9b
 /*
  * Copyright (C) 2002-2003 Fhg Fokus
  *
7dcb7e2a
  * This file is part of SEMS, a free SIP media server.
7c964b9b
  *
7dcb7e2a
  * SEMS is free software; you can redistribute it and/or modify
7c964b9b
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
7dcb7e2a
  * (at your option) any later version. This program is released under
  * the GPL with the additional exemption that compiling, linking,
  * and/or using OpenSSL is allowed.
7c964b9b
  *
7dcb7e2a
  * For a license to use the SEMS software under conditions
7c964b9b
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
7dcb7e2a
  * SEMS is distributed in the hope that it will be useful,
7c964b9b
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License 
  * along with this program; if not, write to the Free Software 
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
37abd537
 #include "AmSipDialog.h"
 #include "AmConfig.h"
 #include "AmSession.h"
 #include "AmUtils.h"
80461b82
 #include "AmSipHeaders.h"
00aa71fa
 #include "SipCtrlInterface.h"
dbbdd8c6
 #include "sems.h"
37abd537
 
c60b508e
 #include "sip/parse_route.h"
 #include "sip/parse_uri.h"
50727c62
 #include "sip/parse_next_hop.h"
c60b508e
 
42e1d05c
 #include "AmB2BMedia.h" // just because of statistics
 
 static void addTranscoderStats(string &hdrs)
 {
   // add transcoder statistics into request/reply headers
   if (!AmConfig::TranscoderOutStatsHdr.empty()) {
     string usage;
     B2BMediaStatistics::instance()->reportCodecWriteUsage(usage);
 
     hdrs += AmConfig::TranscoderOutStatsHdr + ": ";
     hdrs += usage;
     hdrs += CRLF;
   }
   if (!AmConfig::TranscoderInStatsHdr.empty()) {
     string usage;
     B2BMediaStatistics::instance()->reportCodecReadUsage(usage);
 
     hdrs += AmConfig::TranscoderInStatsHdr + ": ";
     hdrs += usage;
     hdrs += CRLF;
   }
 }
 
39131cb0
 AmSipDialog::AmSipDialog(AmSipDialogEventHandler* h)
d85ed5c7
   : AmBasicSipDialog(h),oa(this),rel100(this,h),
cc5676b8
     offeranswer_enabled(true),
24d5489f
     early_session_started(false),session_started(false),
5767cec5
     pending_invites(0),
440323b8
     sdp_local(), sdp_remote()
39131cb0
 {
 }
 
37abd537
 AmSipDialog::~AmSipDialog()
 {
 }
 
d85ed5c7
 bool AmSipDialog::onRxReqSanity(const AmSipRequest& req)
37abd537
 {
d85ed5c7
   if (req.method == SIP_METH_ACK) {
9d6f495d
     if(onRxReqStatus(req) && hdl)
       hdl->onSipRequest(req);
     return false;
d85ed5c7
   }
aacd0b98
 
d85ed5c7
   if (req.method == SIP_METH_CANCEL) {
 
     if (uas_trans.find(req.cseq) == uas_trans.end()) {
       reply_error(req,481,SIP_REPLY_NOT_EXIST);
       return false;
b5a12f33
     }
04f517d7
 
b7cae467
     if(onRxReqStatus(req) && hdl)
       hdl->onSipRequest(req);
 
     return false;
d85ed5c7
   }
577c62a1
 
d85ed5c7
   if(!AmBasicSipDialog::onRxReqSanity(req))
     return false;
cc5676b8
 
d85ed5c7
   if (req.method == SIP_METH_INVITE) {
     bool pending = pending_invites;
     if (offeranswer_enabled) {
       // not sure this is needed here: could be in AmOfferAnswer as well
       pending |= ((oa.getState() != AmOfferAnswer::OA_None) &&
 		  (oa.getState() != AmOfferAnswer::OA_Completed));
b5a12f33
     }
177a7a8f
 
d85ed5c7
     if (pending) {
       reply_error(req, 491, SIP_REPLY_PENDING,
 		  SIP_HDR_COLSP(SIP_HDR_RETRY_AFTER) 
 		  + int2str(get_random() % 10) + CRLF);
       return false;
b5a12f33
     }
d85ed5c7
 
     pending_invites++;
dea103ae
   }
 
d85ed5c7
   return rel100.onRequestIn(req);
 }
 
 bool AmSipDialog::onRxReqStatus(const AmSipRequest& req)
 {
177a7a8f
   switch(status){
   case Disconnected:
d97edca7
     if(req.method == SIP_METH_INVITE)
1f251121
       setStatus(Trying);
177a7a8f
     break;
   case Connected:
d97edca7
     if(req.method == SIP_METH_BYE)
1f251121
       setStatus(Disconnecting);
177a7a8f
     break;
 
   case Trying:
   case Proceeding:
   case Early:
d97edca7
     if(req.method == SIP_METH_BYE)
1f251121
       setStatus(Disconnecting);
d97edca7
     else if(req.method == SIP_METH_CANCEL){
1f251121
       setStatus(Cancelling);
d97edca7
       reply(req,200,"OK");
     }
177a7a8f
     break;
 
   default: break;
   }
cc5676b8
 
d85ed5c7
   bool cont = true;
cc5676b8
   if (offeranswer_enabled) {
d85ed5c7
     cont = (oa.onRequestIn(req) == 0);
cc5676b8
   }
f0ad912e
 
d85ed5c7
   return cont;
b5a12f33
 }
16955079
 
618f6371
 int AmSipDialog::onSdpCompleted()
 {
9fce821e
   if(!hdl) return 0;
 
d85ed5c7
   int ret = ((AmSipDialogEventHandler*)hdl)->
     onSdpCompleted(oa.getLocalSdp(), oa.getRemoteSdp());
 
618f6371
   if(!ret) {
577c62a1
     sdp_local = oa.getLocalSdp();
     sdp_remote = oa.getRemoteSdp();
618f6371
 
24d5489f
     if((getStatus() == Early) && !early_session_started) {
d85ed5c7
       ((AmSipDialogEventHandler*)hdl)->onEarlySessionStart();
24d5489f
       early_session_started = true;
     }
 
     if((getStatus() == Connected) && !session_started) {
d85ed5c7
       ((AmSipDialogEventHandler*)hdl)->onSessionStart();
24d5489f
       session_started = true;
618f6371
     }
   }
577c62a1
   else {
     oa.clear();
   }
24d5489f
 
618f6371
   return ret;
 }
 
577c62a1
 bool AmSipDialog::getSdpOffer(AmSdp& offer)
177a7a8f
 {
9fce821e
   if(!hdl) return false;
d85ed5c7
   return ((AmSipDialogEventHandler*)hdl)->getSdpOffer(offer);
577c62a1
 }
40db2c5a
 
577c62a1
 bool AmSipDialog::getSdpAnswer(const AmSdp& offer, AmSdp& answer)
 {
9fce821e
   if(!hdl) return false;
d85ed5c7
   return ((AmSipDialogEventHandler*)hdl)->getSdpAnswer(offer,answer);
40db2c5a
 }
 
d194acbb
 AmOfferAnswer::OAState AmSipDialog::getOAState() {
   return oa.getState();
 }
 
 void AmSipDialog::setOAState(AmOfferAnswer::OAState n_st) {
   oa.setState(n_st);
 }
 
 void AmSipDialog::setRel100State(Am100rel::State rel100_state) {
   DBG("setting 100rel state for '%s' to %i\n", local_tag.c_str(), rel100_state);
   rel100.setState(rel100_state);
 }
 
cc5676b8
 void AmSipDialog::setOAEnabled(bool oa_enabled) {
   DBG("%sabling offer_answer on SIP dialog '%s'\n",
       oa_enabled?"en":"dis", local_tag.c_str());
   offeranswer_enabled = oa_enabled;
 }
 
d85ed5c7
 int AmSipDialog::onTxRequest(AmSipRequest& req, int& flags)
4e6ce9cb
 {
d85ed5c7
   rel100.onRequestOut(req);
 
   if (offeranswer_enabled && oa.onRequestOut(req))
     return -1;
 
   if(AmBasicSipDialog::onTxRequest(req,flags) < 0)
     return -1;
 
   // add transcoder statistics into request headers
   addTranscoderStats(req.hdrs);
4e6ce9cb
 
618f6371
   if((req.method == SIP_METH_INVITE) && (status == Disconnected)){
1f251121
     setStatus(Trying);
618f6371
   }
   else if((req.method == SIP_METH_BYE) && (status != Disconnecting)){
1f251121
     setStatus(Disconnecting);
618f6371
   }
 
d85ed5c7
   if ((req.method == SIP_METH_BYE) || (req.method == SIP_METH_CANCEL)) {
     flags |= SIP_FLAGS_NOCONTACT;
   }
 
177a7a8f
   return 0;
04d9fe08
 }
 
 // UAS behavior for locally sent replies
d85ed5c7
 int AmSipDialog::onTxReply(const AmSipRequest& req, AmSipReply& reply, int& flags)
37abd537
 {
d85ed5c7
   if (offeranswer_enabled) {
     if(oa.onReplyOut(reply) < 0)
       return -1;
   }
 
   rel100.onReplyOut(reply);
 
177a7a8f
   // update Dialog status
7c964b9b
   switch(status){
37abd537
 
177a7a8f
   case Connected:
   case Disconnected:
     break;
7c964b9b
 
04d9fe08
   case Cancelling:
d85ed5c7
     if( (reply.cseq_method == SIP_METH_INVITE) &&
74b671fa
 	(reply.code < 200) ) {
       // refuse local provisional replies 
       // when state is Cancelling
577c62a1
       ERROR("refuse local provisional replies when state is Cancelling\n");
74b671fa
       return -1;
     }
     // else continue with final
     // reply processing
04d9fe08
   case Proceeding:
   case Trying:
   case Early:
d85ed5c7
     if(reply.cseq_method == SIP_METH_INVITE){
177a7a8f
       if(reply.code < 200) {
1f251121
 	setStatus(Early);
04d9fe08
       }
177a7a8f
       else if(reply.code < 300)
1f251121
 	setStatus(Connected);
7c964b9b
       else
1f251121
 	setStatus(Disconnected);
7c964b9b
     }
     break;
04d9fe08
 
7c964b9b
   case Disconnecting:
d85ed5c7
     if(reply.cseq_method == SIP_METH_BYE){
04d9fe08
 
       // Only reason for refusing a BYE: 
       //  authentication (NYI at this place)
       // Also: we should not send provisionnal replies to a BYE
177a7a8f
       if(reply.code >= 200)
1f251121
 	setStatus(Disconnected);
37abd537
     }
7c964b9b
     break;
04d9fe08
 
   default:
     assert(0);
     break;
7c964b9b
   }
37abd537
 
d85ed5c7
   // add transcoder statistics into reply headers
   addTranscoderStats(reply.hdrs);
 
ddd78bc9
   // target-refresh requests and their replies need to contain Contact (1xx
   // replies only those establishing dialog, take care about them?)
   if(reply.cseq_method != SIP_METH_INVITE && 
      reply.cseq_method != SIP_METH_UPDATE) {
d85ed5c7
     
     flags |= SIP_FLAGS_NOCONTACT;
   }
 
   return AmBasicSipDialog::onTxReply(req,reply,flags);
37abd537
 }
d85ed5c7
 
 void AmSipDialog::onReplyTxed(const AmSipRequest& req, const AmSipReply& reply)
37abd537
 {
d85ed5c7
   AmBasicSipDialog::onReplyTxed(req,reply);
 
   if (offeranswer_enabled) {
     oa.onReplySent(reply);
7c964b9b
   }
c60b508e
 
d85ed5c7
   if (reply.code >= 200) {
     if(reply.cseq_method == SIP_METH_INVITE)
 	pending_invites--;
   }
 }
37abd537
 
d85ed5c7
 void AmSipDialog::onRequestTxed(const AmSipRequest& req)
 {
   AmBasicSipDialog::onRequestTxed(req);
577c62a1
 
d85ed5c7
   if (offeranswer_enabled) {
     oa.onRequestSent(req);
   }
 }
37abd537
 
4960be4d
 bool AmSipDialog::onRxReplyStatus(const AmSipReply& reply)
d85ed5c7
 {
7c964b9b
   // rfc3261 12.1
   // Dialog established only by 101-199 or 2xx 
   // responses to INVITE
37abd537
 
c0f8a07f
   if(reply.cseq_method == SIP_METH_INVITE) {
b24f51ef
 
04d9fe08
     switch(status){
7e57a1b1
 
04d9fe08
     case Trying:
     case Proceeding:
       if(reply.code < 200){
ec9467b8
 	if(reply.code == 100 || reply.to_tag.empty())
1f251121
 	  setStatus(Proceeding);
c0f8a07f
 	else {
1f251121
 	  setStatus(Early);
db3a4483
 	  setRemoteTag(reply.to_tag);
 	  setRouteSet(reply.route);
c0f8a07f
 	}
04d9fe08
       }
       else if(reply.code < 300){
1f251121
 	setStatus(Connected);
db3a4483
 	setRouteSet(reply.route);
04d9fe08
 	if(reply.to_tag.empty()){
 	  DBG("received 2xx reply without to-tag "
 	      "(callid=%s): sending BYE\n",reply.callid.c_str());
7c964b9b
 
ee95971d
 	  send_200_ack(reply.cseq);
8505a938
 	  sendRequest(SIP_METH_BYE);
04d9fe08
 	}
 	else {
db3a4483
 	  setRemoteTag(reply.to_tag);
04d9fe08
 	}
       }
727a487c
 
       if(reply.code >= 300) {// error reply
1f251121
 	setStatus(Disconnected);
ec9467b8
 	setRemoteTag(reply.to_tag);
8e7cef47
       }
04d9fe08
       break;
 
     case Early:
       if(reply.code < 200){
       }
       else if(reply.code < 300){
1f251121
 	setStatus(Connected);
db3a4483
 	setRouteSet(reply.route);
0349c439
 	if(reply.to_tag.empty()){
 	  DBG("received 2xx reply without to-tag "
 	      "(callid=%s): sending BYE\n",reply.callid.c_str());
37abd537
 
0349c439
 	  sendRequest(SIP_METH_BYE);
 	}
 	else {
db3a4483
 	  setRemoteTag(reply.to_tag);
0349c439
 	}
04d9fe08
       }
8e7cef47
       else { // error reply
1f251121
 	setStatus(Disconnected);
ec9467b8
 	setRemoteTag(reply.to_tag);
8e7cef47
       }
04d9fe08
       break;
37abd537
 
04d9fe08
     case Cancelling:
c0f8a07f
       if(reply.code >= 300){
 	// CANCEL accepted
a9c7556c
 	DBG("CANCEL accepted, status -> Disconnected\n");
1f251121
 	setStatus(Disconnected);
7c964b9b
       }
915472ea
       else if(reply.code < 300){
c0f8a07f
 	// CANCEL rejected
a9c7556c
 	DBG("CANCEL rejected/too late - bye()\n");
ec9467b8
 	setRemoteTag(reply.to_tag);
38698f88
 	bye();
 	// if BYE could not be sent,
 	// there is nothing we can do anymore...
7c964b9b
       }
04d9fe08
       break;
7c964b9b
 
ae356a12
     //case Connected: // late 200...
     //  TODO: if reply.to_tag != getRemoteTag()
     //        -> ACK + BYE (+absorb answer)
04d9fe08
     default:
       break;
37abd537
     }
7c964b9b
   }
37abd537
 
177a7a8f
   if(status == Disconnecting){
d85ed5c7
 
     DBG("?Disconnecting?: cseq_method = %s; code = %i\n",
 	reply.cseq_method.c_str(), reply.code);
 
     if((reply.cseq_method == SIP_METH_BYE) && (reply.code >= 200)){
177a7a8f
       //TODO: support the auth case here (401/403)
1f251121
       setStatus(Disconnected);
177a7a8f
     }
   }
 
cc5676b8
   if (offeranswer_enabled) {
     oa.onReplyIn(reply);
   }
618f6371
 
d85ed5c7
   bool cont = true;
   if( (reply.code >= 200) && (reply.code < 300) &&
       (reply.cseq_method == SIP_METH_INVITE) ) {
eba6b14d
 
9fce821e
     if(hdl) ((AmSipDialogEventHandler*)hdl)->onInvite2xx(reply);
f0ad912e
 
d85ed5c7
   } else {
d067c34b
     cont = AmBasicSipDialog::onRxReplyStatus(reply);
25875e49
   }
37abd537
 
d85ed5c7
   return cont && rel100.onReplyIn(reply);
 }
   
7cae214d
 void AmSipDialog::uasTimeout(AmSipTimeoutEvent* to_ev)
 {
   assert(to_ev);
 
   switch(to_ev->type){
6d27feb3
   case AmSipTimeoutEvent::noACK:
     DBG("Timeout: missing ACK\n");
cc5676b8
     if (offeranswer_enabled) {
       oa.onNoAck(to_ev->cseq);
     }
d85ed5c7
     if(hdl) ((AmSipDialogEventHandler*)hdl)->onNoAck(to_ev->cseq);
7cae214d
     break;
 
   case AmSipTimeoutEvent::noPRACK:
     DBG("Timeout: missing PRACK\n");
4822be21
     rel100.onTimeout(to_ev->req, to_ev->rpl);
7cae214d
     break;
 
   case AmSipTimeoutEvent::_noEv:
   default:
     break;
   };
   
   to_ev->processed = true;
 }
 
90cccb33
 bool AmSipDialog::getUACInvTransPending() {
   for (TransMap::iterator it=uac_trans.begin();
        it != uac_trans.end(); it++) {
fbc489ba
     if (it->second.method == SIP_METH_INVITE)
90cccb33
       return true;
   }
   return false;
 }
 
d85ed5c7
 AmSipRequest* AmSipDialog::getUASPendingInv()
fbc489ba
 {
   for (TransMap::iterator it=uas_trans.begin();
        it != uas_trans.end(); it++) {
     if (it->second.method == SIP_METH_INVITE)
       return &(it->second);
   }
   return NULL;
 }
 
9b184809
 int AmSipDialog::bye(const string& hdrs, int flags)
37abd537
 {
778da1ad
     switch(status){
04d9fe08
 
778da1ad
     case Disconnecting:
679ade9e
     case Connected: {
       // collect INVITE UAC transactions
       vector<unsigned int> ack_trans;
       for (TransMap::iterator it=uac_trans.begin(); it != uac_trans.end(); it++) {
1f251121
 	if (it->second.method == SIP_METH_INVITE){
679ade9e
 	  ack_trans.push_back(it->second.cseq);
a56276c6
 	}
679ade9e
       }
       // finish any UAC transaction before sending BYE
       for (vector<unsigned int>::iterator it=
 	     ack_trans.begin(); it != ack_trans.end(); it++) {
 	send_200_ack(*it);
       }
 
       if (status != Disconnecting) {
1f251121
 	setStatus(Disconnected);
d85ed5c7
 	return sendRequest(SIP_METH_BYE, NULL, hdrs, flags);
679ade9e
       } else {
 	return 0;
       }
     }
04d9fe08
 
     case Trying:
     case Proceeding:
     case Early:
3a76c904
 	if(getUACInvTransPending())
778da1ad
 	    return cancel();
91956dcc
 	else {  
 	    for (TransMap::iterator it=uas_trans.begin();
 		 it != uas_trans.end(); it++) {
d85ed5c7
 	      if (it->second.method == SIP_METH_INVITE){
91956dcc
 		// let quit this call by sending final reply
52b10ad7
 		return reply(it->second,
 			     487,"Request terminated");
91956dcc
 	      }
 	    }
 
778da1ad
 	    // missing AmSipRequest to be able
 	    // to send the reply on behalf of the app.
76c86043
 	    ERROR("ignoring bye() in %s state: "
8ffa7342
 		  "no UAC transaction to cancel or UAS transaction to reply.\n",
76c86043
 		  getStatusStr());
1f251121
 	    setStatus(Disconnected);
778da1ad
 	}
 	return 0;
0349c439
 
8ffa7342
     case Cancelling:
       for (TransMap::iterator it=uas_trans.begin();
 	   it != uas_trans.end(); it++) {
 	if (it->second.method == SIP_METH_INVITE){
 	  // let's quit this call by sending final reply
 	  return reply(it->second, 487,"Request terminated");
 	}
       }
 
       // missing AmSipRequest to be able
       // to send the reply on behalf of the app.
       ERROR("ignoring bye() in %s state: no UAS transaction to reply",getStatusStr());
       setStatus(Disconnected);
       return 0;
 
778da1ad
     default:
0349c439
         DBG("bye(): we are not connected "
a56276c6
 	    "(status=%s). do nothing!\n",getStatusStr());
778da1ad
 	return 0;
     }	
37abd537
 }
 
 int AmSipDialog::reinvite(const string& hdrs,  
e904822b
 			  const AmMimeBody* body,
cc314367
 			  int flags)
37abd537
 {
1f251121
   if(getStatus() == Connected) {
d85ed5c7
     return sendRequest(SIP_METH_INVITE, body, hdrs, flags);
04d9fe08
   }
   else {
7c964b9b
     DBG("reinvite(): we are not connected "
35938b4f
 	"(status=%s). do nothing!\n",getStatusStr());
04d9fe08
   }
 
   return -1;
37abd537
 }
 
4e6ce9cb
 int AmSipDialog::invite(const string& hdrs,  
e904822b
 			const AmMimeBody* body)
4e6ce9cb
 {
1f251121
   if(getStatus() == Disconnected) {
d85ed5c7
     int res = sendRequest(SIP_METH_INVITE, body, hdrs);
35938b4f
     DBG("TODO: is status already 'trying'? status=%s\n",getStatusStr());
     //status = Trying;
7c964b9b
     return res;
04d9fe08
   }
   else {
35938b4f
     DBG("invite(): we are already connected "
 	"(status=%s). do nothing!\n",getStatusStr());
04d9fe08
   }
7c964b9b
 
04d9fe08
   return -1;
4e6ce9cb
 }
 
e904822b
 int AmSipDialog::update(const AmMimeBody* body, 
6d27feb3
                         const string &hdrs)
37abd537
 {
1f251121
   switch(getStatus()){
35938b4f
   case Connected://if Connected, we should send a re-INVITE instead...
     DBG("re-INVITE should be used instead (see RFC3311, section 5.1)\n");
04d9fe08
   case Trying:
   case Proceeding:
   case Early:
e904822b
     return sendRequest(SIP_METH_UPDATE, body, hdrs);
7c964b9b
 
   default:
04d9fe08
   case Cancelling:
   case Disconnected:
   case Disconnecting:
35938b4f
     DBG("update(): dialog not connected "
 	"(status=%s). do nothing!\n",getStatusStr());
   }
04d9fe08
 
6d27feb3
   return -1;
37abd537
 }
 
9b4bba32
 int AmSipDialog::refer(const string& refer_to,
a3089798
 		       int expires,
 		       const string& referred_by)
a5dbceef
 {
1f251121
   if(getStatus() == Connected) {
3245bc05
     string hdrs = SIP_HDR_COLSP(SIP_HDR_REFER_TO) + refer_to + CRLF;
40d1c056
     if (expires>=0) 
3245bc05
       hdrs+= SIP_HDR_COLSP(SIP_HDR_EXPIRES) + int2str(expires) + CRLF;
a3089798
     if (!referred_by.empty())
       hdrs+= SIP_HDR_COLSP(SIP_HDR_REFERRED_BY) + referred_by + CRLF;
 
e904822b
     return sendRequest("REFER", NULL, hdrs);
9b4bba32
   }
04d9fe08
   else {
     DBG("refer(): we are not Connected."
35938b4f
 	"(status=%s). do nothing!\n",getStatusStr());
7c964b9b
 
     return 0;
   }	
a5dbceef
 }
 
177a7a8f
 // proprietary
17530b75
 int AmSipDialog::transfer(const string& target)
 {
1f251121
   if(getStatus() == Connected){
17530b75
 
1f251121
     setStatus(Disconnecting);
17530b75
 		
7c964b9b
     string      hdrs = "";
     AmSipDialog tmp_d(*this);
17530b75
 		
b24f51ef
     tmp_d.route = "";
d85ed5c7
     // TODO!!!
     //tmp_d.contact_uri = SIP_HDR_COLSP(SIP_HDR_CONTACT) 
     //  "<" + tmp_d.remote_uri + ">" CRLF;
7c964b9b
     tmp_d.remote_uri = target;
17530b75
 		
7c964b9b
     string r_set;
     if(!route.empty()){
17530b75
 			
7fee6a17
       hdrs = PARAM_HDR ": " "Transfer-RR=\"" + route + "\""+CRLF;
7c964b9b
     }
fa35cb1c
 				
e904822b
     int ret = tmp_d.sendRequest("REFER",NULL,hdrs);
7c964b9b
     if(!ret){
       uac_trans.insert(tmp_d.uac_trans.begin(),
 		       tmp_d.uac_trans.end());
       cseq = tmp_d.cseq;
17530b75
     }
7c964b9b
 		
     return ret;
   }
17530b75
 	
7c964b9b
   DBG("transfer(): we are not connected "
       "(status=%i). do nothing!\n",status);
17530b75
     
7c964b9b
   return 0;
17530b75
 }
 
ce90db72
 int AmSipDialog::prack(const AmSipReply &reply1xx,
e904822b
                        const AmMimeBody* body, 
6d27feb3
                        const string &hdrs)
 {
1f251121
   switch(getStatus()) {
97fc84de
   case Trying:
   case Proceeding:
   case Cancelling:
8e76b11a
   case Early:
46cabb80
   case Connected:
97fc84de
     break;
   case Disconnected:
   case Disconnecting:
6d27feb3
       ERROR("can not send PRACK while dialog is in state '%d'.\n", status);
       return -1;
97fc84de
   default:
6d27feb3
       ERROR("BUG: unexpected dialog state '%d'.\n", status);
       return -1;
   }
ce90db72
   string h = hdrs +
           SIP_HDR_COLSP(SIP_HDR_RACK) + 
           int2str(reply1xx.rseq) + " " + 
           int2str(reply1xx.cseq) + " " + 
24cd80c5
           reply1xx.cseq_method + CRLF;
e904822b
   return sendRequest(SIP_METH_PRACK, body, h);
6d27feb3
 }
 
37abd537
 int AmSipDialog::cancel()
 {
778da1ad
     for(TransMap::reverse_iterator t = uac_trans.rbegin();
 	t != uac_trans.rend(); t++) {
 	
2333d541
 	if(t->second.method == SIP_METH_INVITE){
5767cec5
 
 	  if(getStatus() != Cancelling){
1f251121
 	    setStatus(Cancelling);
011fa700
 	    return SipCtrlInterface::cancel(&t->second.tt, local_tag,
 					    t->first, t->second.hdrs);
177a7a8f
 	  }
2333d541
 	  else {
 	    ERROR("INVITE transaction has already been cancelled\n");
 	    return -1;
 	  }
778da1ad
 	}
37abd537
     }
     
7c964b9b
     ERROR("could not find INVITE transaction to cancel\n");
     return -1;
37abd537
 }
 
14489dd1
 int AmSipDialog::drop()
 {	
1f251121
   setStatus(Disconnected);
7c964b9b
   return 1;
14489dd1
 }
25875e49
 
83c2d073
 int AmSipDialog::send_200_ack(unsigned int inv_cseq,
e904822b
 			      const AmMimeBody* body,
25875e49
 			      const string& hdrs,
 			      int flags)
 {
   // TODO: implement missing pieces from RFC 3261:
   // "The ACK MUST contain the same credentials as the INVITE.  If
   // the 2xx contains an offer (based on the rules above), the ACK MUST
   // carry an answer in its body.  If the offer in the 2xx response is not
   // acceptable, the UAC core MUST generate a valid answer in the ACK and
   // then send a BYE immediately."
 
43274be9
   TransMap::iterator inv_it = uac_trans.find(inv_cseq);
   if (inv_it == uac_trans.end()) {
     ERROR("trying to ACK a non-existing transaction (cseq=%i;local_tag=%s)\n",
 	  inv_cseq,local_tag.c_str());
83c2d073
     return -1;
   }
 
25875e49
   AmSipRequest req;
 
577c62a1
   req.method = SIP_METH_ACK;
25875e49
   req.r_uri = remote_uri;
 
3245bc05
   req.from = SIP_HDR_COLSP(SIP_HDR_FROM) + local_party;
fce17d59
   if(!ext_local_tag.empty())
     req.from += ";tag=" + ext_local_tag;
   else if(!local_tag.empty())
25875e49
     req.from += ";tag=" + local_tag;
     
3245bc05
   req.to = SIP_HDR_COLSP(SIP_HDR_TO) + remote_party;
25875e49
   if(!remote_tag.empty()) 
     req.to += ";tag=" + remote_tag;
     
83c2d073
   req.cseq = inv_cseq;// should be the same as the INVITE
25875e49
   req.callid = callid;
   req.contact = getContactHdr();
     
c60b508e
   req.route = getRoute();
05f37164
 
43274be9
   req.max_forwards = inv_it->second.max_forwards;
 
e904822b
   if(body != NULL)
     req.body = *body;
cc5676b8
 
9ec2fad7
   if(onTxRequest(req,flags) < 0)
c0f8a07f
     return -1;
25875e49
 
9ec2fad7
   if (!(flags&SIP_FLAGS_VERBATIM)) {
     // add Signature
     if (AmConfig::Signature.length())
       req.hdrs += SIP_HDR_COLSP(SIP_HDR_USER_AGENT) + AmConfig::Signature + CRLF;
   }
577c62a1
 
55623e24
   int res = SipCtrlInterface::send(req, local_tag,
9ec2fad7
 				   remote_tag.empty() || !next_hop_1st_req ? 
 				   next_hop : "",
9992a0e4
 				   outbound_interface, 0, logger);
59f0e20c
   if (res)
     return res;
25875e49
 
9ec2fad7
   onRequestTxed(req);
cc5676b8
   return 0;
25875e49
 }
5d829712
 
 
 /** EMACS **
  * Local variables:
  * mode: c++
  * c-basic-offset: 2
  * End:
  */