Browse code

Merge branch 'master' into 1.6-dev

Václav Kubart authored on 12/11/2013 06:30:28
Showing 37 changed files
... ...
@@ -225,6 +225,9 @@ DSMCondition* DSMCoreModule::getCondition(const string& from_str) {
225 225
   if (cmd == "startup")
226 226
     return new TestDSMCondition(params, DSMCondition::Startup);
227 227
 
228
+  if (cmd == "start")
229
+    return new TestDSMCondition(params, DSMCondition::Start);
230
+
228 231
   if (cmd == "reload")
229 232
     return new TestDSMCondition(params, DSMCondition::Reload);
230 233
 
... ...
@@ -230,7 +230,7 @@ void Monitor::logAdd(const AmArg& args, AmArg& ret) {
230 230
   bucket.log_lock.lock();
231 231
   try {
232 232
     AmArg& val = bucket.log[args[0].asCStr()].info[args[1].asCStr()];
233
-    if (!isArgArray(val)) {
233
+    if (!isArgArray(val) && !isArgUndef(val)) {
234 234
       AmArg v1 = val;
235 235
       val = AmArg();
236 236
       val.push(v1);
... ...
@@ -46,14 +46,123 @@ ReliableB2BEvent::~ReliableB2BEvent()
46 46
   }
47 47
 }
48 48
 
49
+////////////////////////////////////////////////////////////////////////////////
50
+// helper functions
51
+
52
+enum HoldMethod { SendonlyStream, InactiveStream, ZeroedConnection };
53
+
54
+static const string sendonly("sendonly");
55
+static const string recvonly("recvonly");
56
+static const string sendrecv("sendrecv");
57
+static const string inactive("inactive");
58
+
59
+static const string zero_connection("0.0.0.0");
60
+
61
+/** returns true if connection is avtive.
62
+ * Returns given default_value if the connection address is empty to cope with
63
+ * connection address set globaly and not per media stream */
64
+static bool connectionActive(const SdpConnection &connection, bool default_value)
65
+{
66
+  if (connection.address.empty()) return default_value;
67
+  if (connection.address == zero_connection) return false;
68
+  return true;
69
+}
70
+
71
+enum MediaActivity { Inactive, Sendonly, Recvonly, Sendrecv };
72
+
73
+/** Returns true if there is no direction=inactione or sendonly attribute in
74
+ * given media stream. It doesn't check the connection address! */
75
+static MediaActivity getMediaActivity(const vector<SdpAttribute> &attrs, MediaActivity default_value)
76
+{
77
+  // go through attributes and try to find sendonly/recvonly/sendrecv/inactive
78
+  for (std::vector<SdpAttribute>::const_iterator a = attrs.begin(); 
79
+      a != attrs.end(); ++a)
80
+  {
81
+    if (a->attribute == sendonly) return Sendonly;
82
+    if (a->attribute == inactive) return Inactive;
83
+    if (a->attribute == recvonly) return Recvonly;
84
+    if (a->attribute == sendrecv) return Sendrecv;
85
+  }
86
+
87
+  return default_value; // none of the attributes given, return (session) default
88
+}
89
+
90
+static MediaActivity getMediaActivity(const SdpMedia &m, MediaActivity default_value)
91
+{
92
+  if (m.send) {
93
+    if (m.recv) return Sendrecv;
94
+    else return Sendonly;
95
+  }
96
+  else {
97
+    if (m.recv) return Recvonly;
98
+  }
99
+  return Inactive;
100
+}
101
+
102
+static bool isHoldRequest(AmSdp &sdp, HoldMethod &method)
103
+{
104
+  // set defaults from session parameters and attributes
105
+  // inactive/sendonly/sendrecv/recvonly may be given as session attributes,
106
+  // connection can be given for session as well
107
+  bool connection_active = connectionActive(sdp.conn, false /* empty connection like inactive? */);
108
+  MediaActivity session_activity = getMediaActivity(sdp.attributes, Sendrecv);
109
+
110
+  for (std::vector<SdpMedia>::iterator m = sdp.media.begin(); 
111
+      m != sdp.media.end(); ++m) 
112
+  {
113
+    if (m->port == 0) continue; // this stream is disabled, handle like inactive (?)
114
+    if (!connectionActive(m->conn, connection_active)) {
115
+      method = ZeroedConnection;
116
+      continue;
117
+    }
118
+    switch (getMediaActivity(*m, session_activity)) {
119
+      case Sendonly:
120
+        method = SendonlyStream;
121
+        continue;
122
+
123
+      case Inactive:
124
+        method = InactiveStream;
125
+        continue;
126
+
127
+      case Recvonly: // ?
128
+      case Sendrecv:
129
+        return false; // media stream is active
130
+    }
131
+  }
132
+
133
+  if (sdp.media.empty()) {
134
+    // no streams in the SDP, needed to set method somehow
135
+    if (!connection_active) method = ZeroedConnection;
136
+    else {
137
+      switch (session_activity) {
138
+        case Sendonly:
139
+          method = SendonlyStream;
140
+          break;
141
+
142
+        case Inactive:
143
+          method = InactiveStream;
144
+          break;
145
+
146
+        case Recvonly:
147
+        case Sendrecv:
148
+          method = InactiveStream; // well, no stream is something like InactiveStream, isn't it?
149
+          break;
150
+
151
+      }
152
+    }
153
+  }
154
+
155
+  return true; // no active stream was found
156
+}
157
+
158
+
49 159
 ////////////////////////////////////////////////////////////////////////////////
50 160
 
51 161
 // callee
52 162
 CallLeg::CallLeg(const CallLeg* caller, AmSipDialog* p_dlg, AmSipSubscription* p_subs)
53 163
   : AmB2BSession(caller->getLocalTag(),p_dlg,p_subs),
54 164
     call_status(Disconnected),
55
-    hold_request_cseq(0), 
56
-    hold_status(NotHeld)
165
+    on_hold(false)
57 166
 {
58 167
   a_leg = !caller->a_leg; // we have to be the complement
59 168
 
... ...
@@ -96,8 +205,7 @@ CallLeg::CallLeg(const CallLeg* caller, AmSipDialog* p_dlg, AmSipSubscription* p
96 205
 CallLeg::CallLeg(AmSipDialog* p_dlg, AmSipSubscription* p_subs)
97 206
   : AmB2BSession("",p_dlg,p_subs),
98 207
     call_status(Disconnected),
99
-    hold_request_cseq(0),
100
-    hold_status(NotHeld)
208
+    on_hold(false)
101 209
 {
102 210
   a_leg = true;
103 211
 
... ...
@@ -207,7 +315,14 @@ void CallLeg::onB2BEvent(B2BEvent* ev)
207 315
     case DisconnectLeg:
208 316
       {
209 317
         DisconnectLegEvent *dle = dynamic_cast<DisconnectLegEvent*>(ev);
210
-        if (dle) disconnect(dle->put_remote_on_hold);
318
+        if (dle) disconnect(dle->put_remote_on_hold, dle->preserve_media_session);
319
+      }
320
+      break;
321
+
322
+    case ResumeHeld:
323
+      {
324
+        ResumeHeldEvent *e = dynamic_cast<ResumeHeldEvent*>(ev);
325
+        if (e) resumeHeld();
211 326
       }
212 327
       break;
213 328
 
... ...
@@ -271,12 +386,12 @@ bool CallLeg::setOther(const string &id, bool use_initial_sdp)
271 386
         TRACE("connecting media session: %s to %s\n", 
272 387
             dlg->getLocalTag().c_str(), getOtherId().c_str());
273 388
         i->media_session->changeSession(a_leg, this);
274
-        if (use_initial_sdp) updateRemoteSdp(initial_sdp);
275 389
       }
276 390
       else {
277 391
         // media session not set, set direct mode if not set already
278 392
         if (rtp_relay_mode != AmB2BSession::RTP_Direct) setRtpRelayMode(AmB2BSession::RTP_Direct);
279 393
       }
394
+      if (use_initial_sdp) updateRemoteSdp(initial_sdp);
280 395
       set_sip_relay_only(true); // relay only from now on
281 396
       return true;
282 397
     }
... ...
@@ -335,10 +450,6 @@ void CallLeg::b2bInitial2xx(AmSipReply& reply, bool forward)
335 450
     other_legs.begin()->releaseMediaSession(); // remove reference hold by OtherLegInfo
336 451
   other_legs.clear(); // no need to remember the connected leg here
337 452
 
338
-  // FIXME: hack here - it should be part of clearRtpReceiverRelay but we
339
-  // need to do after RTP mode change above
340
-  resumeHeld(false);
341
-
342 453
   onCallConnected(reply);
343 454
 
344 455
   if (!forward) {
... ...
@@ -462,26 +573,15 @@ void CallLeg::onB2BConnect(ConnectLegEvent* co_ev)
462 573
   // connected to the A leg yet when handling the in-dialog request.
463 574
   set_sip_relay_only(true); // we should relay everything to the other leg from now
464 575
 
465
-  updateCallStatus(NoReply);
466
-
467
-  AmMimeBody r_body(co_ev->body);
468
-  const AmMimeBody* body = &co_ev->body;
469
-  if (rtp_relay_mode != RTP_Direct) {
470
-    try {
471
-      body = co_ev->body.hasContentType(SIP_APPLICATION_SDP);
472
-      if (body && updateLocalBody(*body, *r_body.hasContentType(SIP_APPLICATION_SDP))) {
473
-        body = &r_body;
474
-      }
475
-      else {
476
-        body = &co_ev->body;
477
-      }
478
-    } catch (const string& s) {
479
-      relayError(SIP_METH_INVITE, co_ev->r_cseq, true, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
480
-      throw;
481
-    }
576
+  AmMimeBody body(co_ev->body);
577
+  try {
578
+    updateLocalBody(body);
579
+  } catch (const string& s) {
580
+    relayError(SIP_METH_INVITE, co_ev->r_cseq, true, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
581
+    throw;
482 582
   }
483 583
 
484
-  int res = dlg->sendRequest(SIP_METH_INVITE, body,
584
+  int res = dlg->sendRequest(SIP_METH_INVITE, &body,
485 585
       co_ev->hdrs, SIP_FLAGS_VERBATIM);
486 586
   if (res < 0) {
487 587
     DBG("sending INVITE failed, relaying back error reply\n");
... ...
@@ -491,6 +591,8 @@ void CallLeg::onB2BConnect(ConnectLegEvent* co_ev)
491 591
     return;
492 592
   }
493 593
 
594
+  updateCallStatus(NoReply);
595
+
494 596
   if (co_ev->relayed_invite) {
495 597
     AmSipRequest fake_req;
496 598
     fake_req.method = SIP_METH_INVITE;
... ...
@@ -520,10 +622,13 @@ void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
520 622
   ev->markAsProcessed();
521 623
 
522 624
   // release old signaling and media session
523
-  terminateOtherLeg();
524
-  resumeHeld(false);
625
+  clear_other();
525 626
   clearRtpReceiverRelay();
526 627
 
628
+  // check if we aren't processing INVITE now (BLF ringing call pickup)
629
+  AmSipRequest *invite = dlg->getUASPendingInv();
630
+  if (invite) acceptPendingInvite(invite);
631
+
527 632
   setOtherId(ev->session_tag);
528 633
   if (ev->role == ReconnectLegEvent::A) a_leg = true;
529 634
   else a_leg = false;
... ...
@@ -546,46 +651,18 @@ void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
546 651
 		  "to", dlg->getRemoteParty().c_str(),
547 652
 		  "ruri", dlg->getRemoteUri().c_str());
548 653
 
549
-  AmMimeBody r_body(ev->body);
550
-  const AmMimeBody* body = &ev->body;
551
-  if (rtp_relay_mode != RTP_Direct) {
552
-    try {
553
-      body = ev->body.hasContentType(SIP_APPLICATION_SDP);
554
-      if (body && updateLocalBody(*body, *r_body.hasContentType(SIP_APPLICATION_SDP))) {
555
-        body = &r_body;
556
-      }
557
-      else {
558
-        body = &ev->body;
559
-      }
560
-    } catch (const string& s) {
561
-      relayError(SIP_METH_INVITE, ev->r_cseq, true, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
562
-      throw;
563
-    }
564
-  }
565
-
566
-  // generate re-INVITE
567
-  int res = dlg->sendRequest(SIP_METH_INVITE, body, ev->hdrs, SIP_FLAGS_VERBATIM);
568
-  if (res < 0) {
569
-    DBG("sending re-INVITE failed, relaying back error reply\n");
570
-    relayError(SIP_METH_INVITE, ev->r_cseq, true, res);
571
-
572
-    stopCall(StatusChangeCause::InternalError);
573
-    return;
574
-  }
575
-
576
-  if (ev->relayed_invite) {
577
-    AmSipRequest fake_req;
578
-    fake_req.method = SIP_METH_INVITE;
579
-    fake_req.cseq = ev->r_cseq;
580
-    relayed_req[dlg->cseq - 1] = fake_req;
581
-    est_invite_other_cseq = ev->r_cseq;
582
-  }
583
-  else est_invite_other_cseq = 0;
584
-
585
-  saveSessionDescription(ev->body);
586
-
587
-  // save CSeq of establising INVITE
588
-  est_invite_cseq = dlg->cseq - 1;
654
+  if (invite) {
655
+    // there is pending INVITE, replied just above but we need to wait for ACK
656
+    // before sending re-INVITE with real body
657
+    PendingReinvite p;
658
+    p.hdrs = ev->hdrs;
659
+    p.body = ev->body;
660
+    p.relayed_invite = ev->relayed_invite;
661
+    p.r_cseq = ev->r_cseq;
662
+    p.establishing = true;
663
+    pending_reinvites.push(p);
664
+  }
665
+  else reinvite(ev->hdrs, ev->body, ev->relayed_invite, ev->r_cseq, true);
589 666
 }
590 667
 
591 668
 void CallLeg::onB2BReplace(ReplaceLegEvent *e)
... ...
@@ -639,7 +716,7 @@ void CallLeg::onB2BReplaceInProgress(ReplaceInProgressEvent *e)
639 716
   }
640 717
 }
641 718
 
642
-void CallLeg::disconnect(bool hold_remote)
719
+void CallLeg::disconnect(bool hold_remote, bool preserve_media_session)
643 720
 {
644 721
   TRACE("disconnecting call leg %s from the other\n", getLocalTag().c_str());
645 722
 
... ...
@@ -653,14 +730,20 @@ void CallLeg::disconnect(bool hold_remote)
653 730
     case Ringing:
654 731
       WARN("trying to disconnect in not connected state, terminating not connected legs in advance (was it intended?)\n");
655 732
       terminateNotConnectedLegs();
656
-      break;
733
+      // do not break, continue with following state handling!
657 734
 
658 735
     case Connected:
659
-      resumeHeld(false); // TODO: do this as part of clearRtpReceiverRelay
660
-      clearRtpReceiverRelay(); // we can't stay connected (at media level) with the other leg
736
+      if (!preserve_media_session) {
737
+        // we can't stay connected (at media level) with the other leg
738
+        clearRtpReceiverRelay();
739
+      }
661 740
       break; // this is OK
662 741
   }
663 742
 
743
+  // create new media session for us if needed
744
+  if (getRtpRelayMode() != RTP_Direct && !preserve_media_session)
745
+    setMediaSession(new AmB2BMedia(a_leg ? this: NULL, a_leg ? NULL : this));
746
+
664 747
   clear_other();
665 748
   set_sip_relay_only(false); // we can't relay once disconnected
666 749
 
... ...
@@ -671,117 +754,88 @@ void CallLeg::disconnect(bool hold_remote)
671 754
   }
672 755
 }
673 756
 
674
-void CallLeg::createHoldRequest(AmSdp &sdp)
757
+static void sdp2body(const AmSdp &sdp, AmMimeBody &body)
675 758
 {
676
-  AmB2BMedia *ms = getMediaSession();
677
-  if (ms) {
678
-    ms->mute(a_leg);
679
-    ms->createHoldRequest(sdp, a_leg, false /*mark_zero_connection*/, true /*mark_sendonly*/);
680
-  }
681
-  else {
682
-    sdp.clear();
683
-
684
-    // FIXME: versioning
685
-    sdp.version = 0;
686
-    sdp.origin.user = "sems";
687
-    //offer.origin.sessId = 1;
688
-    //offer.origin.sessV = 1;
689
-    sdp.sessionName = "sems";
690
-    sdp.conn.network = NT_IN;
691
-    sdp.conn.addrType = AT_V4;
692
-    sdp.conn.address = "0.0.0.0";
693
-
694
-    // FIXME: use media line from stored body?
695
-    sdp.media.push_back(SdpMedia());
696
-    SdpMedia &m = sdp.media.back();
697
-    m.type = MT_AUDIO;
698
-    m.transport = TP_RTPAVP;
699
-    m.send = false;
700
-    m.recv = false;
701
-    m.payloads.push_back(SdpPayload(0));
702
-  }
759
+  string body_str;
760
+  sdp.print(body_str);
761
+
762
+  AmMimeBody *s = body.hasContentType(SIP_APPLICATION_SDP);
763
+  if (s) s->parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
764
+  else body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
703 765
 }
704 766
 
705 767
 void CallLeg::putOnHold()
706 768
 {
707
-  hold_status = HoldRequested;
708
-
709
-  if (isOnHold()) {
710
-    handleHoldReply(true); // really?
711
-    return;
712
-  }
769
+  if (on_hold) return;
713 770
 
714 771
   TRACE("putting remote on hold\n");
772
+  oa.hold = OA::HoldRequested;
773
+
774
+  holdRequested();
715 775
 
716 776
   AmSdp sdp;
717 777
   createHoldRequest(sdp);
718 778
   updateLocalSdp(sdp);
719 779
 
720
-  // generate re-INVITE with hold request
721
-  //reinvite(sdp, hold_request_cseq);
722 780
   AmMimeBody body;
723
-  string body_str;
724
-  sdp.print(body_str);
725
-  body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
781
+  sdp2body(sdp, body);
726 782
   if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
727 783
     ERROR("re-INVITE failed\n");
728
-    handleHoldReply(false);
784
+    offerRejected();
729 785
   }
730
-  else hold_request_cseq = dlg->cseq - 1;
786
+  //else hold_request_cseq = dlg->cseq - 1;
731 787
 }
732 788
 
733
-void CallLeg::resumeHeld(bool send_reinvite)
789
+void CallLeg::resumeHeld(/*bool send_reinvite*/)
734 790
 {
735
-  hold_status = ResumeRequested;
791
+  if (!on_hold) return;
736 792
 
737
-  if (!isOnHold()) {
738
-    handleHoldReply(true); // really?
739
-    return;
740
-  }
793
+  try {
794
+    TRACE("resume held remote\n");
795
+    oa.hold = OA::ResumeRequested;
741 796
 
742
-  TRACE("resume held remote\n");
797
+    resumeRequested();
743 798
 
744
-  if (!send_reinvite) {
745
-    // probably another SDP in progress
746
-    handleHoldReply(true);
747
-    return;
748
-  }
749
-
750
-  AmSdp sdp;
751
-  if (sdp.parse((const char *)established_body.getPayload()) == 0)
799
+    AmSdp sdp;
800
+    createResumeRequest(sdp);
801
+    if (sdp.media.empty()) {
802
+      ERROR("invalid un-hold SDP, can't unhold\n");
803
+      offerRejected();
804
+      return;
805
+    }
752 806
     updateLocalSdp(sdp);
753 807
 
754
-  if (dlg->reinvite("", &established_body, SIP_FLAGS_VERBATIM) != 0) {
755
-    ERROR("re-INVITE failed\n");
756
-    handleHoldReply(false);
808
+    AmMimeBody body(established_body);
809
+    sdp2body(sdp, body);
810
+    if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
811
+      ERROR("re-INVITE failed\n");
812
+      offerRejected();
813
+    }
814
+    //else hold_request_cseq = dlg->cseq - 1;
815
+  }
816
+  catch (...) {
817
+    offerRejected();
757 818
   }
758
-  else hold_request_cseq = dlg->cseq - 1;
759 819
 }
760 820
 
761
-void CallLeg::handleHoldReply(bool succeeded)
821
+void CallLeg::holdAccepted()
762 822
 {
763
-  switch (hold_status) {
764
-    case HoldRequested:
765
-      // ignore the result (if hold request is not accepted that's a pitty but
766
-      // we are Disconnected anyway)
767
-      if (call_status == Disconnecting) updateCallStatus(Disconnected);
768
-
769
-      if (succeeded) hold_status = OnHold; // remote put on hold successfully
770
-      break;
823
+  if (call_status == Disconnecting) updateCallStatus(Disconnected);
824
+  on_hold = true;
825
+  AmB2BMedia *ms = getMediaSession();
826
+  if (ms) ms->mute(!a_leg); // mute the stream in other (!) leg
827
+}
771 828
 
772
-    case ResumeRequested:
773
-      if (succeeded) {
774
-        hold_status = NotHeld; // call resumed successfully
775
-        AmB2BMedia *ms = getMediaSession();
776
-        if (ms) ms->unmute(a_leg);
777
-      }
778
-      break;
829
+void CallLeg::holdRejected()
830
+{
831
+  if (call_status == Disconnecting) updateCallStatus(Disconnected);
832
+}
779 833
 
780
-    case NotHeld:
781
-    case OnHold:
782
-      //DBG("handling hold reply but hold was not requested\n");
783
-      break;
784
-  }
834
+void CallLeg::resumeAccepted()
835
+{
836
+  on_hold = false;
837
+  AmB2BMedia *ms = getMediaSession();
838
+  if (ms) ms->unmute(!a_leg); // unmute the stream in other (!) leg
785 839
 }
786 840
 
787 841
 // was for caller only
... ...
@@ -801,13 +855,11 @@ void CallLeg::onInvite(const AmSipRequest& req)
801 855
     recvd_req.insert(std::make_pair(req.cseq, req));
802 856
 
803 857
     initial_sdp_stored = false;
804
-    if (rtp_relay_mode != RTP_Direct) {
805
-      const AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP);
806
-      DBG("SDP %sfound in initial INVITE\n", sdp_body ? "": "not ");
807
-      if (sdp_body && (initial_sdp.parse((const char *)sdp_body->getPayload()) == 0)) {
808
-        DBG("storing remote SDP for later\n");
809
-        initial_sdp_stored = true;
810
-      }
858
+    const AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP);
859
+    DBG("SDP %sfound in initial INVITE\n", sdp_body ? "": "not ");
860
+    if (sdp_body && (initial_sdp.parse((const char *)sdp_body->getPayload()) == 0)) {
861
+      DBG("storing remote SDP for later\n");
862
+      initial_sdp_stored = true;
811 863
     }
812 864
   }
813 865
 }
... ...
@@ -841,6 +893,13 @@ void CallLeg::onSipRequest(const AmSipRequest& req)
841 893
     else
842 894
       AmB2BSession::onSipRequest(req);
843 895
   }
896
+
897
+  if (req.method == SIP_METH_ACK && !pending_reinvites.empty()) {
898
+    TRACE("ACK received, we can send a queued re-INVITE\n");
899
+    PendingReinvite p = pending_reinvites.front();
900
+    pending_reinvites.pop();
901
+    reinvite(p.hdrs, p.body, p.relayed_invite, p.r_cseq, p.establishing);
902
+  }
844 903
 }
845 904
 
846 905
 void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status)
... ...
@@ -854,25 +913,23 @@ void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSip
854 913
       (relayed_request ? "to relayed request" : "to locally generated request"),
855 914
       callStatus2str(call_status));
856 915
 
857
-  if ((reply.cseq == hold_request_cseq) && (reply.cseq_method == SIP_METH_INVITE)) {
858
-    // hold request replied - handle it
859
-    if (reply.code >= 200) { // handle final replies only
860
-      hold_request_cseq = 0;
861
-      handleHoldReply(reply.code < 300);
862
-    }
863
-
864
-    // we don't want to relay this reply to the other leg! => do the necessary
865
-    // stuff here (copy & paste from AmB2BSession::onSipReply)
866
-    if (rtp_relay_mode != RTP_Direct && reply.code < 300) {
916
+#if 0
917
+  if ((oa.hold != OA::PreserveHoldStatus) && (!relayed_request)) {
918
+    INFO("locally generated hold/resume request replied, not handling by B2B\n");
919
+    // local hold/resume request replied, we don't want to relay this reply to the other leg!
920
+    // => do the necessary stuff here (copy & paste from AmB2BSession::onSipReply)
921
+    if (reply.code < 300) {
867 922
       const AmMimeBody *sdp_part = reply.body.hasContentType(SIP_APPLICATION_SDP);
868 923
       if (sdp_part) {
869 924
         AmSdp sdp;
870 925
         if (sdp.parse((const char *)sdp_part->getPayload()) == 0) updateRemoteSdp(sdp);
871 926
       }
872 927
     }
928
+    else if (reply.code >= 300) offerRejected();
873 929
     AmSession::onSipReply(req, reply, old_dlg_status);
874 930
     return;
875 931
   }
932
+#endif
876 933
 
877 934
   AmB2BSession::onSipReply(req, reply, old_dlg_status);
878 935
 
... ...
@@ -951,6 +1008,7 @@ void CallLeg::onRemoteDisappeared(const AmSipReply& reply)
951 1008
 // was for caller only
952 1009
 void CallLeg::onBye(const AmSipRequest& req)
953 1010
 {
1011
+  terminateNotConnectedLegs();
954 1012
   updateCallStatus(Disconnected, &req);
955 1013
   clearRtpReceiverRelay(); // FIXME: shouldn't be cleared in AmB2BSession as well?
956 1014
   AmB2BSession::onBye(req);
... ...
@@ -1093,6 +1151,22 @@ void CallLeg::addCallee(const string &session_tag, const AmSipRequest &relayed_i
1093 1151
   addExistingCallee(session_tag, new ReconnectLegEvent(getLocalTag(), relayed_invite));
1094 1152
 }
1095 1153
 
1154
+void CallLeg::addCallee(CallLeg *callee, const string &hdrs)
1155
+{
1156
+  if (!non_hold_sdp.media.empty()) {
1157
+    // use non-hold SDP if possible
1158
+    AmMimeBody body(established_body);
1159
+    sdp2body(non_hold_sdp, body);
1160
+    addNewCallee(callee, new ConnectLegEvent(hdrs, body));
1161
+  }
1162
+  else addNewCallee(callee, new ConnectLegEvent(hdrs, established_body));
1163
+}
1164
+
1165
+/*void CallLeg::addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode)
1166
+{
1167
+  addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode);
1168
+}*/
1169
+
1096 1170
 void CallLeg::replaceExistingLeg(const string &session_tag, const AmSipRequest &relayed_invite)
1097 1171
 {
1098 1172
   // add existing session as our B leg
... ...
@@ -1198,6 +1272,24 @@ void CallLeg::changeRtpMode(RTPRelayMode new_mode)
1198 1272
       }
1199 1273
       break;
1200 1274
   }
1275
+
1276
+  switch (oa.status) {
1277
+    case OA::None:
1278
+      // must be followed by OA exchange because we can't updateLocalSdp
1279
+      // (reINVITE would be needed)
1280
+      break;
1281
+
1282
+    case OA::OfferSent:
1283
+      TRACE("changing RTP mode after offer was sent: reINVITE needed\n");
1284
+      // TODO: plan a reINVITE
1285
+      ERROR("not implemented\n");
1286
+      break;
1287
+
1288
+    case OA::OfferReceived:
1289
+      TRACE("changing RTP mode after offer was received, needed to updateRemoteSdp again\n");
1290
+      AmB2BSession::updateRemoteSdp(oa.remote_sdp); // hack
1291
+      break;
1292
+  }
1201 1293
 }
1202 1294
 
1203 1295
 void CallLeg::changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media)
... ...
@@ -1234,6 +1326,25 @@ void CallLeg::changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media)
1234 1326
 
1235 1327
   AmB2BMedia *m = getMediaSession();
1236 1328
   if (m) m->changeSession(a_leg, this);
1329
+
1330
+  switch (oa.status) {
1331
+    case OA::None:
1332
+      // must be followed by OA exchange because we can't updateLocalSdp
1333
+      // (reINVITE would be needed)
1334
+      break;
1335
+
1336
+    case OA::OfferSent:
1337
+      TRACE("changing RTP mode/media session after offer was sent: reINVITE needed\n");
1338
+      // TODO: plan a reINVITE
1339
+      ERROR("%s: not implemented\n", getLocalTag().c_str());
1340
+      break;
1341
+
1342
+    case OA::OfferReceived:
1343
+      TRACE("changing RTP mode/media session after offer was received, needed to updateRemoteSdp again\n");
1344
+      AmB2BSession::updateRemoteSdp(oa.remote_sdp); // hack
1345
+      break;
1346
+  }
1347
+
1237 1348
 }
1238 1349
 
1239 1350
 void CallLeg::changeOtherLegsRtpMode(RTPRelayMode new_mode)
... ...
@@ -1259,4 +1370,208 @@ void CallLeg::changeOtherLegsRtpMode(RTPRelayMode new_mode)
1259 1370
   }
1260 1371
 }
1261 1372
 
1373
+void CallLeg::acceptPendingInvite(AmSipRequest *invite)
1374
+{
1375
+  // reply the INVITE with fake 200 reply
1376
+
1377
+  AmMimeBody *sdp = invite->body.hasContentType(SIP_APPLICATION_SDP);
1378
+  AmSdp s;
1379
+  if (!sdp || s.parse((const char*)sdp->getPayload())) {
1380
+    // no offer in the INVITE (or can't be parsed), we have to append fake offer
1381
+    // into the reply
1382
+    s.version = 0;
1383
+    s.origin.user = "sems";
1384
+    s.sessionName = "sems";
1385
+    s.conn.network = NT_IN;
1386
+    s.conn.addrType = AT_V4;
1387
+    s.conn.address = "0.0.0.0";
1388
+
1389
+    s.media.push_back(SdpMedia());
1390
+    SdpMedia &m = s.media.back();
1391
+    m.type = MT_AUDIO;
1392
+    m.transport = TP_RTPAVP;
1393
+    m.send = false;
1394
+    m.recv = false;
1395
+    m.payloads.push_back(SdpPayload(0));
1396
+  }
1397
+
1398
+  if (!s.conn.address.empty()) s.conn.address = "0.0.0.0";
1399
+  for (vector<SdpMedia>::iterator i = s.media.begin(); i != s.media.end(); ++i) {
1400
+    //i->port = 0;
1401
+    if (!i->conn.address.empty()) i->conn.address = "0.0.0.0";
1402
+  }
1403
+
1404
+  AmMimeBody body;
1405
+  string body_str;
1406
+  s.print(body_str);
1407
+  body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
1408
+  try {
1409
+    updateLocalBody(body);
1410
+  } catch (...) { /* throw ? */  }
1411
+
1412
+  TRACE("replying pending INVITE with body: %s\n", body_str.c_str());
1413
+  dlg->reply(*invite, 200, "OK", &body);
1414
+
1415
+  if (getCallStatus() != Connected) updateCallStatus(Connected);
1416
+}
1417
+
1418
+void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing)
1419
+{
1420
+  int res;
1421
+  try {
1422
+    AmMimeBody r_body(body);
1423
+    updateLocalBody(r_body);
1424
+    res = dlg->sendRequest(SIP_METH_INVITE, &r_body, hdrs, SIP_FLAGS_VERBATIM);
1425
+  } catch (const string& s) { res = -500; }
1426
+
1427
+  if (res < 0) {
1428
+    if (relayed) {
1429
+      DBG("sending re-INVITE failed, relaying back error reply\n");
1430
+      relayError(SIP_METH_INVITE, r_cseq, true, res);
1431
+    }
1432
+
1433
+    DBG("sending re-INVITE failed, terminating the call\n");
1434
+    stopCall(StatusChangeCause::InternalError);
1435
+    return;
1436
+  }
1437
+
1438
+  if (relayed) {
1439
+    AmSipRequest fake_req;
1440
+    fake_req.method = SIP_METH_INVITE;
1441
+    fake_req.cseq = r_cseq;
1442
+    relayed_req[dlg->cseq - 1] = fake_req;
1443
+    est_invite_other_cseq = r_cseq;
1444
+  }
1445
+  else est_invite_other_cseq = 0;
1446
+
1447
+  saveSessionDescription(body);
1448
+
1449
+  if (establishing) {
1450
+    // save CSeq of establishing INVITE
1451
+    est_invite_cseq = dlg->cseq - 1;
1452
+  }
1453
+}
1454
+
1455
+void CallLeg::adjustOffer(AmSdp &sdp)
1456
+{
1457
+  if (oa.hold != OA::PreserveHoldStatus) {
1458
+    // locally generated hold/unhold requests that already contain correct
1459
+    // hold/resume bodies and need not to be altered via createHoldRequest
1460
+    // hold/resumeRequested is already called
1461
+  }
1462
+  else {
1463
+    // handling B2B SDP, check for hold/unhold
1464
+
1465
+    HoldMethod hm;
1466
+    // if hold request, transform to requested kind of hold and remember that hold
1467
+    // was requested with this offer
1468
+    if (isHoldRequest(sdp, hm)) {
1469
+      holdRequested();
1470
+      alterHoldRequest(sdp);
1471
+      oa.hold = OA::HoldRequested;
1472
+    }
1473
+    else {
1474
+      if (on_hold) {
1475
+        resumeRequested();
1476
+        alterResumeRequest(sdp);
1477
+        oa.hold = OA::ResumeRequested;
1478
+      }
1479
+    }
1480
+  }
1481
+}
1482
+
1483
+void CallLeg::updateLocalSdp(AmSdp &sdp)
1484
+{
1485
+  TRACE("%s: updateLocalSdp\n", getLocalTag().c_str());
1486
+  // handle the body based on current offer-answer status
1487
+  // (possibly update the body before sending to remote)
1488
+
1489
+  switch (oa.status) {
1490
+    case OA::None:
1491
+      adjustOffer(sdp);
1492
+      oa.status = OA::OfferSent;
1493
+      //FIXME: oa.offer_cseq = dlg->cseq;
1494
+      break;
1495
+
1496
+    case OA::OfferSent:
1497
+      ERROR("BUG: another SDP offer to be sent before answer/reject");
1498
+      oa.clear(); // or call offerRejected?
1499
+      break;
1500
+
1501
+    case OA::OfferReceived:
1502
+      // sending the answer
1503
+      oaCompleted();
1504
+      break;
1505
+  }
1506
+
1507
+  if (oa.hold == OA::PreserveHoldStatus && !on_hold) {
1508
+    // store non-hold SDP to be able to resumeHeld
1509
+    non_hold_sdp = sdp;
1510
+  }
1511
+
1512
+  AmB2BSession::updateLocalSdp(sdp);
1513
+}
1514
+
1515
+void CallLeg::updateRemoteSdp(AmSdp &sdp)
1516
+{
1517
+  TRACE("%s: updateRemoteSdp\n", getLocalTag().c_str());
1518
+  switch (oa.status) {
1519
+    case OA::None:
1520
+      oa.status = OA::OfferReceived;
1521
+      oa.remote_sdp = sdp;
1522
+      break;
1523
+
1524
+    case OA::OfferSent:
1525
+      oaCompleted();
1526
+      break;
1527
+
1528
+    case OA::OfferReceived:
1529
+      ERROR("BUG: another SDP offer received before answer/reject");
1530
+      oa.clear(); // or call offerRejected?
1531
+      break;
1532
+  }
1533
+
1534
+  AmB2BSession::updateRemoteSdp(sdp);
1535
+}
1536
+
1537
+void CallLeg::oaCompleted()
1538
+{
1539
+  TRACE("%s: oaCompleted\n", getLocalTag().c_str());
1540
+  switch (oa.hold) {
1541
+    case OA::HoldRequested: holdAccepted(); break;
1542
+    case OA::ResumeRequested: resumeAccepted(); break;
1543
+    case OA::PreserveHoldStatus: break;
1544
+  }
1545
+
1546
+  // call a callback here?
1547
+  oa.clear();
1548
+}
1549
+
1550
+void CallLeg::offerRejected()
1551
+{
1552
+  switch (oa.hold) {
1553
+    case OA::HoldRequested: holdRejected(); break;
1554
+    case OA::ResumeRequested: resumeRejected(); break;
1555
+    case OA::PreserveHoldStatus: break;
1556
+  }
1557
+
1558
+  oa.clear();
1559
+}
1560
+
1561
+void CallLeg::createResumeRequest(AmSdp &sdp)
1562
+{
1563
+  // use stored non-hold SDP
1564
+  // Note: this SDP doesn't need to be correct, but established_body need not to
1565
+  // be good enough for unholding (might be held already with zero conncetions)
1566
+  if (!non_hold_sdp.media.empty()) sdp = non_hold_sdp;
1567
+  else {
1568
+    // no stored non-hold SDP
1569
+    ERROR("no stored non-hold SDP, but local resume requested\n");
1570
+    // TODO: try to use established_body here and mark properly
1571
+
1572
+    // if no established body exist
1573
+    throw string("not implemented");
1574
+  }
1575
+  // do not touch the sdp otherwise (use directly B2B SDP)
1576
+}
1262 1577
 
... ...
@@ -5,6 +5,17 @@
5 5
 #include "AmSessionContainer.h"
6 6
 #include "CallLegEvents.h"
7 7
 
8
+#include <queue>
9
+
10
+struct PendingReinvite
11
+{
12
+  string hdrs;
13
+  AmMimeBody body;
14
+  unsigned r_cseq;
15
+  bool relayed_invite;
16
+  bool establishing;
17
+};
18
+
8 19
 /** composed AmB2BCalleeSession & AmB2BCallerSession
9 20
  * represents indepenedently A or B leg of a call,
10 21
  * old clases left for compatibility
... ...
@@ -115,8 +126,29 @@ class CallLeg: public AmB2BSession
115 126
      * A leg, i.e. it creates new B leg(s) for itself. */
116 127
     std::vector<OtherLegInfo> other_legs;
117 128
 
118
-    unsigned hold_request_cseq; // CSeq of a request generated by us to hold the call
119
-    enum { NotHeld, OnHold, HoldRequested, ResumeRequested } hold_status; // remote is put on hold by us
129
+    bool on_hold; // remote is on hold
130
+    AmSdp non_hold_sdp;
131
+
132
+    std::queue<PendingReinvite> pending_reinvites;
133
+
134
+    // offer-answer related stuff
135
+    struct OA {
136
+      enum { None, OfferSent, OfferReceived } status;
137
+      enum { HoldRequested, ResumeRequested, PreserveHoldStatus } hold;
138
+      OA(): status(None), hold(PreserveHoldStatus) { }
139
+      void clear() { status = None; hold = PreserveHoldStatus; remote_sdp.clear(); }
140
+
141
+      /* SDP of remote end. Needed because of the possibility of RTP relay mode
142
+       * change during offer-answer exchange. */
143
+      AmSdp remote_sdp;
144
+    } oa;
145
+
146
+    // generate re-INVITE with given parameters (establishing means that the
147
+    // INVITE is establishing a connection between two legs)
148
+    void reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing);
149
+
150
+    // generate 200 reply on a pending INVITE (uses fake body)
151
+    void acceptPendingInvite(AmSipRequest *invite);
120 152
 
121 153
     // methods just for make this stuff more readable, not intended to be
122 154
     // overriden, override onB2BEvent instead!
... ...
@@ -183,6 +215,15 @@ class CallLeg: public AmB2BSession
183 215
     void changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media);
184 216
     void changeOtherLegsRtpMode(RTPRelayMode new_mode);
185 217
 
218
+    // offer-answer handling
219
+    void adjustOffer(AmSdp &sdp);
220
+    void oaCompleted(); // offer-answer exchange completed, both SDPs are handled
221
+
222
+     /* offer was rejected (called just for negative replies to an request
223
+      * carying offer (not always correct?), answer with disabled streams
224
+      * doesn't cause calling this */
225
+     void offerRejected();
226
+
186 227
   protected:
187 228
 
188 229
     // functions offered to successors
... ...
@@ -210,11 +251,28 @@ class CallLeg: public AmB2BSession
210 251
     virtual void onInitialReply(B2BSipReplyEvent *e);
211 252
 
212 253
     /* callback method called when hold/resume request is replied */
213
-    virtual void handleHoldReply(bool succeeded);
254
+//    virtual void handleHoldReply(bool succeeded);
255
+
256
+    /* called to create SDP of locally generated hold request */
257
+    virtual void createHoldRequest(AmSdp &sdp) = 0;
214 258
 
215
-    /* called to create SDP of hold request
216
-     * (note that resume request always uses established_body stored before) */
217
-    virtual void createHoldRequest(AmSdp &sdp);
259
+    /** called to alter B2B hold request (i.e. the request from other peer) */
260
+    virtual void alterHoldRequest(AmSdp &sdp) { }
261
+
262
+    /* called to create SDP of locally generated resume request */
263
+    virtual void createResumeRequest(AmSdp &sdp);
264
+
265
+    /** called to alter B2B hold request (i.e. the request from other peer) */
266
+    virtual void alterResumeRequest(AmSdp &sdp) { }
267
+
268
+    /* hold requested (either from B2B peer leg or locally)
269
+     * to be overridden */
270
+    virtual void holdRequested() { }
271
+    virtual void holdAccepted();
272
+    virtual void holdRejected();
273
+    virtual void resumeRequested() { }
274
+    virtual void resumeAccepted();
275
+    virtual void resumeRejected() { }
218 276
 
219 277
     virtual void terminateOtherLeg();
220 278
     virtual void terminateLeg();
... ...
@@ -232,6 +290,9 @@ class CallLeg: public AmB2BSession
232 290
      * TODO: add parameter to trigger reINVITE */
233 291
     void changeRtpMode(RTPRelayMode new_mode);
234 292
 
293
+    virtual void updateLocalSdp(AmSdp &sdp);
294
+    virtual void updateRemoteSdp(AmSdp &sdp);
295
+
235 296
   public:
236 297
     virtual void onB2BEvent(B2BEvent* ev);
237 298
 
... ...
@@ -241,7 +302,7 @@ class CallLeg: public AmB2BSession
241 302
      *
242 303
      * The other leg is not affected by disconnect - it is neither terminated
243 304
      * nor informed about the peer disconnection. */
244
-    virtual void disconnect(bool hold_remote);
305
+    virtual void disconnect(bool hold_remote, bool preserve_media_session = false);
245 306
 
246 307
     /** Terminate the whole B2B call (if there is no other leg only this one is
247 308
      * stopped). */
... ...
@@ -256,9 +317,9 @@ class CallLeg: public AmB2BSession
256 317
     virtual void putOnHold();
257 318
 
258 319
     /** resume call if the remote party is on hold */
259
-    virtual void resumeHeld(bool send_reinvite);
320
+    virtual void resumeHeld(/*bool send_reinvite*/);
260 321
 
261
-    virtual bool isOnHold() { return hold_status == OnHold; }
322
+    virtual bool isOnHold() { return on_hold; }
262 323
 
263 324
 
264 325
     /** add given call leg as our B leg */
... ...
@@ -274,8 +335,8 @@ class CallLeg: public AmB2BSession
274 335
      * Can be used in A leg and B leg as well.
275 336
      * Additional headers may be specified - these are used in outgoing INVITE
276 337
      * to the callee's destination. */
277
-    void addCallee(CallLeg *callee, const string &hdrs) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body)); }
278
-    void addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode); }
338
+    void addCallee(CallLeg *callee, const string &hdrs);
339
+//    void addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode); }
279 340
 
280 341
 
281 342
     /** Replace given already existing session in a B2B call. We become new
... ...
@@ -8,10 +8,11 @@ enum {
8 8
   ReplaceLeg,
9 9
   ReplaceInProgress,
10 10
   DisconnectLeg,
11
-  ChangeRtpModeEventId
11
+  ChangeRtpModeEventId,
12
+  ResumeHeld
12 13
 };
13 14
 
14
-#define LAST_B2B_CALL_LEG_EVENT_ID ChangeRtpModeEventId
15
+#define LAST_B2B_CALL_LEG_EVENT_ID ResumeHeld
15 16
 
16 17
 struct ConnectLegEvent: public B2BEvent
17 18
 {
... ...
@@ -138,7 +139,11 @@ struct ReplaceInProgressEvent: public B2BEvent
138 139
 struct DisconnectLegEvent: public B2BEvent
139 140
 {
140 141
   bool put_remote_on_hold;
141
-  DisconnectLegEvent(bool _put_remote_on_hold): B2BEvent(DisconnectLeg), put_remote_on_hold(_put_remote_on_hold) { }
142
+  bool preserve_media_session;
143
+  DisconnectLegEvent(bool _put_remote_on_hold, bool _preserve_media_session = false):
144
+    B2BEvent(DisconnectLeg),
145
+    put_remote_on_hold(_put_remote_on_hold),
146
+    preserve_media_session(_preserve_media_session) { }
142 147
 };
143 148
 
144 149
 /* we don't need to have 'reliable event' for this because we are always
... ...
@@ -155,5 +160,9 @@ struct ChangeRtpModeEvent: public B2BEvent
155 160
   virtual ~ChangeRtpModeEvent() { if (media && media->releaseReference()) delete media; }
156 161
 };
157 162
 
163
+struct ResumeHeldEvent: public B2BEvent
164
+{
165
+  ResumeHeldEvent(): B2BEvent(ResumeHeld) { }
166
+};
158 167
 
159 168
 #endif
... ...
@@ -68,10 +68,12 @@ class ExtendedCCInterface
68 68
     // hold related functionality (seems to work best being explicitly supported
69 69
     // with API than hacking based on another callbacks)
70 70
 
71
-    virtual CCChainProcessing putOnHold(SBCCallLeg *call) { return ContinueProcessing; }
72
-    virtual CCChainProcessing resumeHeld(SBCCallLeg *call, bool send_reinvite) { return ContinueProcessing; }
73
-    virtual CCChainProcessing createHoldRequest(SBCCallLeg *call, AmSdp &sdp) { return ContinueProcessing; }
74
-    virtual CCChainProcessing handleHoldReply(SBCCallLeg *call, bool succeeded) { return ContinueProcessing; }
71
+    virtual void holdRequested(SBCCallLeg *call) { }
72
+    virtual void holdAccepted(SBCCallLeg *call) { }
73
+    virtual void holdRejected(SBCCallLeg *call) { }
74
+    virtual void resumeRequested(SBCCallLeg *call) { }
75
+    virtual void resumeAccepted(SBCCallLeg *call) { }
76
+    virtual void resumeRejected(SBCCallLeg *call) { }
75 77
 
76 78
     /** Possibility to influence messages relayed to the B2B peer leg.
77 79
       return value:
... ...
@@ -62,69 +62,6 @@ static bool containsPayload(const std::vector<SdpPayload>& payloads, const SdpPa
62 62
 
63 63
 ///////////////////////////////////////////////////////////////////////////////////////////
64 64
 
65
-class SBCRelayController: public RelayController {
66
-  private:
67
-    SBCCallProfile::TranscoderSettings *transcoder_settings;
68
-    bool aleg;
69
-
70
-  public:
71
-    SBCRelayController(SBCCallProfile::TranscoderSettings *t, bool _aleg): transcoder_settings(t), aleg(_aleg) { }
72
-
73
-    virtual void computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask);
74
-};
75
-
76
-void SBCRelayController::computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask)
77
-{
78
-  TRACE("entering SBCRelayController::computeRelayMask(%s)\n", aleg ? "A leg" : "B leg");
79
-
80
-  PayloadMask m1, m2;
81
-  bool use_m1 = false;
82
-
83
-  /* if "m" contains only "norelay" codecs, relay is enabled for them (main idea
84
-   * of these codecs is to limit network bandwidth and it makes not much sense
85
-   * to transcode between codecs 'which are better to avoid', right?)
86
-   *
87
-   * if "m" contains other codecs, relay is enabled as well
88
-   *
89
-   * => if m contains at least some codecs, relay is enabled */
90
-  enable = !m.payloads.empty();
91
-
92
-  vector<SdpPayload> &norelay_payloads =
93
-    aleg ? transcoder_settings->audio_codecs_norelay_aleg : transcoder_settings->audio_codecs_norelay;
94
-
95
-  vector<SdpPayload>::const_iterator p;
96
-  for (p = m.payloads.begin(); p != m.payloads.end(); ++p) {
97
-
98
-    // do not mark telephone-event payload for relay (and do not use it for
99
-    // transcoding as well)
100
-    if(strcasecmp("telephone-event",p->encoding_name.c_str()) == 0) continue;
101
-
102
-    // mark every codec for relay in m2
103
-    TRACE("m2: marking payload %d for relay\n", p->payload_type);
104
-    m2.set(p->payload_type);
105
-
106
-    if (!containsPayload(norelay_payloads, *p, m.transport)) {
107
-      // this payload can be relayed
108
-
109
-      TRACE("m1: marking payload %d for relay\n", p->payload_type);
110
-      m1.set(p->payload_type);
111
-
112
-      if (!use_m1 && containsPayload(transcoder_settings->audio_codecs, *p, m.transport)) {
113
-        // the remote SDP contains transcodable codec which can be relayed (i.e.
114
-        // the one with higher "priority" so we want to disable relaying of the
115
-        // payloads which should not be ralyed if possible)
116
-        use_m1 = true;
117
-      }
118
-    }
119
-  }
120
-
121
-  TRACE("using %s\n", use_m1 ? "m1" : "m2");
122
-  if (use_m1) mask = m1;
123
-  else mask = m2;
124
-}
125
-
126
-///////////////////////////////////////////////////////////////////////////////////////////
127
-
128 65
 // map stream index and transcoder payload index (two dimensions) into one under
129 66
 // presumption that there will be less than 128 payloads for transcoding
130 67
 // (might be handy to remember mapping only for dynamic ones (96-127)
... ...
@@ -190,6 +127,7 @@ SBCCallLeg::SBCCallLeg(SBCCallLeg* caller, AmSipDialog* p_dlg,
190 127
   : auth(NULL),
191 128
     call_profile(caller->getCallProfile()),
192 129
     CallLeg(caller,p_dlg,p_subs),
130
+    ext_cc_timer_id(SBC_TIMER_ID_CALL_TIMERS_END + 1),
193 131
     cc_started(false),
194 132
     logger(NULL)
195 133
 {
... ...
@@ -658,7 +596,7 @@ void SBCCallLeg::onDtmf(int event, int duration)
658 596
   }
659 597
 }
660 598
 
661
-bool SBCCallLeg::updateLocalSdp(AmSdp &sdp)
599
+void SBCCallLeg::updateLocalSdp(AmSdp &sdp)
662 600
 {
663 601
   // anonymize SDP if configured to do so (we need to have our local media IP,
664 602
   // not the media IP of our peer leg there)
... ...
@@ -666,20 +604,12 @@ bool SBCCallLeg::updateLocalSdp(AmSdp &sdp)
666 604
 
667 605
   // remember transcodable payload IDs
668 606
   if (call_profile.transcoder.isActive()) savePayloadIDs(sdp);
669
-  return CallLeg::updateLocalSdp(sdp);
607
+  CallLeg::updateLocalSdp(sdp);
670 608
 }
671 609
 
672
-
673
-bool SBCCallLeg::updateRemoteSdp(AmSdp &sdp)
610
+void SBCCallLeg::updateRemoteSdp(AmSdp &sdp)
674 611
 {
675
-  SBCRelayController rc(&call_profile.transcoder, a_leg);
676
-  if (call_profile.transcoder.isActive()) {
677
-    AmB2BMedia *ms = getMediaSession();
678
-    if (ms) return ms->updateRemoteSdp(a_leg, sdp, &rc);
679
-  }
680
-
681
-  // call original implementation because our special conditions above are not met
682
-  return CallLeg::updateRemoteSdp(sdp);
612
+  CallLeg::updateRemoteSdp(sdp);
683 613
 }
684 614
 
685 615
 void SBCCallLeg::onControlCmd(string& cmd, AmArg& params) {
... ...
@@ -1341,7 +1271,7 @@ void SBCCallLeg::logCallStart(const AmSipReply& reply)
1341 1271
 					  (int)reply.code,reply.reason);
1342 1272
   }
1343 1273
   else {
1344
-    ERROR("could not log call-start/call-attempt (ci='%s';lt='%s')",
1274
+    DBG("could not log call-start/call-attempt (ci='%s';lt='%s')",
1345 1275
 	  getCallID().c_str(),getLocalTag().c_str());
1346 1276
   }
1347 1277
 }
... ...
@@ -1604,36 +1534,123 @@ void SBCCallLeg::initCCExtModules()
1604 1534
   }
1605 1535
 }
1606 1536
 
1607
-void SBCCallLeg::putOnHold()
1537
+#define CALL_EXT_CC_MODULES(method) \
1538
+  do { \
1539
+    for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) { \
1540
+      (*i)->method(this); \
1541
+    } \
1542
+  } while (0)
1543
+
1544
+void SBCCallLeg::holdRequested()
1608 1545
 {
1609
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1610
-    if ((*i)->putOnHold(this) == StopProcessing) return;
1611
-  }
1612
-  CallLeg::putOnHold();
1546
+  TRACE("%s: hold requested\n", getLocalTag().c_str());
1547
+  CALL_EXT_CC_MODULES(holdRequested);
1548
+  CallLeg::holdRequested();
1613 1549
 }
1614 1550
 
1615
-void SBCCallLeg::resumeHeld(bool send_reinvite)
1551
+void SBCCallLeg::holdAccepted()
1616 1552
 {
1617
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1618
-    if ((*i)->resumeHeld(this, send_reinvite) == StopProcessing) return;
1553
+  TRACE("%s: hold accepted\n", getLocalTag().c_str());
1554
+  CALL_EXT_CC_MODULES(holdAccepted);
1555
+  CallLeg::holdAccepted();
1556
+}
1557
+
1558
+void SBCCallLeg::holdRejected()
1559
+{
1560
+  TRACE("%s: hold rejected\n", getLocalTag().c_str());
1561
+  CALL_EXT_CC_MODULES(holdRejected);
1562
+  CallLeg::holdRejected();
1563
+}
1564
+
1565
+void SBCCallLeg::resumeRequested()
1566
+{
1567
+  TRACE("%s: resume requested\n", getLocalTag().c_str());
1568
+  CALL_EXT_CC_MODULES(resumeRequested);
1569
+  CallLeg::resumeRequested();
1570
+}
1571
+
1572
+void SBCCallLeg::resumeAccepted()
1573
+{
1574
+  TRACE("%s: resume accepted\n", getLocalTag().c_str());
1575
+  CALL_EXT_CC_MODULES(resumeAccepted);
1576
+  CallLeg::resumeAccepted();
1577
+}
1578
+
1579
+void SBCCallLeg::resumeRejected()
1580
+{
1581
+  TRACE("%s: resume rejected\n", getLocalTag().c_str());
1582
+  CALL_EXT_CC_MODULES(resumeRejected);
1583
+  CallLeg::resumeRejected();
1584
+}
1585
+
1586
+static void zero_connection(SdpConnection &c)
1587
+{
1588
+  if (!c.address.empty()) {
1589
+    if (c.network == NT_IN) {
1590
+      if (c.addrType == AT_V4) {
1591
+        c.address = "0.0.0.0";
1592
+        return;
1593
+      }
1594
+      // TODO: IPv6?
1595
+    }
1619 1596
   }
1620
-  CallLeg::resumeHeld(send_reinvite);
1597
+
1598
+  DBG("unsupported connection type for marking with 0.0.0.0");
1621 1599
 }
1622 1600
 
1623
-void SBCCallLeg::handleHoldReply(bool succeeded)
1601
+static void alterHoldRequest(AmSdp &sdp, bool mark_zero_con, bool enable_recv)
1624 1602
 {
1625
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1626
-    if ((*i)->handleHoldReply(this, succeeded) == StopProcessing) return;
1603
+  if (mark_zero_con) zero_connection(sdp.conn);
1604
+  for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
1605
+    if (mark_zero_con) zero_connection(m->conn);
1606
+    m->recv = enable_recv;
1627 1607
   }
1628
-  CallLeg::handleHoldReply(succeeded);
1608
+}
1609
+
1610
+void SBCCallLeg::alterHoldRequest(AmSdp &sdp)
1611
+{
1612
+  TRACE("altering B2B hold request\n");
1613
+
1614
+  if (!call_profile.hold_settings.alter_b2b(a_leg)) return;
1615
+
1616
+  ::alterHoldRequest(sdp,
1617
+      call_profile.hold_settings.mark_zero_connection(a_leg),
1618
+      call_profile.hold_settings.recv(a_leg));
1629 1619
 }
1630 1620
 
1631 1621
 void SBCCallLeg::createHoldRequest(AmSdp &sdp)
1632 1622
 {
1633
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1634
-    if ((*i)->createHoldRequest(this, sdp) == StopProcessing) return;
1635
-  }
1636
-  CallLeg::createHoldRequest(sdp);
1623
+  // hack: we need to have other side SDP (if the stream is hold already
1624
+  // it should be marked as inactive)
1625
+  // FIXME: fix SDP versioning! (remember generated versions and increase the
1626
+  // version number in every SDP passing through?)
1627
+
1628
+  AmMimeBody *s = established_body.hasContentType(SIP_APPLICATION_SDP);
1629
+  if (s) sdp.parse((const char*)s->getPayload());
1630
+  if (sdp.media.empty()) {
1631
+    // established SDP is not valid! generate complete fake
1632
+    sdp.version = 0;
1633
+    sdp.origin.user = "sems";
1634
+    sdp.sessionName = "sems";
1635
+    sdp.conn.network = NT_IN;
1636
+    sdp.conn.addrType = AT_V4;
1637
+    sdp.conn.address = "0.0.0.0";
1638
+
1639
+    sdp.media.push_back(SdpMedia());
1640
+    SdpMedia &m = sdp.media.back();
1641
+    m.type = MT_AUDIO;
1642
+    m.transport = TP_RTPAVP;
1643
+    m.send = false;
1644
+    m.recv = false;
1645
+    m.payloads.push_back(SdpPayload(0));
1646
+  }
1647
+
1648
+  ::alterHoldRequest(sdp,
1649
+      call_profile.hold_settings.mark_zero_connection(a_leg),
1650
+      call_profile.hold_settings.recv(a_leg));
1651
+
1652
+  AmB2BMedia *ms = getMediaSession();
1653
+  if (ms) ms->replaceOffer(sdp, a_leg);
1637 1654
 }
1638 1655
 
1639 1656
 void SBCCallLeg::setMediaSession(AmB2BMedia *new_session)
... ...
@@ -1675,3 +1692,60 @@ void SBCCallLeg::setLogger(msg_logger *_logger)
1675 1692
     else m->setRtpLogger(NULL);
1676 1693
   }
1677 1694
 }
1695
+
1696
+void SBCCallLeg::computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask)
1697
+{
1698
+  if (call_profile.transcoder.isActive()) {
1699
+    TRACE("entering transcoder's computeRelayMask(%s)\n", a_leg ? "A leg" : "B leg");
1700
+
1701
+    SBCCallProfile::TranscoderSettings &transcoder_settings = call_profile.transcoder;
1702
+    PayloadMask m1, m2;
1703
+    bool use_m1 = false;
1704
+
1705
+    /* if "m" contains only "norelay" codecs, relay is enabled for them (main idea
1706
+     * of these codecs is to limit network bandwidth and it makes not much sense
1707
+     * to transcode between codecs 'which are better to avoid', right?)
1708
+     *
1709
+     * if "m" contains other codecs, relay is enabled as well
1710
+     *
1711
+     * => if m contains at least some codecs, relay is enabled */
1712
+    enable = !m.payloads.empty();
1713
+
1714
+    vector<SdpPayload> &norelay_payloads =
1715
+      a_leg ? transcoder_settings.audio_codecs_norelay_aleg : transcoder_settings.audio_codecs_norelay;
1716
+
1717
+    vector<SdpPayload>::const_iterator p;
1718
+    for (p = m.payloads.begin(); p != m.payloads.end(); ++p) {
1719
+
1720
+      // do not mark telephone-event payload for relay (and do not use it for
1721
+      // transcoding as well)
1722
+      if(strcasecmp("telephone-event",p->encoding_name.c_str()) == 0) continue;
1723
+
1724
+      // mark every codec for relay in m2
1725
+      TRACE("m2: marking payload %d for relay\n", p->payload_type);
1726
+      m2.set(p->payload_type);
1727
+
1728
+      if (!containsPayload(norelay_payloads, *p, m.transport)) {
1729
+        // this payload can be relayed
1730
+
1731
+        TRACE("m1: marking payload %d for relay\n", p->payload_type);
1732
+        m1.set(p->payload_type);
1733
+
1734
+        if (!use_m1 && containsPayload(transcoder_settings.audio_codecs, *p, m.transport)) {
1735
+          // the remote SDP contains transcodable codec which can be relayed (i.e.
1736
+          // the one with higher "priority" so we want to disable relaying of the
1737
+          // payloads which should not be ralyed if possible)
1738
+          use_m1 = true;
1739
+        }
1740
+      }
1741
+    }
1742
+
1743
+    TRACE("using %s\n", use_m1 ? "m1" : "m2");
1744
+    if (use_m1) mask = m1;
1745
+    else mask = m2;
1746
+  }
1747
+  else {
1748
+    // for non-transcoding modes use default
1749
+    CallLeg::computeRelayMask(m, enable, mask);
1750
+  }
1751
+}
... ...
@@ -154,8 +154,8 @@ class SBCCallLeg : public CallLeg, public CredentialHolder
154 154
 
155 155
   // media interface must be accessible from CC modules
156 156
   AmB2BMedia *getMediaSession() { return AmB2BSession::getMediaSession(); }
157
-  virtual bool updateLocalSdp(AmSdp &sdp);
158
-  virtual bool updateRemoteSdp(AmSdp &sdp);
157
+  virtual void updateLocalSdp(AmSdp &sdp);
158
+  virtual void updateRemoteSdp(AmSdp &sdp);
159 159
   void changeRtpMode(RTPRelayMode new_mode) { CallLeg::changeRtpMode(new_mode); }
160 160
 
161 161
   bool reinvite(const AmSdp &sdp, unsigned &request_cseq);
... ...
@@ -164,13 +164,11 @@ class SBCCallLeg : public CallLeg, public CredentialHolder
164 164
   void onSipRequest(const AmSipRequest& req);
165 165
   bool isALeg() { return a_leg; }
166 166
 
167
-  virtual void putOnHold();
168
-  virtual void resumeHeld(bool send_reinvite);
169
-