Browse code

sbc: improved hold handling

Václav Kubart authored on 05/11/2013 08:47:30
Showing 7 changed files
... ...
@@ -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
 
... ...
@@ -337,7 +445,7 @@ void CallLeg::b2bInitial2xx(AmSipReply& reply, bool forward)
337 445
 
338 446
   // FIXME: hack here - it should be part of clearRtpReceiverRelay but we
339 447
   // need to do after RTP mode change above
340
-  resumeHeld(false);
448
+  // FIXME: resumeHeld(false);
341 449
 
342 450
   onCallConnected(reply);
343 451
 
... ...
@@ -512,7 +620,7 @@ void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
512 620
 
513 621
   // release old signaling and media session
514 622
   clear_other();
515
-  resumeHeld(false);
623
+  // FIXME: resumeHeld(false);
516 624
   clearRtpReceiverRelay();
517 625
 
518 626
   // check if we aren't processing INVITE now (BLF ringing call pickup)
... ...
@@ -620,14 +728,19 @@ void CallLeg::disconnect(bool hold_remote)
620 728
     case Ringing:
621 729
       WARN("trying to disconnect in not connected state, terminating not connected legs in advance (was it intended?)\n");
622 730
       terminateNotConnectedLegs();
731
+      clearRtpReceiverRelay(); // we can't stay connected (at media level) with the other leg
623 732
       break;
624 733
 
625 734
     case Connected:
626
-      resumeHeld(false); // TODO: do this as part of clearRtpReceiverRelay
735
+      //FIXME: resumeHeld(false); // TODO: do this as part of clearRtpReceiverRelay
627 736
       clearRtpReceiverRelay(); // we can't stay connected (at media level) with the other leg
628 737
       break; // this is OK
629 738
   }
630 739
 
740
+  // create new media session for us if needed
741
+  if (getRtpRelayMode() != RTP_Direct)
742
+    setMediaSession(new AmB2BMedia(a_leg ? this: NULL, a_leg ? NULL : this));
743
+
631 744
   clear_other();
632 745
   set_sip_relay_only(false); // we can't relay once disconnected
633 746
 
... ...
@@ -638,113 +751,84 @@ void CallLeg::disconnect(bool hold_remote)
638 751
   }
639 752
 }
640 753
 
641
-void CallLeg::createHoldRequest(AmSdp &sdp)
754
+static void sdp2body(const AmSdp &sdp, AmMimeBody &body)
642 755
 {
643
-  AmB2BMedia *ms = getMediaSession();
644
-  if (ms) {
645
-    ms->mute(a_leg);
646
-    ms->createHoldRequest(sdp, a_leg, false /*mark_zero_connection*/, true /*mark_sendonly*/);
647
-  }
648
-  else {
649
-    sdp.clear();
650
-
651
-    // FIXME: versioning
652
-    sdp.version = 0;
653
-    sdp.origin.user = "sems";
654
-    //offer.origin.sessId = 1;
655
-    //offer.origin.sessV = 1;
656
-    sdp.sessionName = "sems";
657
-    sdp.conn.network = NT_IN;
658
-    sdp.conn.addrType = AT_V4;
659
-    sdp.conn.address = "0.0.0.0";
660
-
661
-    // FIXME: use media line from stored body?
662
-    sdp.media.push_back(SdpMedia());
663
-    SdpMedia &m = sdp.media.back();
664
-    m.type = MT_AUDIO;
665
-    m.transport = TP_RTPAVP;
666
-    m.send = false;
667
-    m.recv = false;
668
-    m.payloads.push_back(SdpPayload(0));
669
-  }
756
+  string body_str;
757
+  sdp.print(body_str);
758
+
759
+  AmMimeBody *s = body.hasContentType(SIP_APPLICATION_SDP);
760
+  if (s) s->parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
761
+  else body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
670 762
 }
671 763
 
672 764
 void CallLeg::putOnHold()
673 765
 {
674
-  hold_status = HoldRequested;
675
-
676
-  if (isOnHold()) {
677
-    handleHoldReply(true); // really?
678
-    return;
679
-  }
766
+  if (on_hold) return;
680 767
 
681 768
   TRACE("putting remote on hold\n");
769
+  oa.hold = OA::HoldRequested;
682 770
 
683 771
   AmSdp sdp;
684 772
   createHoldRequest(sdp);
685 773
   updateLocalSdp(sdp);
686 774
 
687 775
   AmMimeBody body;
688
-  string body_str;
689
-  sdp.print(body_str);
690
-  body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
776
+  sdp2body(sdp, body);
691 777
   if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
692 778
     ERROR("re-INVITE failed\n");
693
-    handleHoldReply(false);
779
+    offerRejected();
694 780
   }
695
-  else hold_request_cseq = dlg->cseq - 1;
781
+  //else hold_request_cseq = dlg->cseq - 1;
696 782
 }
697 783
 
698
-void CallLeg::resumeHeld(bool send_reinvite)
784
+void CallLeg::resumeHeld(/*bool send_reinvite*/)
699 785
 {
700
-  hold_status = ResumeRequested;
701
-
702
-  if (!isOnHold()) {
703
-    handleHoldReply(true); // really?
704
-    return;
705
-  }
786
+  if (!on_hold) return;
706 787
 
707
-  TRACE("resume held remote\n");
788
+  try {
789
+    TRACE("resume held remote\n");
790
+    oa.hold = OA::ResumeRequested;
791
+
792
+    AmSdp sdp;
793
+    createResumeRequest(sdp);
794
+    if (sdp.media.empty()) {
795
+      ERROR("invalid un-hold SDP, can't unhold\n");
796
+      offerRejected();
797
+      return;
798
+    }
799
+    updateLocalSdp(sdp);
708 800
 
709
-  if (!send_reinvite) {
710
-    // probably another SDP in progress
711
-    handleHoldReply(true);
712
-    return;
801
+    AmMimeBody body(established_body);
802
+    sdp2body(sdp, body);
803
+    if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
804
+      ERROR("re-INVITE failed\n");
805
+      offerRejected();
806
+    }
807
+    //else hold_request_cseq = dlg->cseq - 1;
713 808
   }
714
-
715
-  AmMimeBody body(established_body);
716
-  updateLocalBody(body);
717
-  if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
718
-    ERROR("re-INVITE failed\n");
719
-    handleHoldReply(false);
809
+  catch (...) {
810
+    offerRejected();
720 811
   }
721
-  else hold_request_cseq = dlg->cseq - 1;
722 812
 }
723 813
 
724
-void CallLeg::handleHoldReply(bool succeeded)
814
+void CallLeg::holdAccepted()
725 815
 {
726
-  switch (hold_status) {
727
-    case HoldRequested:
728
-      // ignore the result (if hold request is not accepted that's a pitty but
729
-      // we are Disconnected anyway)
730
-      if (call_status == Disconnecting) updateCallStatus(Disconnected);
731
-
732
-      if (succeeded) hold_status = OnHold; // remote put on hold successfully
733
-      break;
816
+  if (call_status == Disconnecting) updateCallStatus(Disconnected);
817
+  on_hold = true;
818
+  AmB2BMedia *ms = getMediaSession();
819
+  if (ms) ms->mute(!a_leg); // mute the stream in other (!) leg
820
+}
734 821
 
735
-    case ResumeRequested:
736
-      if (succeeded) {
737
-        hold_status = NotHeld; // call resumed successfully
738
-        AmB2BMedia *ms = getMediaSession();
739
-        if (ms) ms->unmute(a_leg);
740
-      }
741
-      break;
822
+void CallLeg::holdRejected()
823
+{
824
+  if (call_status == Disconnecting) updateCallStatus(Disconnected);
825
+}
742 826
 
743
-    case NotHeld:
744
-    case OnHold:
745
-      //DBG("handling hold reply but hold was not requested\n");
746
-      break;
747
-  }
827
+void CallLeg::resumeAccepted()
828
+{
829
+  on_hold = false;
830
+  AmB2BMedia *ms = getMediaSession();
831
+  if (ms) ms->unmute(!a_leg); // unmute the stream in other (!) leg
748 832
 }
749 833
 
750 834
 // was for caller only
... ...
@@ -764,13 +848,11 @@ void CallLeg::onInvite(const AmSipRequest& req)
764 848
     recvd_req.insert(std::make_pair(req.cseq, req));
765 849
 
766 850
     initial_sdp_stored = false;
767
-    if (rtp_relay_mode != RTP_Direct) {
768
-      const AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP);
769
-      DBG("SDP %sfound in initial INVITE\n", sdp_body ? "": "not ");
770
-      if (sdp_body && (initial_sdp.parse((const char *)sdp_body->getPayload()) == 0)) {
771
-        DBG("storing remote SDP for later\n");
772
-        initial_sdp_stored = true;
773
-      }
851
+    const AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP);
852
+    DBG("SDP %sfound in initial INVITE\n", sdp_body ? "": "not ");
853
+    if (sdp_body && (initial_sdp.parse((const char *)sdp_body->getPayload()) == 0)) {
854
+      DBG("storing remote SDP for later\n");
855
+      initial_sdp_stored = true;
774 856
     }
775 857
   }
776 858
 }
... ...
@@ -824,15 +906,11 @@ void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSip
824 906
       (relayed_request ? "to relayed request" : "to locally generated request"),
825 907
       callStatus2str(call_status));
826 908
 
827
-  if ((reply.cseq == hold_request_cseq) && (reply.cseq_method == SIP_METH_INVITE)) {
828
-    // hold request replied - handle it
829
-    if (reply.code >= 200) { // handle final replies only
830
-      hold_request_cseq = 0;
831
-      handleHoldReply(reply.code < 300);
832
-    }
833
-
834
-    // we don't want to relay this reply to the other leg! => do the necessary
835
-    // stuff here (copy & paste from AmB2BSession::onSipReply)
909
+#if 0
910
+  if ((oa.hold != OA::PreserveHoldStatus) && (!relayed_request)) {
911
+    INFO("locally generated hold/resume request replied, not handling by B2B\n");
912
+    // local hold/resume request replied, we don't want to relay this reply to the other leg!
913
+    // => do the necessary stuff here (copy & paste from AmB2BSession::onSipReply)
836 914
     if (reply.code < 300) {
837 915
       const AmMimeBody *sdp_part = reply.body.hasContentType(SIP_APPLICATION_SDP);
838 916
       if (sdp_part) {
... ...
@@ -840,9 +918,11 @@ void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSip
840 918
         if (sdp.parse((const char *)sdp_part->getPayload()) == 0) updateRemoteSdp(sdp);
841 919
       }
842 920
     }
921
+    else if (reply.code >= 300) offerRejected();
843 922
     AmSession::onSipReply(req, reply, old_dlg_status);
844 923
     return;
845 924
   }
925
+#endif
846 926
 
847 927
   AmB2BSession::onSipReply(req, reply, old_dlg_status);
848 928
 
... ...
@@ -1312,3 +1392,126 @@ void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed,
1312 1392
   }
1313 1393
 }
1314 1394
 
1395
+void CallLeg::adjustOffer(AmSdp &sdp)
1396
+{
1397
+  if (oa.hold != OA::PreserveHoldStatus) {
1398
+    // locally generated hold/unhold requests that already contain correct
1399
+    // hold/resume bodies and need not to be altered via createHoldRequest
1400
+
1401
+    if (oa.hold == OA::HoldRequested) holdRequested();
1402
+    else resumeRequested();
1403
+  }
1404
+  else {
1405
+    // handling B2B SDP, check for hold/unhold
1406
+
1407
+    HoldMethod hm;
1408
+    // if hold request, transform to requested kind of hold and remember that hold
1409
+    // was requested with this offer
1410
+    if (isHoldRequest(sdp, hm)) {
1411
+      holdRequested();
1412
+      alterHoldRequest(sdp);
1413
+      oa.hold = OA::HoldRequested;
1414
+    }
1415
+    else {
1416
+      if (on_hold) {
1417
+        resumeRequested();
1418
+        alterResumeRequest(sdp);
1419
+        oa.hold = OA::ResumeRequested;
1420
+      }
1421
+    }
1422
+  }
1423
+}
1424
+
1425
+void CallLeg::updateLocalSdp(AmSdp &sdp)
1426
+{
1427
+  //INFO("%s: updateLocalSdp\n", getLocalTag().c_str());
1428
+  // handle the body based on current offer-answer status
1429
+  // (possibly update the body before sending to remote)
1430
+
1431
+  switch (oa.status) {
1432
+    case OA::None:
1433
+      adjustOffer(sdp);
1434
+      oa.status = OA::OfferSent;
1435
+      //FIXME: oa.offer_cseq = dlg->cseq;
1436
+      break;
1437
+
1438
+    case OA::OfferSent:
1439
+      ERROR("BUG: another SDP offer to be sent before answer/reject");
1440
+      oa.clear(); // or call offerRejected?
1441
+      break;
1442
+
1443
+    case OA::OfferReceived:
1444
+      // sending the answer
1445
+      oaCompleted();
1446
+      break;
1447
+  }
1448
+
1449
+  if (oa.hold == OA::PreserveHoldStatus && !on_hold) {
1450
+    // store non-hold SDP to be able to resumeHeld
1451
+    non_hold_sdp = sdp;
1452
+  }
1453
+
1454
+  AmB2BSession::updateLocalSdp(sdp);
1455
+}
1456
+
1457
+void CallLeg::updateRemoteSdp(AmSdp &sdp)
1458
+{
1459
+  //INFO("%s: updateRemoteSdp\n", getLocalTag().c_str());
1460
+  switch (oa.status) {
1461
+    case OA::None:
1462
+      oa.status = OA::OfferReceived;
1463
+      break;
1464
+
1465
+    case OA::OfferSent:
1466
+      oaCompleted();
1467
+      break;
1468
+
1469
+    case OA::OfferReceived:
1470
+      ERROR("BUG: another SDP offer received before answer/reject");
1471
+      oa.clear(); // or call offerRejected?
1472
+      break;
1473
+  }
1474
+
1475
+  AmB2BSession::updateRemoteSdp(sdp);
1476
+}
1477
+
1478
+void CallLeg::oaCompleted()
1479
+{
1480
+  switch (oa.hold) {
1481
+    case OA::HoldRequested: holdAccepted(); break;
1482
+    case OA::ResumeRequested: resumeAccepted(); break;
1483
+    case OA::PreserveHoldStatus: break;
1484
+  }
1485
+
1486
+  // call a callback here?
1487
+  oa.clear();
1488
+}
1489
+
1490
+void CallLeg::offerRejected()
1491
+{
1492
+  switch (oa.hold) {
1493
+    case OA::HoldRequested: holdRejected(); break;
1494
+    case OA::ResumeRequested: resumeRejected(); break;
1495
+    case OA::PreserveHoldStatus: break;
1496
+  }
1497
+
1498
+  oa.clear();
1499
+}
1500
+
1501
+void CallLeg::createResumeRequest(AmSdp &sdp)
1502
+{
1503
+  // use stored non-hold SDP
1504
+  // Note: this SDP doesn't need to be correct, but established_body need not to
1505
+  // be good enough for unholding (might be held already with zero conncetions)
1506
+  if (!non_hold_sdp.media.empty()) sdp = non_hold_sdp;
1507
+  else {
1508
+    // no stored non-hold SDP
1509
+    ERROR("no stored non-hold SDP, but local resume requested\n");
1510
+    // TODO: try to use established_body here and mark properly
1511
+
1512
+    // if no established body exist
1513
+    throw string("not implemented");
1514
+  }
1515
+  // do not touch the sdp otherwise (use directly B2B SDP)
1516
+}
1517
+
... ...
@@ -126,11 +126,19 @@ class CallLeg: public AmB2BSession
126 126
      * A leg, i.e. it creates new B leg(s) for itself. */
127 127
     std::vector<OtherLegInfo> other_legs;
128 128
 
129
-    unsigned hold_request_cseq; // CSeq of a request generated by us to hold the call
130
-    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 131
 
132 132
     std::queue<PendingReinvite> pending_reinvites;
133 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; }
140
+    } oa;
141
+
134 142
     // generate re-INVITE with given parameters (establishing means that the
135 143
     // INVITE is establishing a connection between two legs)
136 144
     void reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing);
... ...
@@ -203,6 +211,15 @@ class CallLeg: public AmB2BSession
203 211
     void changeRtpMode(RTPRelayMode new_mode, AmB2BMedia *new_media);
204 212
     void changeOtherLegsRtpMode(RTPRelayMode new_mode);
205 213
 
214
+    // offer-answer handling
215
+    void adjustOffer(AmSdp &sdp);
216
+    void oaCompleted(); // offer-answer exchange completed, both SDPs are handled
217
+
218
+     /* offer was rejected (called just for negative replies to an request
219
+      * carying offer (not always correct?), answer with disabled streams
220
+      * doesn't cause calling this */
221
+     void offerRejected();
222
+
206 223
   protected:
207 224
 
208 225
     // functions offered to successors
... ...
@@ -230,11 +247,28 @@ class CallLeg: public AmB2BSession
230 247
     virtual void onInitialReply(B2BSipReplyEvent *e);
231 248
 
232 249
     /* callback method called when hold/resume request is replied */
233
-    virtual void handleHoldReply(bool succeeded);
250
+//    virtual void handleHoldReply(bool succeeded);
251
+
252
+    /* called to create SDP of locally generated hold request */
253
+    virtual void createHoldRequest(AmSdp &sdp) = 0;
234 254
 
235
-    /* called to create SDP of hold request
236
-     * (note that resume request always uses established_body stored before) */
237
-    virtual void createHoldRequest(AmSdp &sdp);
255
+    /** called to alter B2B hold request (i.e. the request from other peer) */
256
+    virtual void alterHoldRequest(AmSdp &sdp) { }
257
+
258
+    /* called to create SDP of locally generated resume request */
259
+    virtual void createResumeRequest(AmSdp &sdp);
260
+
261
+    /** called to alter B2B hold request (i.e. the request from other peer) */
262
+    virtual void alterResumeRequest(AmSdp &sdp) { }
263
+
264
+    /* hold requested (either from B2B peer leg or locally)
265
+     * to be overridden */
266
+    virtual void holdRequested() { }
267
+    virtual void holdAccepted();
268
+    virtual void holdRejected();
269
+    virtual void resumeRequested() { }
270
+    virtual void resumeAccepted();
271
+    virtual void resumeRejected() { }
238 272
 
239 273
     virtual void terminateOtherLeg();
240 274
     virtual void terminateLeg();
... ...
@@ -252,6 +286,9 @@ class CallLeg: public AmB2BSession
252 286
      * TODO: add parameter to trigger reINVITE */
253 287
     void changeRtpMode(RTPRelayMode new_mode);
254 288
 
289
+    virtual void updateLocalSdp(AmSdp &sdp);
290
+    virtual void updateRemoteSdp(AmSdp &sdp);
291
+
255 292
   public:
256 293
     virtual void onB2BEvent(B2BEvent* ev);
257 294
 
... ...
@@ -276,9 +313,9 @@ class CallLeg: public AmB2BSession
276 313
     virtual void putOnHold();
277 314
 
278 315
     /** resume call if the remote party is on hold */
279
-    virtual void resumeHeld(bool send_reinvite);
316
+    virtual void resumeHeld(/*bool send_reinvite*/);
280 317
 
281
-    virtual bool isOnHold() { return hold_status == OnHold; }
318
+    virtual bool isOnHold() { return on_hold; }
282 319
 
283 320
 
284 321
     /** add given call leg as our B leg */
... ...
@@ -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:
... ...
@@ -1534,36 +1534,121 @@ void SBCCallLeg::initCCExtModules()
1534 1534
   }
1535 1535
 }
1536 1536
 
1537
-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()
1538 1545
 {
1539
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1540
-    if ((*i)->putOnHold(this) == StopProcessing) return;
1541
-  }
1542
-  CallLeg::putOnHold();
1546
+  TRACE("%s: hold requested\n", getLocalTag().c_str());
1547
+  CALL_EXT_CC_MODULES(holdRequested);
1548
+  CallLeg::holdRequested();
1543 1549
 }
1544 1550
 
1545
-void SBCCallLeg::resumeHeld(bool send_reinvite)
1551
+void SBCCallLeg::holdAccepted()
1546 1552
 {
1547
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1548
-    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
+    }
1549 1596
   }
1550
-  CallLeg::resumeHeld(send_reinvite);
1597
+
1598
+  DBG("unsupported connection type for marking with 0.0.0.0");
1551 1599
 }
1552 1600
 
1553
-void SBCCallLeg::handleHoldReply(bool succeeded)
1601
+void SBCCallLeg::alterHoldRequest(AmSdp &sdp)
1554 1602
 {
1555
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1556
-    if ((*i)->handleHoldReply(this, succeeded) == StopProcessing) return;
1603
+  TRACE("altering B2B hold request\n");
1604
+
1605
+  if (!call_profile.hold_settings.alter_b2b(a_leg)) return;
1606
+
1607
+  bool zero = call_profile.hold_settings.mark_zero_connection(a_leg);
1608
+  bool recv = call_profile.hold_settings.recv(a_leg);
1609
+
1610
+  if (zero) zero_connection(sdp.conn);
1611
+  for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
1612
+    if (zero) zero_connection(m->conn);
1613
+    m->recv = recv;
1557 1614
   }
1558
-  CallLeg::handleHoldReply(succeeded);
1559 1615
 }
1560 1616
 
1561 1617
 void SBCCallLeg::createHoldRequest(AmSdp &sdp)
1562 1618
 {
1563
-  for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
1564
-    if ((*i)->createHoldRequest(this, sdp) == StopProcessing) return;
1619
+  // FIXME: hold SDP must be based on sdp if possible (updates existing session)
1620
+  // hack: we need to have other side SDP (if the stream is hold already
1621
+  // it should be marked as inactive)
1622
+
1623
+  AmB2BMedia *ms = getMediaSession();
1624
+  if (ms) {
1625
+    ms->mute(a_leg);
1626
+    ms->createHoldRequest(sdp, a_leg,
1627
+        call_profile.hold_settings.mark_zero_connection(a_leg),
1628
+        !call_profile.hold_settings.recv(a_leg));
1629
+  }
1630
+  else {
1631
+    sdp.clear();
1632
+
1633
+    // FIXME: versioning
1634
+    sdp.version = 0;
1635
+    sdp.origin.user = "sems";
1636
+    //offer.origin.sessId = 1;
1637
+    //offer.origin.sessV = 1;
1638
+    sdp.sessionName = "sems";
1639
+    sdp.conn.network = NT_IN;
1640
+    sdp.conn.addrType = AT_V4;
1641
+    sdp.conn.address = "0.0.0.0"; // we have no socket for RTP/RTCP on our side so we must do this
1642
+
1643
+    // FIXME: use media line from stored body?
1644
+    sdp.media.push_back(SdpMedia());
1645
+    SdpMedia &m = sdp.media.back();
1646
+    m.type = MT_AUDIO;
1647
+    m.transport = TP_RTPAVP;
1648
+    m.send = false;
1649
+    m.recv = false;
1650
+    m.payloads.push_back(SdpPayload(0));
1565 1651
   }
1566
-  CallLeg::createHoldRequest(sdp);
1567 1652
 }
1568 1653
 
1569 1654
 void SBCCallLeg::setMediaSession(AmB2BMedia *new_session)
... ...
@@ -164,9 +164,6 @@ 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
 
... ...
@@ -210,8 +207,14 @@ class SBCCallLeg : public CallLeg, public CredentialHolder
210 207
   bool getCCInterfaces();
211 208
   vector<AmDynInvoke*>& getCCModules() { return cc_modules; }
212 209
 
213
-  virtual void handleHoldReply(bool succeeded);
214 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();
215 218
 
216 219
   int applySSTCfg(AmConfigReader& sst_cfg, const AmSipRequest* p_req);
217 220
 
... ...
@@ -367,6 +367,7 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
367 367
 
368 368
   if (!codec_prefs.readConfig(cfg)) return false;
369 369
   if (!transcoder.readConfig(cfg)) return false;
370
+  hold_settings.readConfig(cfg);
370 371
 
371 372
   msg_logger_path = cfg.getParameter("msg_logger_path");
372 373
   log_rtp = cfg.getParameter("log_rtp","no") == "yes";
... ...
@@ -806,6 +807,7 @@ bool SBCCallProfile::evaluate(ParamReplacerCtx& ctx,
806 807
   REPLACE_IFACE_SIP(outbound_interface, outbound_interface_value);
807 808
 
808 809
   if (!codec_prefs.evaluate(ctx,req)) return false;
810
+  if (!hold_settings.evaluate(ctx,req)) return false;
809 811
 
810 812
   // TODO: activate filter if transcoder or codec_prefs is set?
811 813
 /*  if ((!aleg_payload_order.empty() || !bleg_payload_order.empty()) && (!sdpfilter_enabled)) {
... ...
@@ -1621,3 +1623,30 @@ bool PayloadDesc::operator==(const PayloadDesc &other) const
1621 1623
   if (clock_rate != other.clock_rate) return false;
1622 1624
   return true;
1623 1625
 }
1626
+
1627
+//////////////////////////////////////////////////////////////////////////////////
1628
+
1629
+void SBCCallProfile::HoldSettings::readConfig(AmConfigReader &cfg)
1630
+{
1631
+  // store string values for later evaluation
1632
+  aleg.mark_zero_connection_str = cfg.getParameter("hold_zero_connection_aleg");
1633
+  aleg.recv_str = cfg.getParameter("hold_enable_recv_aleg");
1634
+  aleg.alter_b2b_str = cfg.getParameter("hold_alter_b2b_aleg");
1635
+
1636
+  bleg.mark_zero_connection_str = cfg.getParameter("hold_zero_connection_bleg");
1637
+  bleg.recv_str = cfg.getParameter("hold_enable_recv_bleg");
1638
+  bleg.alter_b2b_str = cfg.getParameter("hold_alter_b2b_bleg");
1639
+}
1640
+
1641
+bool SBCCallProfile::HoldSettings::evaluate(ParamReplacerCtx& ctx, const AmSipRequest& req)
1642
+{
1643
+  REPLACE_BOOL(aleg.mark_zero_connection_str, aleg.mark_zero_connection);
1644
+  REPLACE_BOOL(aleg.recv_str, aleg.recv);
1645
+  REPLACE_BOOL(aleg.alter_b2b_str, aleg.alter_b2b);
1646
+
1647
+  REPLACE_BOOL(bleg.mark_zero_connection_str, bleg.mark_zero_connection);
1648
+  REPLACE_BOOL(bleg.recv_str, bleg.recv);
1649
+  REPLACE_BOOL(bleg.alter_b2b_str, bleg.alter_b2b);
1650
+
1651
+  return true;
1652
+}
... ...
@@ -280,6 +280,29 @@ struct SBCCallProfile
280 280
 
281 281
   // todo: RTP transcoding mode
282 282
 
283
+  // hold settings
284
+  class HoldSettings {
285
+    private:
286
+      struct HoldParams {
287
+        // non-replaced params
288
+        string mark_zero_connection_str, recv_str, alter_b2b_str;
289
+
290
+        bool mark_zero_connection;
291
+        bool recv; // sendrecv/recvonly (if set) X sendonly/inactive (if unset)
292
+        bool alter_b2b; // transform B2B hold requests (not locally generated ones)
293
+
294
+        HoldParams(): mark_zero_connection(false), recv(false), alter_b2b(false) { }
295
+      } aleg, bleg;
296
+
297
+    public:
298
+      bool mark_zero_connection(bool a_leg) { return a_leg ? aleg.mark_zero_connection : bleg.mark_zero_connection; }
299
+      bool recv(bool a_leg) { return a_leg ? aleg.recv : bleg.recv; }
300
+      bool alter_b2b(bool a_leg) { return a_leg ? aleg.alter_b2b : bleg.alter_b2b; }
301
+
302
+      void readConfig(AmConfigReader &cfg);
303
+      bool evaluate(ParamReplacerCtx& ctx, const AmSipRequest& req);
304
+  } hold_settings;
305
+
283 306
  private:
284 307
   // message logging feature
285 308
   string msg_logger_path;