1 /*
2  * Copyright (C) 2002-2003 Fhg Fokus
3  *
4  * This file is part of SEMS, a free SIP media server.
5  *
6  * SEMS is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version. This program is released under
10  * the GPL with the additional exemption that compiling, linking,
11  * and/or using OpenSSL is allowed.
12  *
13  * For a license to use the SEMS software under conditions
14  * other than those described here, or to purchase support for this
15  * software, please contact iptel.org by e-mail at the following addresses:
16  *    info@iptel.org
17  *
18  * SEMS is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26  */
27 
28 #include "AmSipDialog.h"
29 #include "AmConfig.h"
30 #include "AmSession.h"
31 #include "AmUtils.h"
32 #include "AmSipHeaders.h"
33 #include "SipCtrlInterface.h"
34 #include "sems.h"
35 
36 #include "sip/parse_route.h"
37 #include "sip/parse_uri.h"
38 #include "sip/parse_next_hop.h"
39 
40 #include "AmB2BMedia.h" // just because of statistics
41 
addTranscoderStats(string & hdrs)42 static void addTranscoderStats(string &hdrs)
43 {
44   // add transcoder statistics into request/reply headers
45   if (!AmConfig::TranscoderOutStatsHdr.empty()) {
46     string usage;
47     B2BMediaStatistics::instance()->reportCodecWriteUsage(usage);
48 
49     hdrs += AmConfig::TranscoderOutStatsHdr + ": ";
50     hdrs += usage;
51     hdrs += CRLF;
52   }
53   if (!AmConfig::TranscoderInStatsHdr.empty()) {
54     string usage;
55     B2BMediaStatistics::instance()->reportCodecReadUsage(usage);
56 
57     hdrs += AmConfig::TranscoderInStatsHdr + ": ";
58     hdrs += usage;
59     hdrs += CRLF;
60   }
61 }
62 
AmSipDialog(AmSipDialogEventHandler * h)63 AmSipDialog::AmSipDialog(AmSipDialogEventHandler* h)
64   : AmBasicSipDialog(h),pending_invites(0),sdp_local(),
65     sdp_remote(),
66     early_session_started(false),session_started(false),
67     oa(this),
68     offeranswer_enabled(true), rel100(this,h)
69 {
70 }
71 
~AmSipDialog()72 AmSipDialog::~AmSipDialog()
73 {
74 }
75 
onRxReqSanity(const AmSipRequest & req)76 bool AmSipDialog::onRxReqSanity(const AmSipRequest& req)
77 {
78   if (req.method == SIP_METH_ACK) {
79     if(onRxReqStatus(req) && hdl)
80       hdl->onSipRequest(req);
81     return false;
82   }
83 
84   if (req.method == SIP_METH_CANCEL) {
85 
86     if (uas_trans.find(req.cseq) == uas_trans.end()) {
87       reply_error(req,481,SIP_REPLY_NOT_EXIST);
88       return false;
89     }
90 
91     if(onRxReqStatus(req) && hdl)
92       hdl->onSipRequest(req);
93 
94     return false;
95   }
96 
97   if(!AmBasicSipDialog::onRxReqSanity(req))
98     return false;
99 
100   if (req.method == SIP_METH_INVITE) {
101     bool pending = pending_invites;
102     if (offeranswer_enabled) {
103       // not sure this is needed here: could be in AmOfferAnswer as well
104       pending |= ((oa.getState() != AmOfferAnswer::OA_None) &&
105 		  (oa.getState() != AmOfferAnswer::OA_Completed));
106     }
107 
108     if (pending) {
109       reply_error(req, 491, SIP_REPLY_PENDING,
110 		  SIP_HDR_COLSP(SIP_HDR_RETRY_AFTER)
111 		  + int2str(get_random() % 10) + CRLF);
112       return false;
113     }
114 
115     pending_invites++;
116   }
117 
118   return rel100.onRequestIn(req);
119 }
120 
onRxReqStatus(const AmSipRequest & req)121 bool AmSipDialog::onRxReqStatus(const AmSipRequest& req)
122 {
123   switch(status){
124   case Disconnected:
125     if(req.method == SIP_METH_INVITE)
126       setStatus(Trying);
127     break;
128   case Connected:
129     if(req.method == SIP_METH_BYE)
130       setStatus(Disconnecting);
131     break;
132 
133   case Trying:
134   case Proceeding:
135   case Early:
136     if(req.method == SIP_METH_BYE)
137       setStatus(Disconnecting);
138     else if(req.method == SIP_METH_CANCEL){
139       setStatus(Cancelling);
140       reply(req,200,"OK");
141     }
142     break;
143 
144   default: break;
145   }
146 
147   bool cont = true;
148   if (offeranswer_enabled) {
149     cont = (oa.onRequestIn(req) == 0);
150   }
151 
152   return cont;
153 }
154 
onSdpCompleted()155 int AmSipDialog::onSdpCompleted()
156 {
157   if(!hdl) return 0;
158 
159   int ret = ((AmSipDialogEventHandler*)hdl)->
160     onSdpCompleted(oa.getLocalSdp(), oa.getRemoteSdp());
161 
162   if(!ret) {
163     sdp_local = oa.getLocalSdp();
164     sdp_remote = oa.getRemoteSdp();
165 
166     if((getStatus() == Early) && !early_session_started) {
167       ((AmSipDialogEventHandler*)hdl)->onEarlySessionStart();
168       early_session_started = true;
169     }
170 
171     if((getStatus() == Connected) && !session_started) {
172       ((AmSipDialogEventHandler*)hdl)->onSessionStart();
173       session_started = true;
174     }
175   }
176   else {
177     oa.clear();
178   }
179 
180   return ret;
181 }
182 
getSdpOffer(AmSdp & offer)183 bool AmSipDialog::getSdpOffer(AmSdp& offer)
184 {
185   if(!hdl) return false;
186   return ((AmSipDialogEventHandler*)hdl)->getSdpOffer(offer);
187 }
188 
getSdpAnswer(const AmSdp & offer,AmSdp & answer)189 bool AmSipDialog::getSdpAnswer(const AmSdp& offer, AmSdp& answer)
190 {
191   if(!hdl) return false;
192   return ((AmSipDialogEventHandler*)hdl)->getSdpAnswer(offer,answer);
193 }
194 
getOAState()195 AmOfferAnswer::OAState AmSipDialog::getOAState() {
196   return oa.getState();
197 }
198 
setOAState(AmOfferAnswer::OAState n_st)199 void AmSipDialog::setOAState(AmOfferAnswer::OAState n_st) {
200   oa.setState(n_st);
201 }
202 
setRel100State(Am100rel::State rel100_state)203 void AmSipDialog::setRel100State(Am100rel::State rel100_state) {
204   DBG("setting 100rel state for '%s' to %i\n", local_tag.c_str(), rel100_state);
205   rel100.setState(rel100_state);
206 }
207 
setOAEnabled(bool oa_enabled)208 void AmSipDialog::setOAEnabled(bool oa_enabled) {
209   DBG("%sabling offer_answer on SIP dialog '%s'\n",
210       oa_enabled?"en":"dis", local_tag.c_str());
211   offeranswer_enabled = oa_enabled;
212 }
213 
onTxRequest(AmSipRequest & req,int & flags)214 int AmSipDialog::onTxRequest(AmSipRequest& req, int& flags)
215 {
216   rel100.onRequestOut(req);
217 
218   if (offeranswer_enabled && oa.onRequestOut(req))
219     return -1;
220 
221   if(AmBasicSipDialog::onTxRequest(req,flags) < 0)
222     return -1;
223 
224   // add transcoder statistics into request headers
225   addTranscoderStats(req.hdrs);
226 
227   if((req.method == SIP_METH_INVITE) && (status == Disconnected)){
228     setStatus(Trying);
229   }
230   else if((req.method == SIP_METH_BYE) && (status != Disconnecting)){
231     setStatus(Disconnecting);
232   }
233 
234   if ((req.method == SIP_METH_BYE) || (req.method == SIP_METH_CANCEL)) {
235     flags |= SIP_FLAGS_NOCONTACT;
236   }
237 
238   return 0;
239 }
240 
241 // UAS behavior for locally sent replies
onTxReply(const AmSipRequest & req,AmSipReply & reply,int & flags)242 int AmSipDialog::onTxReply(const AmSipRequest& req, AmSipReply& reply, int& flags)
243 {
244   if (offeranswer_enabled) {
245     if(oa.onReplyOut(reply) < 0)
246       return -1;
247   }
248 
249   rel100.onReplyOut(reply);
250 
251   // update Dialog status
252   switch(status){
253 
254   case Connected:
255   case Disconnected:
256     break;
257 
258   case Cancelling:
259     if( (reply.cseq_method == SIP_METH_INVITE) &&
260 	(reply.code < 200) ) {
261       // refuse local provisional replies
262       // when state is Cancelling
263       ERROR("refuse local provisional replies when state is Cancelling\n");
264       return -1;
265     }
266     // else continue with final
267     // reply processing
268   case Proceeding:
269   case Trying:
270   case Early:
271     if(reply.cseq_method == SIP_METH_INVITE){
272       if(reply.code < 200) {
273 	setStatus(Early);
274       }
275       else if(reply.code < 300)
276 	setStatus(Connected);
277       else
278 	setStatus(Disconnected);
279     }
280     break;
281 
282   case Disconnecting:
283     if(reply.cseq_method == SIP_METH_BYE){
284 
285       // Only reason for refusing a BYE:
286       //  authentication (NYI at this place)
287       // Also: we should not send provisionnal replies to a BYE
288       if(reply.code >= 200)
289 	setStatus(Disconnected);
290     }
291     break;
292 
293   default:
294     assert(0);
295     break;
296   }
297 
298   // add transcoder statistics into reply headers
299   addTranscoderStats(reply.hdrs);
300 
301   // target-refresh requests and their replies need to contain Contact (1xx
302   // replies only those establishing dialog, take care about them?)
303   if(reply.cseq_method != SIP_METH_INVITE &&
304      reply.cseq_method != SIP_METH_UPDATE) {
305 
306     flags |= SIP_FLAGS_NOCONTACT;
307   }
308 
309   return AmBasicSipDialog::onTxReply(req,reply,flags);
310 }
311 
onReplyTxed(const AmSipRequest & req,const AmSipReply & reply)312 void AmSipDialog::onReplyTxed(const AmSipRequest& req, const AmSipReply& reply)
313 {
314   AmBasicSipDialog::onReplyTxed(req,reply);
315 
316   if (offeranswer_enabled) {
317     oa.onReplySent(reply);
318   }
319 
320   if (reply.code >= 200) {
321     if(reply.cseq_method == SIP_METH_INVITE)
322 	pending_invites--;
323   }
324 }
325 
onRequestTxed(const AmSipRequest & req)326 void AmSipDialog::onRequestTxed(const AmSipRequest& req)
327 {
328   AmBasicSipDialog::onRequestTxed(req);
329 
330   if (offeranswer_enabled) {
331     oa.onRequestSent(req);
332   }
333 }
334 
onRxReplySanity(const AmSipReply & reply)335 bool AmSipDialog::onRxReplySanity(const AmSipReply& reply)
336 {
337   if(!getRemoteTag().empty() && reply.to_tag != getRemoteTag()) {
338 
339     if(status == Early) {
340       if(reply.code < 200 && !reply.to_tag.empty()) {
341 	// Provision reply, such as 180, can come from a new UAS
342 	return true;
343       }
344     } else if(reply.cseq_method == SIP_METH_INVITE
345 	      || reply.cseq_method == SIP_METH_CANCEL
346 	      || reply.cseq_method == SIP_METH_BYE) {
347       // do not drop in order to avoid avoid sessions hangs
348       DBG("[%s] reply for %s '%d %s' is not matched with dialog. but matched with transaction. process it",
349           local_tag.c_str(), reply.cseq_method.c_str(), reply.code,
350 	  reply.reason.c_str());
351     } else {
352       DBG("dropping reply (%u %s)\n", reply.code, reply.reason.c_str());
353       return false;
354     }
355   }
356 
357   return true;
358 }
359 
onRxReplyStatus(const AmSipReply & reply)360 bool AmSipDialog::onRxReplyStatus(const AmSipReply& reply)
361 {
362   // rfc3261 12.1
363   // Dialog established only by 101-199 or 2xx
364   // responses to INVITE
365 
366   if(reply.cseq_method == SIP_METH_INVITE) {
367 
368     switch(status){
369 
370     case Trying:
371     case Proceeding:
372       if(reply.code < 200){
373 	// Do not go to Early state if reply did not come from an UAS
374 	if (((reply.code == 100) || (reply.code == 181) || (reply.code == 182))
375 	    || reply.to_tag.empty())
376 	  setStatus(Proceeding);
377 	else {
378 	  setStatus(Early);
379 	  setRemoteTag(reply.to_tag);
380 	  setRouteSet(reply.route);
381 	}
382       }
383       else if(reply.code < 300){
384 	setStatus(Connected);
385 	setRouteSet(reply.route);
386 	if(reply.to_tag.empty()){
387 	  DBG("received 2xx reply without to-tag "
388 	      "(callid=%s): sending BYE\n",reply.callid.c_str());
389 
390 	  send_200_ack(reply.cseq);
391 	  sendRequest(SIP_METH_BYE);
392 	}
393 	else {
394 	  setRemoteTag(reply.to_tag);
395 	}
396       }
397 
398       if(reply.code >= 300) {// error reply
399 	setStatus(Disconnected);
400 	setRemoteTag(reply.to_tag);
401       }
402       break;
403 
404     case Early:
405       if(reply.code < 200){
406 	if (!reply.to_tag.empty() && (reply.to_tag != getRemoteTag())) {
407 	  DBG("updating dialog based on reply (%u %s) in Early state\n",
408 	      reply.code, reply.reason.c_str());
409 	  setRemoteTag(reply.to_tag);
410 	  setRouteSet(reply.route);
411 	  break;
412 	}
413         //DROP!!!
414 	DBG("ignoring provisional reply (%u %s) in Early state\n",
415 	    reply.code, reply.reason.c_str());
416       }
417       else if(reply.code < 300){
418 	setStatus(Connected);
419 	setRouteSet(reply.route);
420 	if(reply.to_tag.empty()){
421 	  DBG("received 2xx reply without to-tag "
422 	      "(callid=%s): sending BYE\n",reply.callid.c_str());
423 
424 	  sendRequest(SIP_METH_BYE);
425 	}
426 	else {
427 	  setRemoteTag(reply.to_tag);
428 	}
429       }
430       else { // error reply
431 	setStatus(Disconnected);
432 	setRemoteTag(reply.to_tag);
433       }
434       break;
435 
436     case Cancelling:
437       if(reply.code >= 300){
438 	// CANCEL accepted
439 	DBG("CANCEL accepted, status -> Disconnected\n");
440 	setStatus(Disconnected);
441       }
442       else if(reply.code < 300){
443 	// CANCEL rejected
444 	DBG("CANCEL rejected/too late - bye()\n");
445 	setRemoteTag(reply.to_tag);
446 	setStatus(Connected);
447 	bye();
448 	// if BYE could not be sent,
449 	// there is nothing we can do anymore...
450       }
451       break;
452 
453     //case Connected: // late 200...
454     //  TODO: if reply.to_tag != getRemoteTag()
455     //        -> ACK + BYE (+absorb answer)
456     default:
457       break;
458     }
459   }
460 
461   if(status == Disconnecting){
462 
463     DBG("?Disconnecting?: cseq_method = %s; code = %i\n",
464 	reply.cseq_method.c_str(), reply.code);
465 
466     if((reply.cseq_method == SIP_METH_BYE) && (reply.code >= 200)){
467       //TODO: support the auth case here (401/403)
468       setStatus(Disconnected);
469     }
470   }
471 
472   if (offeranswer_enabled) {
473     oa.onReplyIn(reply);
474   }
475 
476   bool cont = true;
477   if( (reply.code >= 200) && (reply.code < 300) &&
478       (reply.cseq_method == SIP_METH_INVITE) ) {
479 
480     if(hdl) ((AmSipDialogEventHandler*)hdl)->onInvite2xx(reply);
481 
482   } else {
483     cont = AmBasicSipDialog::onRxReplyStatus(reply);
484   }
485 
486   return cont && rel100.onReplyIn(reply);
487 }
488 
uasTimeout(AmSipTimeoutEvent * to_ev)489 void AmSipDialog::uasTimeout(AmSipTimeoutEvent* to_ev)
490 {
491   assert(to_ev);
492 
493   switch(to_ev->type){
494   case AmSipTimeoutEvent::noACK:
495     DBG("Timeout: missing ACK\n");
496     if (offeranswer_enabled) {
497       oa.onNoAck(to_ev->cseq);
498     }
499     if(hdl) ((AmSipDialogEventHandler*)hdl)->onNoAck(to_ev->cseq);
500     break;
501 
502   case AmSipTimeoutEvent::noPRACK:
503     DBG("Timeout: missing PRACK\n");
504     rel100.onTimeout(to_ev->req, to_ev->rpl);
505     break;
506 
507   case AmSipTimeoutEvent::_noEv:
508   default:
509     break;
510   };
511 
512   to_ev->processed = true;
513 }
514 
getUACInvTransPending()515 bool AmSipDialog::getUACInvTransPending() {
516   for (TransMap::iterator it=uac_trans.begin();
517        it != uac_trans.end(); it++) {
518     if (it->second.method == SIP_METH_INVITE)
519       return true;
520   }
521   return false;
522 }
523 
getUASPendingInv()524 AmSipRequest* AmSipDialog::getUASPendingInv()
525 {
526   for (TransMap::iterator it=uas_trans.begin();
527        it != uas_trans.end(); it++) {
528     if (it->second.method == SIP_METH_INVITE)
529       return &(it->second);
530   }
531   return NULL;
532 }
533 
bye(const string & hdrs,int flags)534 int AmSipDialog::bye(const string& hdrs, int flags)
535 {
536   switch(status){
537 
538     case Disconnecting:
539     case Connected: {
540       // collect INVITE UAC transactions
541       vector<unsigned int> ack_trans;
542       for (TransMap::iterator it=uac_trans.begin(); it != uac_trans.end(); it++) {
543 	if (it->second.method == SIP_METH_INVITE){
544 	  ack_trans.push_back(it->second.cseq);
545 	}
546       }
547       // finish any UAC transaction before sending BYE
548       for (vector<unsigned int>::iterator it=
549 	     ack_trans.begin(); it != ack_trans.end(); it++) {
550 	send_200_ack(*it);
551       }
552 
553       if (status != Disconnecting) {
554 	setStatus(Disconnected);
555 	return sendRequest(SIP_METH_BYE, NULL, hdrs, flags);
556       } else {
557 	return 0;
558       }
559     }
560 
561     case Trying:
562     case Proceeding:
563     case Early:
564       if(getUACInvTransPending())
565 	return cancel(hdrs);
566       else {
567 	    for (TransMap::iterator it=uas_trans.begin();
568 		 it != uas_trans.end(); it++) {
569 	      if (it->second.method == SIP_METH_INVITE){
570 		// let quit this call by sending final reply
571 		return reply(it->second,
572 			     487,"Request terminated");
573 	      }
574 	    }
575 
576 	    // missing AmSipRequest to be able
577 	    // to send the reply on behalf of the app.
578 	    ERROR("ignoring bye() in %s state: "
579 		  "no UAC transaction to cancel or UAS transaction to reply.\n",
580 		  getStatusStr());
581 	    setStatus(Disconnected);
582 	}
583 	return 0;
584 
585     case Cancelling:
586       for (TransMap::iterator it=uas_trans.begin();
587 	   it != uas_trans.end(); it++) {
588 	if (it->second.method == SIP_METH_INVITE){
589 	  // let's quit this call by sending final reply
590 	  return reply(it->second, 487,"Request terminated");
591 	}
592       }
593 
594       // missing AmSipRequest to be able
595       // to send the reply on behalf of the app.
596       DBG("ignoring bye() in %s state: no UAS transaction to reply",getStatusStr());
597       setStatus(Disconnected);
598       return 0;
599 
600     default:
601         DBG("bye(): we are not connected "
602 	    "(status=%s). do nothing!\n",getStatusStr());
603 	return 0;
604     }
605 }
606 
reinvite(const string & hdrs,const AmMimeBody * body,int flags)607 int AmSipDialog::reinvite(const string& hdrs,
608 			  const AmMimeBody* body,
609 			  int flags)
610 {
611   if(getStatus() == Connected) {
612     return sendRequest(SIP_METH_INVITE, body, hdrs, flags);
613   }
614   else {
615     DBG("reinvite(): we are not connected "
616 	"(status=%s). do nothing!\n",getStatusStr());
617   }
618 
619   return -1;
620 }
621 
invite(const string & hdrs,const AmMimeBody * body)622 int AmSipDialog::invite(const string& hdrs,
623 			const AmMimeBody* body)
624 {
625   if(getStatus() == Disconnected) {
626     int res = sendRequest(SIP_METH_INVITE, body, hdrs);
627     DBG("TODO: is status already 'trying'? status=%s\n",getStatusStr());
628     //status = Trying;
629     return res;
630   }
631   else {
632     DBG("invite(): we are already connected "
633 	"(status=%s). do nothing!\n",getStatusStr());
634   }
635 
636   return -1;
637 }
638 
update(const AmMimeBody * body,const string & hdrs)639 int AmSipDialog::update(const AmMimeBody* body,
640                         const string &hdrs)
641 {
642   switch(getStatus()){
643   case Connected://if Connected, we should send a re-INVITE instead...
644     DBG("re-INVITE should be used instead (see RFC3311, section 5.1)\n");
645   case Trying:
646   case Proceeding:
647   case Early:
648     return sendRequest(SIP_METH_UPDATE, body, hdrs);
649 
650   default:
651   case Cancelling:
652   case Disconnected:
653   case Disconnecting:
654     DBG("update(): dialog not connected "
655 	"(status=%s). do nothing!\n",getStatusStr());
656   }
657 
658   return -1;
659 }
660 
refer(const string & refer_to,int expires,const string & referred_by,const string & extrahdrs)661 int AmSipDialog::refer(const string& refer_to,
662 		       int expires,
663 		       const string& referred_by,
664 		       const string& extrahdrs)
665 {
666   if(getStatus() == Connected) {
667     string hdrs = SIP_HDR_COLSP(SIP_HDR_REFER_TO) + refer_to + CRLF;
668     if (expires>=0)
669       hdrs+= SIP_HDR_COLSP(SIP_HDR_EXPIRES) + int2str(expires) + CRLF;
670     hdrs+= extrahdrs;
671     if (!referred_by.empty())
672       hdrs+= SIP_HDR_COLSP(SIP_HDR_REFERRED_BY) + referred_by + CRLF;
673 
674     return sendRequest("REFER", NULL, hdrs);
675   }
676   else {
677     DBG("refer(): we are not Connected."
678 	"(status=%s). do nothing!\n",getStatusStr());
679 
680     return 0;
681   }
682 }
683 
info(const string & hdrs,const AmMimeBody * body)684 int AmSipDialog::info(const string& hdrs, const AmMimeBody* body)
685 {
686   if(getStatus() == Connected) {
687     return sendRequest("INFO", body, hdrs);
688   } else {
689     DBG("info(): we are not Connected."
690 	"(status=%s). do nothing!\n", getStatusStr());
691     return 0;
692   }
693 }
694 
695 // proprietary
transfer(const string & target)696 int AmSipDialog::transfer(const string& target)
697 {
698   if(getStatus() == Connected){
699 
700     setStatus(Disconnecting);
701 
702     string      hdrs = "";
703     AmSipDialog tmp_d(*this);
704 
705     tmp_d.route = "";
706     // TODO!!!
707     //tmp_d.contact_uri = SIP_HDR_COLSP(SIP_HDR_CONTACT)
708     //  "<" + tmp_d.remote_uri + ">" CRLF;
709     tmp_d.remote_uri = target;
710 
711     string r_set;
712     if(!route.empty()){
713 
714       hdrs = PARAM_HDR ": " "Transfer-RR=\"" + route + "\""+CRLF;
715     }
716 
717     int ret = tmp_d.sendRequest("REFER",NULL,hdrs);
718     if(!ret){
719       uac_trans.insert(tmp_d.uac_trans.begin(),
720 		       tmp_d.uac_trans.end());
721       cseq = tmp_d.cseq;
722     }
723 
724     return ret;
725   }
726 
727   DBG("transfer(): we are not connected "
728       "(status=%i). do nothing!\n",status);
729 
730   return 0;
731 }
732 
prack(const AmSipReply & reply1xx,const AmMimeBody * body,const string & hdrs)733 int AmSipDialog::prack(const AmSipReply &reply1xx,
734                        const AmMimeBody* body,
735                        const string &hdrs)
736 {
737   switch(getStatus()) {
738   case Trying:
739   case Proceeding:
740   case Cancelling:
741   case Early:
742   case Connected:
743     break;
744   case Disconnected:
745   case Disconnecting:
746       ERROR("can not send PRACK while dialog is in state '%d'.\n", status);
747       return -1;
748   default:
749       ERROR("BUG: unexpected dialog state '%d'.\n", status);
750       return -1;
751   }
752   string h = hdrs +
753           SIP_HDR_COLSP(SIP_HDR_RACK) +
754           int2str(reply1xx.rseq) + " " +
755           int2str(reply1xx.cseq) + " " +
756           reply1xx.cseq_method + CRLF;
757   return sendRequest(SIP_METH_PRACK, body, h);
758 }
759 
cancel()760 int AmSipDialog::cancel()
761 {
762     for(TransMap::reverse_iterator t = uac_trans.rbegin();
763 	t != uac_trans.rend(); t++) {
764 
765 	if(t->second.method == SIP_METH_INVITE){
766 
767 	  if(getStatus() != Cancelling){
768 	    setStatus(Cancelling);
769 	    return SipCtrlInterface::cancel(&t->second.tt, local_tag,
770 					    t->first, t->second.hdrs);
771 	  }
772 	  else {
773 	    ERROR("INVITE transaction has already been cancelled\n");
774 	    return -1;
775 	  }
776 	}
777     }
778 
779     ERROR("could not find INVITE transaction to cancel\n");
780     return -1;
781 }
782 
cancel(const string & hdrs)783 int AmSipDialog::cancel(const string& hdrs)
784 {
785     for(TransMap::reverse_iterator t = uac_trans.rbegin();
786 	t != uac_trans.rend(); t++) {
787 
788 	if(t->second.method == SIP_METH_INVITE){
789 
790 	  if(getStatus() != Cancelling){
791 	    setStatus(Cancelling);
792 	    return SipCtrlInterface::cancel(&t->second.tt, local_tag,
793 					    t->first, hdrs);
794 	  }
795 	  else {
796 	    ERROR("INVITE transaction has already been cancelled\n");
797 	    return -1;
798 	  }
799 	}
800     }
801 
802     ERROR("could not find INVITE transaction to cancel\n");
803     return -1;
804 }
805 
drop()806 int AmSipDialog::drop()
807 {
808   setStatus(Disconnected);
809   return 1;
810 }
811 
send_200_ack(unsigned int inv_cseq,const AmMimeBody * body,const string & hdrs,int flags)812 int AmSipDialog::send_200_ack(unsigned int inv_cseq,
813 			      const AmMimeBody* body,
814 			      const string& hdrs,
815 			      int flags)
816 {
817   // TODO: implement missing pieces from RFC 3261:
818   // "The ACK MUST contain the same credentials as the INVITE.  If
819   // the 2xx contains an offer (based on the rules above), the ACK MUST
820   // carry an answer in its body.  If the offer in the 2xx response is not
821   // acceptable, the UAC core MUST generate a valid answer in the ACK and
822   // then send a BYE immediately."
823 
824   TransMap::iterator inv_it = uac_trans.find(inv_cseq);
825   if (inv_it == uac_trans.end()) {
826     ERROR("trying to ACK a non-existing transaction (cseq=%i;local_tag=%s)\n",
827 	  inv_cseq,local_tag.c_str());
828     return -1;
829   }
830 
831   AmSipRequest req;
832 
833   req.method = SIP_METH_ACK;
834   req.r_uri = remote_uri;
835 
836   req.from = SIP_HDR_COLSP(SIP_HDR_FROM) + local_party;
837   if(!ext_local_tag.empty())
838     req.from += ";tag=" + ext_local_tag;
839   else if(!local_tag.empty())
840     req.from += ";tag=" + local_tag;
841 
842   req.to = SIP_HDR_COLSP(SIP_HDR_TO) + remote_party;
843   if(!remote_tag.empty())
844     req.to += ";tag=" + remote_tag;
845 
846   req.cseq = inv_cseq;// should be the same as the INVITE
847   req.callid = callid;
848   req.contact = getContactHdr();
849 
850   req.route = getRoute();
851 
852   req.max_forwards = inv_it->second.max_forwards;
853 
854   if(body != NULL)
855     req.body = *body;
856 
857   if(onTxRequest(req,flags) < 0)
858     return -1;
859 
860   if (!(flags&SIP_FLAGS_VERBATIM)) {
861     // add Signature
862     if (AmConfig::Signature.length())
863       req.hdrs += SIP_HDR_COLSP(SIP_HDR_USER_AGENT) + AmConfig::Signature + CRLF;
864   }
865 
866   int res = SipCtrlInterface::send(req, local_tag,
867 				   remote_tag.empty() || !next_hop_1st_req ?
868 				   next_hop : "",
869 				   outbound_interface, 0, logger);
870   if (res)
871     return res;
872 
873   onRequestTxed(req);
874   return 0;
875 }
876 
877 
878 /** EMACS **
879  * Local variables:
880  * mode: c++
881  * c-basic-offset: 2
882  * End:
883  */
884