Browse code

Merge remote-tracking branch 'frafos/1.6-dev' into 1.6-dev-ccdsm

* frafos/1.6-dev: (27 commits)
b2b calls quick hack: make RelayController accessible from derrived classes
sbc: keep original CSeq to support authentication case with Asterisk
sbc: allow to preserve media session when disconnecting
core: disable blacklist for in-dialog requests
sip: disable blacklist lookup if TR_FLAG_DISABLE_BL flag is set
sip: do not insert into blacklist if duration == 0
sbc: added active registration counter
b2b media b/f: do not try to set relay with empty remote address
AmRtpStream b/f: do not set mute together with hold
core: CPS limiter Calls-per-sec limiting capability, similar to the maximum number of calls limiter. There is a "soft limit" that is intended to use by the applications: if the app uses async processing and detects that the incoming workload is too much for the currenti capacity (which can depend on for example the remote DB engine's load), it can slewi it back to some percent of the current CPS; then when the circumstances are restored it can switch the CPS limit back to the configuration (or XMLRPC/stats interface) mandated value.
sbc: use non-hold SDP if possible when adding new callee
sbc: new event for asynchronous held call resume
b2b media cleanup: remove unused methods
sbc b/f: create hold request cleaner way
sbc b/f: signalize hold/resume request before creating the hold/resume request itself
webconference: add lonely_user_timer option
sbc b/f: apply remote SDP again if RTP mode changed during OA exchange
b2b media b/f: do not clear audio when removed from media processor
core: b/f: make get_header_keyvalue_single() case-insensitive as specified in RFC3261 7.3.1
sbc: improved hold handling
...

Conflicts:
apps/sbc/CallLeg.cpp
core/AmB2BMedia.h

Stefan Sayer authored on 13/11/2013 16:02:45
Showing 38 changed files
... ...
@@ -328,10 +328,12 @@ EXEC_ACTION_START(MODSBCActionStopCall) {
328 328
   call_leg->stopCall(cause.c_str());
329 329
 } EXEC_ACTION_END;
330 330
 
331
+CONST_ACTION_2P(MODSBCActionDisconnect, ',', true);
331 332
 EXEC_ACTION_START(MODSBCActionDisconnect) {
332 333
   GET_CALL_LEG(Disconnect);
333
-  string hold_remote = resolveVars(arg, sess, sc_sess, event_params);
334
-  call_leg->disconnect(hold_remote == DSM_TRUE);
334
+  string hold_remote = resolveVars(par1, sess, sc_sess, event_params);
335
+  string preserve_media_session = resolveVars(par2, sess, sc_sess, event_params);
336
+  call_leg->disconnect(hold_remote == DSM_TRUE, preserve_media_session == DSM_TRUE);
335 337
 } EXEC_ACTION_END;
336 338
 
337 339
 EXEC_ACTION_START(MODSBCActionSendDisconnectEvent) {
... ...
@@ -351,8 +353,7 @@ EXEC_ACTION_START(MODSBCActionPutOnHold) {
351 353
 
352 354
 EXEC_ACTION_START(MODSBCActionResumeHeld) {
353 355
   GET_CALL_LEG(ResumeHeld);
354
-  string send_reinvite = resolveVars(arg, sess, sc_sess, event_params);
355
-  call_leg->resumeHeld(send_reinvite == DSM_TRUE);
356
+  call_leg->resumeHeld();
356 357
 } EXEC_ACTION_END;
357 358
 
358 359
 EXEC_ACTION_START(MODSBCActionGetCallStatus) {
... ...
@@ -442,20 +443,7 @@ EXEC_ACTION_START(MODSBCActionAddCallee) {
442 443
     if (it != sc_sess->var.end())
443 444
       p.outbound_proxy = it->second;
444 445
 
445
-    it = sc_sess->var.find(varname+"." DSM_SBC_PARAM_ADDCALLEE_RTP_MODE);
446
-    if (it != sc_sess->var.end()) {
447
-      if (it->second == "RTP_Direct") {
448
-	rtp_mode = AmB2BSession::RTP_Direct;
449
-      } else if (it->second == "RTP_Relay") {
450
-	rtp_mode = AmB2BSession::RTP_Relay;
451
-      } else if (it->second == "RTP_Transcoding") {
452
-	rtp_mode = AmB2BSession::RTP_Transcoding;
453
-      } else {
454
-	WARN("Unknown rtp_mode '%s', using this leg's mode\n", it->second.c_str());
455
-      }
456
-    }
457
-
458
-    sbc_call_leg->addCallee(peer, hdrs, rtp_mode);
446
+    sbc_call_leg->addCallee(peer, hdrs);
459 447
   } else if (mode == DSM_SBC_PARAM_ADDCALLEE_MODE_LTAG) {
460 448
     string ltag;
461 449
     string hdrs;
... ...
@@ -37,7 +37,7 @@ DECLARE_MODULE_END;
37 37
 
38 38
 DEF_ACTION_2P(MODSBCActionProfileSet);
39 39
 DEF_ACTION_1P(MODSBCActionStopCall);
40
-DEF_ACTION_1P(MODSBCActionDisconnect);
40
+DEF_ACTION_2P(MODSBCActionDisconnect);
41 41
 DEF_ACTION_1P(MODSBCActionSendDisconnectEvent);
42 42
 
43 43
 DEF_ACTION_1P(MODSBCActionPutOnHold);
... ...
@@ -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,24 +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
-  AmMimeBody r_body(co_ev->body);
466
-  const AmMimeBody* body = &co_ev->body;
467
-  if (rtp_relay_mode != RTP_Direct) {
468
-    try {
469
-      body = co_ev->body.hasContentType(SIP_APPLICATION_SDP);
470
-      if (body && updateLocalBody(*body, *r_body.hasContentType(SIP_APPLICATION_SDP))) {
471
-        body = &r_body;
472
-      }
473
-      else {
474
-        body = &co_ev->body;
475
-      }
476
-    } catch (const string& s) {
477
-      relayError(SIP_METH_INVITE, co_ev->r_cseq, true, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
478
-      throw;
479
-    }
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;
480 582
   }
481 583
 
482
-  int res = dlg->sendRequest(SIP_METH_INVITE, body,
584
+  int res = dlg->sendRequest(SIP_METH_INVITE, &body,
483 585
       co_ev->hdrs, SIP_FLAGS_VERBATIM);
484 586
   if (res < 0) {
485 587
     DBG("sending INVITE failed, relaying back error reply\n");
... ...
@@ -521,7 +623,6 @@ void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
521 623
 
522 624
   // release old signaling and media session
523 625
   clear_other();
524
-  resumeHeld(false);
525 626
   clearRtpReceiverRelay();
526 627
 
527 628
   // check if we aren't processing INVITE now (BLF ringing call pickup)
... ...
@@ -615,7 +716,7 @@ void CallLeg::onB2BReplaceInProgress(ReplaceInProgressEvent *e)
615 716
   }
616 717
 }
617 718
 
618
-void CallLeg::disconnect(bool hold_remote)
719
+void CallLeg::disconnect(bool hold_remote, bool preserve_media_session)
619 720
 {
620 721
   TRACE("disconnecting call leg %s from the other\n", getLocalTag().c_str());
621 722
 
... ...
@@ -629,14 +730,20 @@ void CallLeg::disconnect(bool hold_remote)
629 730
     case Ringing:
630 731
       WARN("trying to disconnect in not connected state, terminating not connected legs in advance (was it intended?)\n");
631 732
       terminateNotConnectedLegs();
632
-      break;
733
+      // do not break, continue with following state handling!
633 734
 
634 735
     case Connected:
635
-      resumeHeld(false); // TODO: do this as part of clearRtpReceiverRelay
636
-      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
+      }
637 740
       break; // this is OK
638 741
   }
639 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
+
640 747
   clear_other();
641 748
   set_sip_relay_only(false); // we can't relay once disconnected
642 749
 
... ...
@@ -647,117 +754,88 @@ void CallLeg::disconnect(bool hold_remote)
647 754
   }
648 755
 }
649 756
 
650
-void CallLeg::createHoldRequest(AmSdp &sdp)
757
+static void sdp2body(const AmSdp &sdp, AmMimeBody &body)
651 758
 {
652
-  AmB2BMedia *ms = getMediaSession();
653
-  if (ms) {
654
-    ms->mute(a_leg);
655
-    ms->createHoldRequest(sdp, a_leg, false /*mark_zero_connection*/, true /*mark_sendonly*/);
656
-  }
657
-  else {
658
-    sdp.clear();
659
-
660
-    // FIXME: versioning
661
-    sdp.version = 0;
662
-    sdp.origin.user = "sems";
663
-    //offer.origin.sessId = 1;
664
-    //offer.origin.sessV = 1;
665
-    sdp.sessionName = "sems";
666
-    sdp.conn.network = NT_IN;
667
-    sdp.conn.addrType = AT_V4;
668
-    sdp.conn.address = "0.0.0.0";
669
-
670
-    // FIXME: use media line from stored body?
671
-    sdp.media.push_back(SdpMedia());
672
-    SdpMedia &m = sdp.media.back();
673
-    m.type = MT_AUDIO;
674
-    m.transport = TP_RTPAVP;
675
-    m.send = false;
676
-    m.recv = false;
677
-    m.payloads.push_back(SdpPayload(0));
678
-  }
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());
679 765
 }
680 766
 
681 767
 void CallLeg::putOnHold()
682 768
 {
683
-  hold_status = HoldRequested;
684
-
685
-  if (isOnHold()) {
686
-    handleHoldReply(true); // really?
687
-    return;
688
-  }
769
+  if (on_hold) return;
689 770
 
690 771
   TRACE("putting remote on hold\n");
772
+  oa.hold = OA::HoldRequested;
773
+
774
+  holdRequested();
691 775
 
692 776
   AmSdp sdp;
693 777
   createHoldRequest(sdp);
694 778
   updateLocalSdp(sdp);
695 779
 
696
-  // generate re-INVITE with hold request
697
-  //reinvite(sdp, hold_request_cseq);
698 780
   AmMimeBody body;
699
-  string body_str;
700
-  sdp.print(body_str);
701
-  body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
781
+  sdp2body(sdp, body);
702 782
   if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
703 783
     ERROR("re-INVITE failed\n");
704
-    handleHoldReply(false);
784
+    offerRejected();
705 785
   }
706
-  else hold_request_cseq = dlg->cseq - 1;
786
+  //else hold_request_cseq = dlg->cseq - 1;
707 787
 }
708 788
 
709
-void CallLeg::resumeHeld(bool send_reinvite)
789
+void CallLeg::resumeHeld(/*bool send_reinvite*/)
710 790
 {
711
-  if (!isOnHold()) {
712
-    handleHoldReply(true); // really?
713
-    return;
714
-  }
715
-
716
-  hold_status = ResumeRequested;
791
+  if (!on_hold) return;
717 792
 
718
-  TRACE("resume held remote\n");
793
+  try {
794
+    TRACE("resume held remote\n");
795
+    oa.hold = OA::ResumeRequested;
719 796
 
720
-  if (!send_reinvite) {
721
-    // probably another SDP in progress
722
-    handleHoldReply(true);
723
-    return;
724
-  }
797
+    resumeRequested();
725 798
 
726
-  AmSdp sdp;
727
-  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
+    }
728 806
     updateLocalSdp(sdp);
729 807
 
730
-  if (dlg->reinvite("", &established_body, SIP_FLAGS_VERBATIM) != 0) {
731
-    ERROR("re-INVITE failed\n");
732
-    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();
733 818
   }
734
-  else hold_request_cseq = dlg->cseq - 1;
735 819
 }
736 820
 
737
-void CallLeg::handleHoldReply(bool succeeded)
821
+void CallLeg::holdAccepted()
738 822
 {
739
-  switch (hold_status) {
740
-    case HoldRequested:
741
-      // ignore the result (if hold request is not accepted that's a pitty but
742
-      // we are Disconnected anyway)
743
-      if (call_status == Disconnecting) updateCallStatus(Disconnected);
744
-
745
-      if (succeeded) hold_status = OnHold; // remote put on hold successfully
746
-      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
+}
747 828
 
748
-    case ResumeRequested:
749
-      if (succeeded) {
750
-        hold_status = NotHeld; // call resumed successfully
751
-        AmB2BMedia *ms = getMediaSession();
752
-        if (ms) ms->unmute(a_leg);
753
-      }
754
-      break;
829
+void CallLeg::holdRejected()
830
+{
831
+  if (call_status == Disconnecting) updateCallStatus(Disconnected);
832
+}
755 833
 
756
-    case NotHeld:
757
-    case OnHold:
758
-      //DBG("handling hold reply but hold was not requested\n");
759
-      break;
760
-  }
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
761 839
 }
762 840
 
763 841
 // was for caller only
... ...
@@ -777,13 +855,11 @@ void CallLeg::onInvite(const AmSipRequest& req)
777 855
     recvd_req.insert(std::make_pair(req.cseq, req));
778 856
 
779 857
     initial_sdp_stored = false;
780
-    if (rtp_relay_mode != RTP_Direct) {
781
-      const AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP);
782
-      DBG("SDP %sfound in initial INVITE\n", sdp_body ? "": "not ");
783
-      if (sdp_body && (initial_sdp.parse((const char *)sdp_body->getPayload()) == 0)) {
784
-        DBG("storing remote SDP for later\n");
785
-        initial_sdp_stored = true;
786
-      }
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;
787 863
     }
788 864
   }
789 865
 }
... ...
@@ -837,25 +913,23 @@ void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSip
837 913
       (relayed_request ? "to relayed request" : "to locally generated request"),
838 914
       callStatus2str(call_status));
839 915
 
840
-  if ((reply.cseq == hold_request_cseq) && (reply.cseq_method == SIP_METH_INVITE)) {
841
-    // hold request replied - handle it
842
-    if (reply.code >= 200) { // handle final replies only
843
-      hold_request_cseq = 0;
844
-      handleHoldReply(reply.code < 300);
845
-    }
846
-
847
-    // we don't want to relay this reply to the other leg! => do the necessary
848
-    // stuff here (copy & paste from AmB2BSession::onSipReply)
849
-    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) {
850 922
       const AmMimeBody *sdp_part = reply.body.hasContentType(SIP_APPLICATION_SDP);
851 923
       if (sdp_part) {
852 924
         AmSdp sdp;
853 925
         if (sdp.parse((const char *)sdp_part->getPayload()) == 0) updateRemoteSdp(sdp);
854 926
       }
855 927
     }
928
+    else if (reply.code >= 300) offerRejected();
856 929
     AmSession::onSipReply(req, reply, old_dlg_status);
857 930
     return;
858 931
   }
932
+#endif
859 933
 
860 934
   AmB2BSession::onSipReply(req, reply, old_dlg_status);
861 935
 
... ...
@@ -1107,6 +1181,22 @@ void CallLeg::addCallee(const string &session_tag, const AmSipRequest &relayed_i
1107 1181
   addExistingCallee(session_tag, new ReconnectLegEvent(getLocalTag(), relayed_invite));
1108 1182
 }
1109 1183
 
1184
+void CallLeg::addCallee(CallLeg *callee, const string &hdrs)
1185
+{
1186
+  if (!non_hold_sdp.media.empty()) {
1187
+    // use non-hold SDP if possible
1188
+    AmMimeBody body(established_body);
1189
+    sdp2body(non_hold_sdp, body);
1190
+    addNewCallee(callee, new ConnectLegEvent(hdrs, body));
1191
+  }
1192
+  else addNewCallee(callee, new ConnectLegEvent(hdrs, established_body));
1193
+}
1194
+
1195
+/*void CallLeg::addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode)
1196
+{
1197
+  addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode);
1198
+}*/
1199
+
1110 1200
 void CallLeg::replaceExistingLeg(const string &session_tag, const AmSipRequest &relayed_invite)
1111 1201
 {
1112 1202
   // add existing session as our B leg
... ...
@@ -1218,6 +1308,24 @@ void CallLeg::changeRtpMode(RTPRelayMode new_mode)
1218 1308
       }
1219 1309
       break;
1220 1310
   }
1311
+
1312
+  switch (oa.status) {
1313
+    case OA::None:
1314
+      // must be followed by OA exchange because we can't updateLocalSdp
1315
+      // (reINVITE would be needed)
1316
+      break;
1317
+
1318
+    case OA::OfferSent:
1319
+      TRACE("changing RTP mode after offer was sent: reINVITE needed\n");
1320
+      // TODO: plan a reINVITE
1321
+      ERROR("not implemented\n");
1322
+      break;
1323
+
1324
+    case OA::OfferReceived:
1325
+      TRACE("changing RTP mode after offer was received, needed to updateRemoteSdp again\n");
1326
+      AmB2BSession::updateRemoteSdp(oa.remote_sdp); // hack
1327
+      break;
1328
+  }
1221 1329
 }
1222 1330
 
1223 1331
 void CallLeg::changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media)
... ...
@@ -1254,6 +1362,25 @@ void CallLeg::changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media)
1254 1362
 
1255 1363
   AmB2BMedia *m = getMediaSession();
1256 1364
   if (m) m->changeSession(a_leg, this);
1365
+
1366
+  switch (oa.status) {
1367
+    case OA::None:
1368
+      // must be followed by OA exchange because we can't updateLocalSdp
1369
+      // (reINVITE would be needed)
1370
+      break;
1371
+
1372
+    case OA::OfferSent:
1373
+      TRACE("changing RTP mode/media session after offer was sent: reINVITE needed\n");
1374
+      // TODO: plan a reINVITE
1375
+      ERROR("%s: not implemented\n", getLocalTag().c_str());
1376
+      break;
1377
+
1378
+    case OA::OfferReceived:
1379
+      TRACE("changing RTP mode/media session after offer was received, needed to updateRemoteSdp again\n");
1380
+      AmB2BSession::updateRemoteSdp(oa.remote_sdp); // hack
1381
+      break;
1382
+  }
1383
+
1257 1384
 }
1258 1385
 
1259 1386
 void CallLeg::changeOtherLegsRtpMode(RTPRelayMode new_mode)
... ...
@@ -1314,11 +1441,9 @@ void CallLeg::acceptPendingInvite(AmSipRequest *invite)
1314 1441
   string body_str;
1315 1442
   s.print(body_str);
1316 1443
   body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
1317
-  if (rtp_relay_mode != RTP_Direct) {
1318
-    try {
1319
-      if (!updateLocalBody(body, body)) ERROR("failed to update local body with fake SDP\n");
1320
-    } catch (...) { /* throw ? */  }
1321
-  }
1444
+  try {
1445
+    updateLocalBody(body);
1446
+  } catch (...) { /* throw ? */  }
1322 1447
 
1323 1448
   TRACE("replying pending INVITE with body: %s\n", body_str.c_str());
1324 1449
   dlg->reply(*invite, 200, "OK", &body);
... ...
@@ -1329,18 +1454,11 @@ void CallLeg::acceptPendingInvite(AmSipRequest *invite)
1329 1454
 void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing)
1330 1455
 {
1331 1456
   int res;
1332
-  if (rtp_relay_mode != RTP_Direct && body.hasContentType(SIP_APPLICATION_SDP)) {
1333
-    try {
1334
-      AmMimeBody r_body(body);
1335
-      AmMimeBody *sdp = r_body.hasContentType(SIP_APPLICATION_SDP);
1336
-      if (!updateLocalBody(*sdp, *sdp)) {
1337
-        ERROR("can't update local body\n");
1338
-        res = -500;
1339
-      }
1340
-      else res = dlg->sendRequest(SIP_METH_INVITE, &r_body, hdrs, SIP_FLAGS_VERBATIM);
1341
-    } catch (const string& s) { res = -500; }
1342
-  }
1343
-  else res = dlg->sendRequest(SIP_METH_INVITE, &body, hdrs, SIP_FLAGS_VERBATIM);
1457
+  try {
1458
+    AmMimeBody r_body(body);
1459
+    updateLocalBody(r_body);
1460
+    res = dlg->sendRequest(SIP_METH_INVITE, &r_body, hdrs, SIP_FLAGS_VERBATIM);
1461
+  } catch (const string& s) { res = -500; }
1344 1462
 
1345 1463
   if (res < 0) {
1346 1464
     if (relayed) {
... ...
@@ -1370,3 +1488,126 @@ void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed,
1370 1488
   }
1371 1489
 }
1372 1490
 
1491
+void CallLeg::adjustOffer(AmSdp &sdp)
1492
+{
1493
+  if (oa.hold != OA::PreserveHoldStatus) {
1494
+    // locally generated hold/unhold requests that already contain correct
1495
+    // hold/resume bodies and need not to be altered via createHoldRequest
1496
+    // hold/resumeRequested is already called
1497
+  }
1498
+  else {
1499
+    // handling B2B SDP, check for hold/unhold
1500
+
1501
+    HoldMethod hm;
1502
+    // if hold request, transform to requested kind of hold and remember that hold
1503
+    // was requested with this offer
1504
+    if (isHoldRequest(sdp, hm)) {
1505
+      holdRequested();
1506
+      alterHoldRequest(sdp);
1507
+      oa.hold = OA::HoldRequested;
1508
+    }
1509
+    else {
1510
+      if (on_hold) {
1511
+        resumeRequested();
1512
+        alterResumeRequest(sdp);
1513
+        oa.hold = OA::ResumeRequested;
1514
+      }
1515
+    }
1516
+  }
1517
+}
1518
+
1519
+void CallLeg::updateLocalSdp(AmSdp &sdp)
1520
+{
1521
+  TRACE("%s: updateLocalSdp\n", getLocalTag().c_str());
1522
+  // handle the body based on current offer-answer status
1523
+  // (possibly update the body before sending to remote)
1524
+
1525
+  switch (oa.status) {
1526
+    case OA::None:
1527
+      adjustOffer(sdp);
1528
+      oa.status = OA::OfferSent;
1529
+      //FIXME: oa.offer_cseq = dlg->cseq;
1530
+      break;
1531
+
1532
+    case OA::OfferSent:
1533
+      ERROR("BUG: another SDP offer to be sent before answer/reject");
1534
+      oa.clear(); // or call offerRejected?
1535
+      break;
1536
+
1537
+    case OA::OfferReceived:
1538
+      // sending the answer
1539
+      oaCompleted();
1540
+      break;
1541
+  }
1542
+
1543
+  if (oa.hold == OA::PreserveHoldStatus && !on_hold) {
1544
+    // store non-hold SDP to be able to resumeHeld
1545
+    non_hold_sdp = sdp;
1546
+  }
1547
+
1548
+  AmB2BSession::updateLocalSdp(sdp);
1549
+}
1550
+
1551
+void CallLeg::updateRemoteSdp(AmSdp &sdp)
1552
+{
1553
+  TRACE("%s: updateRemoteSdp\n", getLocalTag().c_str());
1554
+  switch (oa.status) {
1555
+    case OA::None:
1556
+      oa.status = OA::OfferReceived;
1557
+      oa.remote_sdp = sdp;
1558
+      break;
1559
+
1560
+    case OA::OfferSent:
1561
+      oaCompleted();
1562
+      break;
1563
+
1564
+    case OA::OfferReceived:
1565
+      ERROR("BUG: another SDP offer received before answer/reject");
1566
+      oa.clear(); // or call offerRejected?
1567
+      break;
1568
+  }
1569
+
1570
+  AmB2BSession::updateRemoteSdp(sdp);
1571
+}
1572
+
1573
+void CallLeg::oaCompleted()
1574
+{
1575
+  TRACE("%s: oaCompleted\n", getLocalTag().c_str());
1576
+  switch (oa.hold) {
1577
+    case OA::HoldRequested: holdAccepted(); break;
1578
+    case OA::ResumeRequested: resumeAccepted(); break;
1579
+    case OA::PreserveHoldStatus: break;
1580
+  }
1581
+
1582
+  // call a callback here?
1583
+  oa.clear();
1584
+}
1585
+
1586
+void CallLeg::offerRejected()
1587
+{
1588
+  switch (oa.hold) {
1589
+    case OA::HoldRequested: holdRejected(); break;
1590
+    case OA::ResumeRequested: resumeRejected(); break;
1591
+    case OA::PreserveHoldStatus: break;
1592
+  }
1593
+
1594
+  oa.clear();
1595
+}
1596
+
1597
+void CallLeg::createResumeRequest(AmSdp &sdp)
1598
+{
1599
+  // use stored non-hold SDP
1600
+  // Note: this SDP doesn't need to be correct, but established_body need not to
1601
+  // be good enough for unholding (might be held already with zero conncetions)
1602
+  if (!non_hold_sdp.media.empty()) sdp = non_hold_sdp;
1603
+  else {
1604
+    // no stored non-hold SDP
1605
+    ERROR("no stored non-hold SDP, but local resume requested\n");
1606
+    // TODO: try to use established_body here and mark properly
1607
+
1608
+    // if no established body exist
1609
+    throw string("not implemented");
1610
+  }
1611
+  // do not touch the sdp otherwise (use directly B2B SDP)
1612
+}
1613
+
... ...
@@ -125,11 +125,23 @@ class CallLeg: public AmB2BSession
125 125
      * A leg, i.e. it creates new B leg(s) for itself. */
126 126
     std::vector<OtherLegInfo> other_legs;
127 127
 
128
-    unsigned hold_request_cseq; // CSeq of a request generated by us to hold the call
129
-    enum { NotHeld, OnHold, HoldRequested, ResumeRequested } hold_status; // remote is put on hold by us
128
+    bool on_hold; // remote is on hold
129
+    AmSdp non_hold_sdp;
130 130
 
131 131
     std::queue<PendingReinvite> pending_reinvites;
132 132
 
133
+    // offer-answer related stuff
134
+    struct OA {
135
+      enum { None, OfferSent, OfferReceived } status;
136
+      enum { HoldRequested, ResumeRequested, PreserveHoldStatus } hold;
137
+      OA(): status(None), hold(PreserveHoldStatus) { }
138
+      void clear() { status = None; hold = PreserveHoldStatus; remote_sdp.clear(); }
139
+
140
+      /* SDP of remote end. Needed because of the possibility of RTP relay mode
141
+       * change during offer-answer exchange. */
142
+      AmSdp remote_sdp;
143
+    } oa;
144
+
133 145
     // generate re-INVITE with given parameters (establishing means that the
134 146
     // INVITE is establishing a connection between two legs)
135 147
     void reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing);
... ...
@@ -202,6 +214,15 @@ class CallLeg: public AmB2BSession
202 214
     void changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media);
203 215
     void changeOtherLegsRtpMode(RTPRelayMode new_mode);
204 216
 
217
+    // offer-answer handling
218
+    void adjustOffer(AmSdp &sdp);
219
+    void oaCompleted(); // offer-answer exchange completed, both SDPs are handled
220
+
221
+     /* offer was rejected (called just for negative replies to an request
222
+      * carying offer (not always correct?), answer with disabled streams
223
+      * doesn't cause calling this */
224
+     void offerRejected();
225
+
205 226
   protected:
206 227
 
207 228
     // functions offered to successors
... ...
@@ -229,11 +250,28 @@ class CallLeg: public AmB2BSession
229 250
     virtual void onInitialReply(B2BSipReplyEvent *e);
230 251
 
231 252
     /* callback method called when hold/resume request is replied */
232
-    virtual void handleHoldReply(bool succeeded);
253
+//    virtual void handleHoldReply(bool succeeded);
254
+
255
+    /* called to create SDP of locally generated hold request */
256
+    virtual void createHoldRequest(AmSdp &sdp) = 0;
233 257
 
234
-    /* called to create SDP of hold request
235
-     * (note that resume request always uses established_body stored before) */
236
-    virtual void createHoldRequest(AmSdp &sdp);
258
+    /** called to alter B2B hold request (i.e. the request from other peer) */
259
+    virtual void alterHoldRequest(AmSdp &sdp) { }
260
+
261
+    /* called to create SDP of locally generated resume request */
262
+    virtual void createResumeRequest(AmSdp &sdp);
263
+
264
+    /** called to alter B2B hold request (i.e. the request from other peer) */
265
+    virtual void alterResumeRequest(AmSdp &sdp) { }
266
+
267
+    /* hold requested (either from B2B peer leg or locally)
268
+     * to be overridden */
269
+    virtual void holdRequested() { }
270
+    virtual void holdAccepted();
271
+    virtual void holdRejected();
272
+    virtual void resumeRequested() { }
273
+    virtual void resumeAccepted();
274
+    virtual void resumeRejected() { }
237 275
 
238 276
     virtual void terminateOtherLeg();
239 277
     virtual void terminateLeg();
... ...
@@ -251,6 +289,9 @@ class CallLeg: public AmB2BSession
251 289
      * TODO: add parameter to trigger reINVITE */
252 290
     void changeRtpMode(RTPRelayMode new_mode);
253 291
 
292
+    virtual void updateLocalSdp(AmSdp &sdp);
293
+    virtual void updateRemoteSdp(AmSdp &sdp);
294
+
254 295
   public:
255 296
     virtual void onB2BEvent(B2BEvent* ev);
256 297
 
... ...
@@ -260,7 +301,7 @@ class CallLeg: public AmB2BSession
260 301
      *
261 302
      * The other leg is not affected by disconnect - it is neither terminated
262 303
      * nor informed about the peer disconnection. */
263
-    virtual void disconnect(bool hold_remote);
304
+    virtual void disconnect(bool hold_remote, bool preserve_media_session = false);
264 305
 
265 306
     /** Terminate the whole B2B call (if there is no other leg only this one is
266 307
      * stopped). */
... ...
@@ -275,9 +316,9 @@ class CallLeg: public AmB2BSession
275 316
     virtual void putOnHold();
276 317
 
277 318
     /** resume call if the remote party is on hold */
278
-    virtual void resumeHeld(bool send_reinvite);
319
+    virtual void resumeHeld(/*bool send_reinvite*/);
279 320
 
280
-    virtual bool isOnHold() { return hold_status == OnHold; }
321
+    virtual bool isOnHold() { return on_hold; }
281 322
 
282 323
 
283 324
     /** add given call leg as our B leg */
... ...
@@ -293,8 +334,8 @@ class CallLeg: public AmB2BSession
293 334
      * Can be used in A leg and B leg as well.
294 335
      * Additional headers may be specified - these are used in outgoing INVITE
295 336
      * to the callee's destination. */
296
-    void addCallee(CallLeg *callee, const string &hdrs) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body)); }
297
-    void addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode); }
337
+    void addCallee(CallLeg *callee, const string &hdrs);
338
+//    void addCallee(CallLeg *callee, const string &hdrs, AmB2BSession::RTPRelayMode mode) { addNewCallee(callee, new ConnectLegEvent(hdrs, established_body), mode); }
298 339
 
299 340
 
300 341
     /** 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
 {
... ...
@@ -140,7 +141,11 @@ struct ReplaceInProgressEvent: public B2BEvent
140 141
 struct DisconnectLegEvent: public B2BEvent
141 142
 {
142 143
   bool put_remote_on_hold;
143
-  DisconnectLegEvent(bool _put_remote_on_hold): B2BEvent(DisconnectLeg), put_remote_on_hold(_put_remote_on_hold) { }
144
+  bool preserve_media_session;
145
+  DisconnectLegEvent(bool _put_remote_on_hold, bool _preserve_media_session = false):
146
+    B2BEvent(DisconnectLeg),
147
+    put_remote_on_hold(_put_remote_on_hold),
148
+    preserve_media_session(_preserve_media_session) { }
144 149
 };
145 150
 
146 151
 /* we don't need to have 'reliable event' for this because we are always
... ...
@@ -157,5 +162,9 @@ struct ChangeRtpModeEvent: public B2BEvent
157 162
     virtual ~ChangeRtpModeEvent() { if (media) media->releaseReference(); }
158 163
 };
159 164
 
165
+struct ResumeHeldEvent: public B2BEvent
166
+{
167
+  ResumeHeldEvent(): B2BEvent(ResumeHeld) { }
168
+};
160 169
 
161 170
 #endif
... ...
@@ -69,10 +69,12 @@ class ExtendedCCInterface
69 69
     // hold related functionality (seems to work best being explicitly supported
70 70
     // with API than hacking based on another callbacks)
71 71
 
72
-    virtual CCChainProcessing putOnHold(SBCCallLeg *call) { return ContinueProcessing; }
73
-    virtual CCChainProcessing resumeHeld(SBCCallLeg *call, bool send_reinvite) { return ContinueProcessing; }
74
-    virtual CCChainProcessing createHoldRequest(SBCCallLeg *call, AmSdp &sdp) { return ContinueProcessing; }
75
-    virtual CCChainProcessing handleHoldReply(SBCCallLeg *call, bool succeeded) { return ContinueProcessing; }
72
+    virtual void holdRequested(SBCCallLeg *call) { }
73
+    virtual void holdAccepted(SBCCallLeg *call) { }
74
+    virtual void holdRejected(SBCCallLeg *call) { }
75
+    virtual void resumeRequested(SBCCallLeg *call) { }
76
+    virtual void resumeAccepted(SBCCallLeg *call) { }
77
+    virtual void resumeRejected(SBCCallLeg *call) { }
76 78
 
77 79
     /** Possibility to influence messages relayed to the B2B peer leg.
78 80
       return value:
... ...
@@ -409,7 +409,7 @@ void _RegisterCache::update(const string& alias, long int reg_expires,
409 409
       binding = binding_it->second;
410 410
     }
411 411
   }
412
-  
412
+
413 413
   if(!binding) {
414 414
     // insert one if none exist
415 415
     binding = new RegBinding();
... ...
@@ -418,6 +418,9 @@ void _RegisterCache::update(const string& alias, long int reg_expires,
418 418
     DBG("inserted new binding: '%s' -> '%s'",
419 419
 	uri.c_str(), alias.c_str());
420 420
 
421
+    // inc stats
422
+    active_regs.inc();
423
+
421 424
     ContactBucket* ct_bucket = getContactBucket(uri,alias_update.source_ip,
422 425
 						alias_update.source_port);
423 426
     ct_bucket->lock();
... ...
@@ -537,6 +540,9 @@ void _RegisterCache::update(long int reg_expires, const AliasEntry& alias_update
537 540
     binding->alias = _RegisterCache::
538 541
       compute_alias_hash(canon_aor,uri,public_ip);
539 542
 
543
+    // inc stats
544
+    active_regs.inc();
545
+
540 546
     string idx = uri + "/" + public_ip;
541 547
     aor_e->insert(AorEntry::value_type(idx, binding));
542 548
     DBG("inserted new binding: '%s' -> '%s'",
... ...
@@ -715,6 +721,9 @@ void _RegisterCache::removeAlias(const string& alias, bool generate_event)
715 721
     ct_bucket->remove(ae->contact_uri,ae->source_ip,ae->source_port);
716 722
     ct_bucket->unlock();
717 723
 
724
+    // dec stats
725
+    active_regs.dec();
726
+
718 727
     storage_handler->onDelete(ae->aor,
719 728
 			      ae->contact_uri,
720 729
 			      ae->alias);
... ...
@@ -3,6 +3,7 @@
3 3
 
4 4
 #include "singleton.h"
5 5
 #include "hash_table.h"
6
+#include "atomic_types.h"
6 7
 
7 8
 #include "AmSipMsg.h"
8 9
 #include "AmUriParser.h"
... ...
@@ -201,6 +202,9 @@ class _RegisterCache
201 202
 
202 203
   AmSharedVar<bool> running;
203 204
 
205
+  // stats
206
+  atomic_int active_regs;
207
+
204 208
   void gbc(unsigned int bucket_id);
205 209
   void removeAlias(const string& alias, bool generate_event);
206 210
 
... ...
@@ -346,6 +350,11 @@ public:
346 350
   bool saveSingleContact(RegisterCacheCtx& ctx,
347 351
 			const AmSipRequest& req,
348 352
                         msg_logger *logger);
353
+
354
+  /**
355
+   * Statistics
356
+   */
357
+  unsigned int getActiveRegs() { return active_regs.get(); }
349 358
 };
350 359
 
351 360
 typedef singleton<_RegisterCache> RegisterCache;
... ...
@@ -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)
... ...
@@ -209,6 +146,7 @@ SBCCallLeg::SBCCallLeg(SBCCallLeg* caller, AmSipDialog* p_dlg,
209 146
   if (call_profile.transparent_dlg_id && caller) {
210 147
     dlg->setCallid(caller->dlg->getCallid());
211 148
     dlg->setExtLocalTag(caller->dlg->getRemoteTag());
149
+    dlg->cseq = caller->dlg->r_cseq;
212 150
   }
213 151
 
214 152
   // copy RTP rate limit from caller leg
... ...
@@ -670,7 +608,7 @@ void SBCCallLeg::onDtmf(int event, int duration)
670 608
   }
671 609
 }
672 610
 
673
-bool SBCCallLeg::updateLocalSdp(AmSdp &sdp)
611
+void SBCCallLeg::updateLocalSdp(AmSdp &sdp)
674 612
 {
675 613
   // anonymize SDP if configured to do so (we need to have our local media IP,
676 614
   // not the media IP of our peer leg there)
... ...
@@ -678,20 +616,12 @@ bool SBCCallLeg::updateLocalSdp(AmSdp &sdp)
678 616
 
679 617
   // remember transcodable payload IDs
680 618
   if (call_profile.transcoder.isActive()) savePayloadIDs(sdp);
681
-  return CallLeg::updateLocalSdp(sdp);
619
+  CallLeg::updateLocalSdp(sdp);
682 620
 }
683 621
 
684
-
685
-bool SBCCallLeg::updateRemoteSdp(AmSdp &sdp)
622
+void SBCCallLeg::updateRemoteSdp(AmSdp &sdp)
686 623
 {
687
-  SBCRelayController rc(&call_profile.transcoder, a_leg);
688
-  if (call_profile.transcoder.isActive()) {
689
-    AmB2BMedia *ms = getMediaSession();
690
-    if (ms) return ms->updateRemoteSdp(a_leg, sdp, &rc);
691
-  }
692
-
693
-  // call original implementation because our special conditions above are not met
694
-  return CallLeg::updateRemoteSdp(sdp);
624
+  CallLeg::updateRemoteSdp(sdp);
695 625
 }
696 626
 
697 627
 void SBCCallLeg::onControlCmd(string& cmd, AmArg& params) {
... ...
@@ -1356,7 +1286,7 @@ void SBCCallLeg::logCallStart(const AmSipReply& reply)
1356 1286
 					  (int)reply.code,reply.reason);
1357 1287
   }
1358 1288
   else {
1359
-    ERROR("could not log call-start/call-attempt (ci='%s';lt='%s')",
1289
+    DBG("could not log call-start/call-attempt (ci='%s';lt='%s')",
1360 1290
 	  getCallID().c_str(),getLocalTag().c_str());
1361 1291
   }
1362 1292
 }
... ...
@@ -1628,36 +1558,123 @@ bool SBCCallLeg::initCCExtModules()
1628 1558
   return true; // success
1629 1559
 }
1630 1560
 
1631
-void SBCCallLeg::putOnHold()
1561
+#define CALL_EXT_CC_MODULES(method) \
1562
+  do { \
1563
+    for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) { \
1564
+      (*i)->method(this); \
1565
+    } \
1566
+  } while (0)
1567
+
1568
+void SBCCallLeg::holdRequested()
1632 1569
 {
1633
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1634
-    if ((*i)->putOnHold(this) == StopProcessing) return;
1635
-  }
1636
-  CallLeg::putOnHold();
1570
+  TRACE("%s: hold requested\n", getLocalTag().c_str());
1571
+  CALL_EXT_CC_MODULES(holdRequested);
1572
+  CallLeg::holdRequested();
1637 1573
 }
1638 1574
 
1639
-void SBCCallLeg::resumeHeld(bool send_reinvite)
1575
+void SBCCallLeg::holdAccepted()
1640 1576
 {
1641
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1642
-    if ((*i)->resumeHeld(this, send_reinvite) == StopProcessing) return;
1577
+  TRACE("%s: hold accepted\n", getLocalTag().c_str());
1578
+  CALL_EXT_CC_MODULES(holdAccepted);
1579
+  CallLeg::holdAccepted();
1580
+}
1581
+
1582
+void SBCCallLeg::holdRejected()
1583
+{
1584
+  TRACE("%s: hold rejected\n", getLocalTag().c_str());
1585
+  CALL_EXT_CC_MODULES(holdRejected);
1586
+  CallLeg::holdRejected();
1587
+}
1588
+
1589
+void SBCCallLeg::resumeRequested()
1590
+{
1591
+  TRACE("%s: resume requested\n", getLocalTag().c_str());
1592
+  CALL_EXT_CC_MODULES(resumeRequested);
1593
+  CallLeg::resumeRequested();
1594
+}
1595
+
1596
+void SBCCallLeg::resumeAccepted()
1597
+{
1598
+  TRACE("%s: resume accepted\n", getLocalTag().c_str());
1599
+  CALL_EXT_CC_MODULES(resumeAccepted);
1600
+  CallLeg::resumeAccepted();
1601
+}
1602
+
1603
+void SBCCallLeg::resumeRejected()
1604
+{
1605
+  TRACE("%s: resume rejected\n", getLocalTag().c_str());
1606
+  CALL_EXT_CC_MODULES(resumeRejected);
1607
+  CallLeg::resumeRejected();
1608
+}
1609
+
1610
+static void zero_connection(SdpConnection &c)
1611
+{
1612
+  if (!c.address.empty()) {
1613
+    if (c.network == NT_IN) {
1614
+      if (c.addrType == AT_V4) {
1615
+        c.address = "0.0.0.0";
1616
+        return;
1617
+      }
1618
+      // TODO: IPv6?
1619
+    }
1643 1620
   }
1644
-  CallLeg::resumeHeld(send_reinvite);
1621
+
1622
+  DBG("unsupported connection type for marking with 0.0.0.0");
1645 1623
 }
1646 1624
 
1647
-void SBCCallLeg::handleHoldReply(bool succeeded)
1625
+static void alterHoldRequest(AmSdp &sdp, bool mark_zero_con, bool enable_recv)
1648 1626
 {
1649
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1650
-    if ((*i)->handleHoldReply(this, succeeded) == StopProcessing) return;
1627
+  if (mark_zero_con) zero_connection(sdp.conn);
1628
+  for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
1629
+    if (mark_zero_con) zero_connection(m->conn);
1630
+    m->recv = enable_recv;
1651 1631
   }
1652
-  CallLeg::handleHoldReply(succeeded);
1632
+}
1633
+
1634
+void SBCCallLeg::alterHoldRequest(AmSdp &sdp)
1635
+{
1636
+  TRACE("altering B2B hold request\n");
1637
+
1638
+  if (!call_profile.hold_settings.alter_b2b(a_leg)) return;
1639
+
1640
+  ::alterHoldRequest(sdp,
1641
+      call_profile.hold_settings.mark_zero_connection(a_leg),
1642
+      call_profile.hold_settings.recv(a_leg));
1653 1643
 }
1654 1644
 
1655 1645
 void SBCCallLeg::createHoldRequest(AmSdp &sdp)
1656 1646
 {
1657
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1658
-    if ((*i)->createHoldRequest(this, sdp) == StopProcessing) return;
1659
-  }
1660
-  CallLeg::createHoldRequest(sdp);
1647
+  // hack: we need to have other side SDP (if the stream is hold already
1648
+  // it should be marked as inactive)
1649
+  // FIXME: fix SDP versioning! (remember generated versions and increase the
1650
+  // version number in every SDP passing through?)
1651
+
1652
+  AmMimeBody *s = established_body.hasContentType(SIP_APPLICATION_SDP);
1653
+  if (s) sdp.parse((const char*)s->getPayload());
1654
+  if (sdp.media.empty()) {
1655
+    // established SDP is not valid! generate complete fake
1656
+    sdp.version = 0;
1657
+    sdp.origin.user = "sems";
1658
+    sdp.sessionName = "sems";
1659
+    sdp.conn.network = NT_IN;
1660
+    sdp.conn.addrType = AT_V4;
1661
+    sdp.conn.address = "0.0.0.0";
1662
+
1663
+    sdp.media.push_back(SdpMedia());
1664
+    SdpMedia &m = sdp.media.back();
1665
+    m.type = MT_AUDIO;
1666
+    m.transport = TP_RTPAVP;
1667
+    m.send = false;
1668
+    m.recv = false;
1669
+    m.payloads.push_back(SdpPayload(0));
1670
+  }
1671
+
1672
+  ::alterHoldRequest(sdp,
1673
+      call_profile.hold_settings.mark_zero_connection(a_leg),
1674
+      call_profile.hold_settings.recv(a_leg));
1675
+
1676
+  AmB2BMedia *ms = getMediaSession();
1677
+  if (ms) ms->replaceOffer(sdp, a_leg);
1661 1678
 }
1662 1679
 
1663 1680
 void SBCCallLeg::setMediaSession(AmB2BMedia *new_session)
... ...
@@ -1699,3 +1716,60 @@ void SBCCallLeg::setLogger(msg_logger *_logger)
1699 1716
     else m->setRtpLogger(NULL);
1700 1717
   }
1701 1718
 }
1719
+
1720
+void SBCCallLeg::computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask)
1721
+{
1722
+  if (call_profile.transcoder.isActive()) {
1723
+    TRACE("entering transcoder's computeRelayMask(%s)\n", a_leg ? "A leg" : "B leg");
1724
+
1725
+    SBCCallProfile::TranscoderSettings &transcoder_settings = call_profile.transcoder;
1726
+    PayloadMask m1, m2;
1727
+    bool use_m1 = false;
1728
+
1729
+    /* if "m" contains only "norelay" codecs, relay is enabled for them (main idea
1730
+     * of these codecs is to limit network bandwidth and it makes not much sense
1731
+     * to transcode between codecs 'which are better to avoid', right?)
1732
+     *
1733
+     * if "m" contains other codecs, relay is enabled as well
1734
+     *
1735
+     * => if m contains at least some codecs, relay is enabled */
1736
+    enable = !m.payloads.empty();
1737
+
1738
+    vector<SdpPayload> &norelay_payloads =
1739
+      a_leg ? transcoder_settings.audio_codecs_norelay_aleg : transcoder_settings.audio_codecs_norelay;
1740
+
1741
+    vector<SdpPayload>::const_iterator p;
1742
+    for (p = m.payloads.begin(); p != m.payloads.end(); ++p) {
1743
+
1744
+      // do not mark telephone-event payload for relay (and do not use it for
1745
+      // transcoding as well)
1746
+      if(strcasecmp("telephone-event",p->encoding_name.c_str()) == 0) continue;
1747
+
1748
+      // mark every codec for relay in m2
1749
+      TRACE("m2: marking payload %d for relay\n", p->payload_type);
1750
+      m2.set(p->payload_type);
1751
+
1752
+      if (!containsPayload(norelay_payloads, *p, m.transport)) {
1753
+        // this payload can be relayed
1754
+
1755
+        TRACE("m1: marking payload %d for relay\n", p->payload_type);
1756
+        m1.set(p->payload_type);
1757
+
1758
+        if (!use_m1 && containsPayload(transcoder_settings.audio_codecs, *p, m.transport)) {
1759
+          // the remote SDP contains transcodable codec which can be relayed (i.e.
1760
+          // the one with higher "priority" so we want to disable relaying of the
1761
+          // payloads which should not be ralyed if possible)
1762
+          use_m1 = true;
1763
+        }
1764
+      }
1765
+    }
1766
+
1767
+    TRACE("using %s\n", use_m1 ? "m1" : "m2");
1768
+    if (use_m1) mask = m1;
1769
+    else mask = m2;
1770
+  }
1771
+  else {
1772
+    // for non-transcoding modes use default
1773
+    CallLeg::computeRelayMask(m, enable, mask);
1774
+  }
1775
+}
... ...
@@ -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
-
170 167
   // timers accessible from CC modules
171 168
   int startTimer(double timeout) { setTimer(ext_cc_timer_id, timeout); return ext_cc_timer_id++; }
172 169
 
173 170
   virtual void setMediaSession(AmB2BMedia *new_session);
171
+  virtual void computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask);
174 172
 
175 173
  protected:
176 174
   /** set to true once CCStart passed to call CCEnd implicitly (from onStop)
... ...
@@ -209,8 +207,14 @@ class SBCCallLeg : public CallLeg, public CredentialHolder
209 207
   bool getCCInterfaces();
210 208
   vector<AmDynInvoke*>& getCCModules() { return cc_modules; }
211 209
 
212
-  virtual void handleHoldReply(bool succeeded);
213 210
   virtual void createHoldRequest(AmSdp &sdp);
211
+  virtual void alterHoldRequest(AmSdp &sdp);
212
+  virtual void holdRequested();
213
+  virtual void holdAccepted();
214
+  virtual void holdRejected();
215
+  virtual void resumeRequested();
216
+  virtual void resumeAccepted();
217
+  virtual void resumeRejected();
214 218
 
215 219
   int applySSTCfg(AmConfigReader& sst_cfg, const AmSipRequest* p_req);
216 220
 
... ...
@@ -375,6 +375,7 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
375 375
 
376 376
   if (!codec_prefs.readConfig(cfg)) return false;
377 377
   if (!transcoder.readConfig(cfg)) return false;
378
+  hold_settings.readConfig(cfg);
378 379
 
379 380
   msg_logger_path = cfg.getParameter("msg_logger_path");
380 381
   log_rtp = cfg.getParameter("log_rtp","no") == "yes";
... ...
@@ -818,6 +819,7 @@ bool SBCCallProfile::evaluate(ParamReplacerCtx& ctx,
818 819
   REPLACE_IFACE_SIP(outbound_interface, outbound_interface_value);
819 820
 
820 821
   if (!codec_prefs.evaluate(ctx,req)) return false;
822
+  if (!hold_settings.evaluate(ctx,req)) return false;
821 823
 
822 824
   // TODO: activate filter if transcoder or codec_prefs is set?
823 825
 /*  if ((!aleg_payload_order.empty() || !bleg_payload_order.empty()) && (!sdpfilter_enabled)) {
... ...
@@ -1643,3 +1645,30 @@ bool PayloadDesc::operator==(const PayloadDesc &other) const
1643 1645
   if (clock_rate != other.clock_rate) return false;
1644 1646
   return true;
1645 1647
 }
1648
+
1649
+//////////////////////////////////////////////////////////////////////////////////
1650
+
1651
+void SBCCallProfile::HoldSettings::readConfig(AmConfigReader &cfg)
1652
+{
1653
+  // store string values for later evaluation
1654
+  aleg.mark_zero_connection_str = cfg.getParameter("hold_zero_connection_aleg");
1655
+  aleg.recv_str = cfg.getParameter("hold_enable_recv_aleg");
1656
+  aleg.alter_b2b_str = cfg.getParameter("hold_alter_b2b_aleg");
1657
+
1658
+  bleg.mark_zero_connection_str = cfg.getParameter("hold_zero_connection_bleg");
1659
+  bleg.recv_str = cfg.getParameter("hold_enable_recv_bleg");
1660
+  bleg.alter_b2b_str = cfg.getParameter("hold_alter_b2b_bleg");
1661
+}