Browse code

Merge branch 'pbx/base' into pbx/master_subnot

Conflicts:
apps/sbc/SBC.cpp
apps/sbc/SBC.h
apps/sbc/SDPFilter.cpp
core/AmB2BMedia.cpp
core/AmB2BMedia.h
core/AmB2BSession.cpp
core/AmRtpStream.cpp
core/AmRtpStream.h
core/AmSipDialog.h
core/sip/defs.h

Work in progress - compilable but not working correctly.

Václav Kubart authored on 31/12/2012 15:51:24
Showing 17 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1073 @@
1
+#include "CallLeg.h"
2
+#include "AmSessionContainer.h"
3
+#include "AmConfig.h"
4
+#include "ampi/MonitoringAPI.h"
5
+#include "AmSipHeaders.h"
6
+#include "AmUtils.h"
7
+#include "AmRtpReceiver.h"
8
+
9
+#define TRACE DBG
10
+
11
+// helper functions
12
+
13
+static const char *callStatus2str(const CallLeg::CallStatus state)
14
+{
15
+  static const char *disconnected = "Disconnected";
16
+  static const char *disconnecting = "Disconnecting";
17
+  static const char *noreply = "NoReply";
18
+  static const char *ringing = "Ringing";
19
+  static const char *connected = "Connected";
20
+  static const char *unknown = "???";
21
+
22
+  switch (state) {
23
+    case CallLeg::Disconnected: return disconnected;
24
+    case CallLeg::Disconnecting: return disconnecting;
25
+    case CallLeg::NoReply: return noreply;
26
+    case CallLeg::Ringing: return ringing;
27
+    case CallLeg::Connected: return connected;
28
+  }
29
+
30
+  return unknown;
31
+}
32
+
33
+ReliableB2BEvent::~ReliableB2BEvent()
34
+{
35
+  TRACE("reliable event was %sprocessed, sending %p to %s\n",
36
+      processed ? "" : "NOT ",
37
+      processed ? processed_reply : unprocessed_reply,
38
+      sender.c_str());
39
+  if (processed) {
40
+    if (unprocessed_reply) delete unprocessed_reply;
41
+    if (processed_reply) AmSessionContainer::instance()->postEvent(sender, processed_reply);
42
+  }
43
+  else {
44
+    if (processed_reply) delete processed_reply;
45
+    if (unprocessed_reply) AmSessionContainer::instance()->postEvent(sender, unprocessed_reply);
46
+  }
47
+}
48
+
49
+////////////////////////////////////////////////////////////////////////////////
50
+
51
+// callee
52
+CallLeg::CallLeg(const CallLeg* caller):
53
+  AmB2BSession(caller->getLocalTag()),
54
+  call_status(Disconnected),
55
+  hold_request_cseq(0), hold_status(NotHeld)
56
+{
57
+  a_leg = !caller->a_leg; // we have to be the complement
58
+
59
+  set_sip_relay_only(false); // will be changed later on (for now we have no peer so we can't relay)
60
+
61
+  // code below taken from createCalleeSession
62
+
63
+  const AmSipDialog& caller_dlg = caller->dlg;
64
+
65
+  dlg.local_tag    = AmSession::getNewId();
66
+  dlg.callid       = AmSession::getNewId();
67
+
68
+  // take important data from A leg
69
+  dlg.local_party  = caller_dlg.remote_party;
70
+  dlg.remote_party = caller_dlg.local_party;
71
+  dlg.remote_uri   = caller_dlg.local_uri;
72
+
73
+/*  if (AmConfig::LogSessions) {
74
+    INFO("Starting B2B callee session %s\n",
75
+	 getLocalTag().c_str());
76
+  }
77
+
78
+  MONITORING_LOG4(other_id.c_str(), 
79
+		  "dir",  "out",
80
+		  "from", dlg.local_party.c_str(),
81
+		  "to",   dlg.remote_party.c_str(),
82
+		  "ruri", dlg.remote_uri.c_str());
83
+*/
84
+
85
+  // copy common RTP relay settings from A leg
86
+  //initRTPRelay(caller);
87
+  vector<SdpPayload> lowfi_payloads;
88
+  setRtpRelayMode(caller->getRtpRelayMode());
89
+  setEnableDtmfTranscoding(caller->getEnableDtmfTranscoding());
90
+  caller->getLowFiPLs(lowfi_payloads);
91
+  setLowFiPLs(lowfi_payloads);
92
+}
93
+
94
+// caller
95
+CallLeg::CallLeg(): 
96
+  AmB2BSession(),
97
+  call_status(Disconnected),
98
+  hold_request_cseq(0), hold_status(NotHeld)
99
+{
100
+  a_leg = true;
101
+
102
+  // At least in the first version we start relaying after the call is fully
103
+  // established.  This is because of forking possibility - we can't simply
104
+  // relay if we have one A leg and multiple B legs.
105
+  // It is possible to start relaying before call is established if we have
106
+  // exactly one B leg (i.e. no parallel fork happened).
107
+  set_sip_relay_only(false);
108
+}
109
+    
110
+void CallLeg::terminateOtherLeg()
111
+{
112
+  if (call_status != Connected) {
113
+    DBG("trying to terminate other leg in %s state -> terminating the others as well\n", callStatus2str(call_status));
114
+    // FIXME: may happen when for example reply forward fails, do we want to terminate
115
+    // all other legs in such case?
116
+    terminateNotConnectedLegs(); // terminates all except the one identified by other_id
117
+  }
118
+  
119
+  AmB2BSession::terminateOtherLeg();
120
+
121
+  // FIXME: call disconnect if connected (to put remote on hold)?
122
+  updateCallStatus(Disconnected); // no B legs should be remaining
123
+}
124
+
125
+void CallLeg::terminateNotConnectedLegs()
126
+{
127
+  bool found = false;
128
+  OtherLegInfo b;
129
+
130
+  for (vector<OtherLegInfo>::iterator i = other_legs.begin(); i != other_legs.end(); ++i) {
131
+    if (i->id != other_id) {
132
+      i->releaseMediaSession();
133
+      AmSessionContainer::instance()->postEvent(i->id, new B2BEvent(B2BTerminateLeg));
134
+    }
135
+    else {
136
+      found = true; // other_id is there
137
+      b = *i;
138
+    }
139
+  }
140
+
141
+  // quick hack to remove all terminated entries from the list
142
+  other_legs.clear();
143
+  if (found) other_legs.push_back(b);
144
+}
145
+
146
+void CallLeg::removeOtherLeg(const string &id)
147
+{
148
+  if (other_id == id) other_id.clear();
149
+
150
+  // remove the call leg from list of B legs
151
+  for (vector<OtherLegInfo>::iterator i = other_legs.begin(); i != other_legs.end(); ++i) {
152
+    if (i->id == id) {
153
+      i->releaseMediaSession();
154
+      other_legs.erase(i);
155
+      break;
156
+    }
157
+  }
158
+
159
+  /*if (terminate) AmSessionContainer::instance()->postEvent(id, new B2BEvent(B2BTerminateLeg));*/
160
+}
161
+
162
+// composed for caller and callee already
163
+void CallLeg::onB2BEvent(B2BEvent* ev)
164
+{
165
+  switch (ev->event_id) {
166
+
167
+    case B2BSipReply:
168
+      onB2BReply(dynamic_cast<B2BSipReplyEvent*>(ev));
169
+      break;
170
+
171
+    case ConnectLeg:
172
+      onB2BConnect(dynamic_cast<ConnectLegEvent*>(ev));
173
+      break;
174
+
175
+    case ReconnectLeg:
176
+      onB2BReconnect(dynamic_cast<ReconnectLegEvent*>(ev));
177
+      break;
178
+
179
+    case ReplaceLeg:
180
+      onB2BReplace(dynamic_cast<ReplaceLegEvent*>(ev));
181
+      break;
182
+
183
+    case ReplaceInProgress:
184
+      onB2BReplaceInProgress(dynamic_cast<ReplaceInProgressEvent*>(ev));
185
+      break;
186
+
187
+    case DisconnectLeg:
188
+      {
189
+        DisconnectLegEvent *dle = dynamic_cast<DisconnectLegEvent*>(ev);
190
+        if (dle) disconnect(dle->put_remote_on_hold);
191
+      }
192
+      break;
193
+
194
+    case B2BSipRequest:
195
+      if (!sip_relay_only) {
196
+        // disable forwarding of relayed request if we are not connected [yet]
197
+        // (only we known that, the B leg has just delayed information about being
198
+        // connected to us and thus it can't set)
199
+        // Need not to be done if we have only one possible B leg so instead of
200
+        // checking call_status we can check if sip_relay_only is set or not
201
+        B2BSipRequestEvent *req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
202
+        if (req_ev) req_ev->forward = false;
203
+      }
204
+      // continue handling in AmB2bSession
205
+
206
+    default:
207
+      AmB2BSession::onB2BEvent(ev);
208
+  }
209
+}
210
+
211
+int CallLeg::relaySipReply(AmSipReply &reply)
212
+{
213
+  std::map<int,AmSipRequest>::iterator t_req = recvd_req.find(reply.cseq);
214
+
215
+  if (t_req == recvd_req.end()) {
216
+    ERROR("Request with CSeq %u not found in recvd_req.\n", reply.cseq);
217
+    return 0; // ignore?
218
+  }
219
+
220
+  int res;
221
+
222
+  if ((reply.code >= 300) && (reply.code <= 305) && !reply.contact.empty()) {
223
+    // relay with Contact in 300 - 305 redirect messages
224
+    AmSipReply n_reply(reply);
225
+    n_reply.hdrs += SIP_HDR_COLSP(SIP_HDR_CONTACT) + reply.contact + CRLF;
226
+
227
+    res = relaySip(t_req->second, n_reply);
228
+  }
229
+  else res = relaySip(t_req->second, reply) < 0; // relay response directly
230
+
231
+  if (reply.code >= 200){
232
+    DBG("recvd_req.erase(<%u,%s>)\n", t_req->first, t_req->second.method.c_str());
233
+    recvd_req.erase(t_req);
234
+  }
235
+  return res;
236
+}
237
+
238
+// was for caller only
239
+void CallLeg::onB2BReply(B2BSipReplyEvent *ev)
240
+{
241
+  if (!ev) {
242
+    ERROR("BUG: invalid argument given\n");
243
+    return;
244
+  }
245
+
246
+  AmSipReply& reply = ev->reply;
247
+
248
+  TRACE("%s: B2B SIP reply %d/%d %s received in %s state\n",
249
+      getLocalTag().c_str(),
250
+      reply.code, reply.cseq, reply.cseq_method.c_str(),
251
+      callStatus2str(call_status));
252
+
253
+  // FIXME: do we wat to have the check below? multiple other legs are
254
+  // possible so the check can stay as it is for Connected state but for other
255
+  // should check other_legs instead of other_id
256
+#if 0
257
+  if(other_id.empty()){
258
+    //DBG("Discarding B2BSipReply from other leg (other_id empty)\n");
259
+    DBG("B2BSipReply: other_id empty ("
260
+        "reply code=%i; method=%s; callid=%s; from_tag=%s; "
261
+        "to_tag=%s; cseq=%i)\n",
262
+        reply.code,reply.cseq_method.c_str(),reply.callid.c_str(),reply.from_tag.c_str(),
263
+        reply.to_tag.c_str(),reply.cseq);
264
+    //return;
265
+  }
266
+  else if(other_id != reply.from_tag){// was: local_tag
267
+    DBG("Dialog mismatch! (oi=%s;ft=%s)\n",
268
+        other_id.c_str(),reply.from_tag.c_str());
269
+    return;
270
+  }
271
+#endif
272
+
273
+  // handle relayed initial replies specific way
274
+  // testing est_invite_cseq is wrong! (checking in what direction or what role
275
+  // would be needed)
276
+  if (reply.cseq_method == SIP_METH_INVITE &&
277
+      (call_status == NoReply || call_status == Ringing) &&
278
+      ((reply.cseq == est_invite_cseq && ev->forward) ||
279
+       (!ev->forward))) { // connect not related to initial INVITE
280
+    // handle only replies to the original INVITE (CSeq is really the same?)
281
+
282
+    TRACE("established CSeq: %d, forward: %s\n", est_invite_cseq, ev->forward ? "yes": "no");
283
+
284
+    // TODO: stop processing of 100 reply here or add Trying state to handle it
285
+    // without remembering other_id
286
+
287
+    if (reply.code < 200) { // 1xx replies
288
+      if (call_status == NoReply) {
289
+        if (ev->forward && relaySipReply(reply) != 0) {
290
+          stopCall();
291
+          return;
292
+        }
293
+        if (!reply.to_tag.empty()) {
294
+          other_id = reply.from_tag;
295
+          TRACE("1xx reply with to-tag received in NoReply state, changing status to Ringing and remembering the other leg ID (%s)\n", other_id.c_str());
296
+          if (ev->forward && relaySipReply(reply) != 0) {
297
+            stopCall();
298
+            return;
299
+          }
300
+          updateCallStatus(Ringing);
301
+        }
302
+      }
303
+      else {
304
+        if (other_id != reply.from_tag) {
305
+           // in Ringing state but the reply comes from another B leg than
306
+           // previous 1xx reply => do not relay or process other way
307
+          DBG("1xx reply received in %s state from another B leg, ignoring\n", callStatus2str(call_status));
308
+          return;
309
+        }
310
+        // we can relay this reply because it is from the same B leg from which
311
+        // we already relayed something
312
+        // FIXME: but we shouldn't relay the body until we are connected because
313
+        // fork still can happen, right? (so no early media support? or we would
314
+        // just destroy the before-fork-media-session and use the
315
+        // after-fork-one? but problem could be with two B legs trying to do
316
+        // early media)
317
+        if (!sip_relay_only && !reply.body.empty()) {
318
+          DBG("not going to relay 1xx body\n");
319
+          static const AmMimeBody empty_body;
320
+          reply.body = empty_body;
321
+        }
322
+        if (ev->forward && relaySipReply(reply) != 0) {
323
+          stopCall();
324
+          return;
325
+        }
326
+      }
327
+    } else if (reply.code < 300) { // 2xx replies
328
+      other_id = reply.from_tag;
329
+      TRACE("setting call status to connected with leg %s\n", other_id.c_str());
330
+
331
+      // terminate all other legs than the connected one (determined by other_id)
332
+      terminateNotConnectedLegs();
333
+
334
+      if (other_legs.empty()) {
335
+        ERROR("BUG: connected but there is no B leg remaining\n");
336
+        stopCall();
337
+        return;
338
+      }
339
+
340
+      // connect media with the other leg if RTP relay is enabled
341
+      clearRtpReceiverRelay(); // release old media session if set
342
+      setMediaSession(other_legs.begin()->media_session);
343
+      other_legs.begin()->releaseMediaSession(); // remove reference hold by OtherLegInfo
344
+      other_legs.clear(); // no need to remember the connected leg here
345
+      if (media_session) {
346
+        TRACE("connecting media session: %s to %s\n", dlg.local_tag.c_str(), other_id.c_str());
347
+        media_session->changeSession(a_leg, this);
348
+        if (initial_sdp_stored && ev->forward) updateRemoteSdp(initial_sdp);
349
+      }
350
+      else {
351
+        // media session not set, set direct mode if not set already
352
+        if (rtp_relay_mode != AmB2BSession::RTP_Direct) setRtpRelayMode(AmB2BSession::RTP_Direct);
353
+      }
354
+
355
+      // FIXME: hack here - it should be part of clearRtpReceiverRelay but we
356
+      // need to do after RTP mode change above
357
+      resumeHeld(false);
358
+
359
+      onCallConnected(reply);
360
+      set_sip_relay_only(true); // relay only from now on
361
+
362
+      if (!ev->forward) {
363
+        // we need to generate re-INVITE based on received SDP
364
+        saveSessionDescription(reply.body);
365
+        sendEstablishedReInvite();
366
+      }
367
+      else if (relaySipReply(reply) != 0) {
368
+        stopCall();
369
+        return;
370
+      }
371
+      updateCallStatus(Connected);
372
+    } else { // 3xx-6xx replies
373
+      removeOtherLeg(reply.from_tag); // we don't care about this leg any more
374
+      onBLegRefused(reply); // possible serial fork here
375
+
376
+      // there are other B legs for us => wait for their responses and do not
377
+      // relay current response
378
+      if (!other_legs.empty()) return;
379
+
380
+      if (ev->forward) relaySipReply(reply);
381
+
382
+      // no other B legs, terminate
383
+      updateCallStatus(Disconnected);
384
+      stopCall();
385
+    }
386
+
387
+    return; // relayed reply to initial request is processed
388
+  }
389
+
390
+  // reply not from our peer (might be one of the discarded ones)
391
+  if (other_id != reply.from_tag) {
392
+    TRACE("ignoring reply from %s in %s state\n", reply.from_tag.c_str(), callStatus2str(call_status));
393
+    return;
394
+  }
395
+
396
+  // handle replies to other requests than the initial one
397
+  DBG("handling reply via AmB2BSession\n");
398
+  AmB2BSession::onB2BEvent(ev);
399
+}
400
+
401
+// TODO: original callee's version, update
402
+void CallLeg::onB2BConnect(ConnectLegEvent* co_ev)
403
+{
404
+  if (!co_ev) {
405
+    ERROR("BUG: invalid argument given\n");
406
+    return;
407
+  }
408
+
409
+  if (call_status != Disconnected) {
410
+    ERROR("BUG: ConnectLegEvent received in %s state\n", callStatus2str(call_status));
411
+    return;
412
+  }
413
+
414
+  MONITORING_LOG3(getLocalTag().c_str(), 
415
+      "b2b_leg", other_id.c_str(),
416
+      "to", dlg.remote_party.c_str(),
417
+      "ruri", dlg.remote_uri.c_str());
418
+
419
+  // This leg is marked as 'relay only' since the beginning because it might
420
+  // need not to know on time that it is connected and thus should relay.
421
+  //
422
+  // For example: B leg received 2xx reply, relayed it to A leg and is
423
+  // immediatelly processing in-dialog request which should be relayed, but
424
+  // A leg didn't have chance to process the relayed reply so the B leg is not
425
+  // connected to the A leg yet when handling the in-dialog request.
426
+  set_sip_relay_only(true); // we should relay everything to the other leg from now
427
+
428
+  updateCallStatus(NoReply);
429
+
430
+  AmMimeBody r_body(co_ev->body);
431
+  const AmMimeBody* body = &co_ev->body;
432
+  if (rtp_relay_mode == RTP_Relay) {
433
+    try {
434
+      body = co_ev->body.hasContentType(SIP_APPLICATION_SDP);
435
+      if (body && updateLocalBody(*body, *r_body.hasContentType(SIP_APPLICATION_SDP))) {
436
+        body = &r_body;
437
+      }
438
+      else {
439
+        body = &co_ev->body;
440
+      }
441
+    } catch (const string& s) {
442
+      relayError(SIP_METH_INVITE, co_ev->r_cseq, true, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
443
+      throw;
444
+    }
445
+  }
446
+
447
+  int res = dlg.sendRequest(SIP_METH_INVITE, body,
448
+      co_ev->hdrs, SIP_FLAGS_VERBATIM);
449
+  if (res < 0) {
450
+    DBG("sending INVITE failed, relaying back error reply\n");
451
+    relayError(SIP_METH_INVITE, co_ev->r_cseq, true, res);
452
+
453
+    stopCall();
454
+    return;
455
+  }
456
+
457
+  if (co_ev->relayed_invite) {
458
+    AmSipRequest fake_req;
459
+    fake_req.method = SIP_METH_INVITE;
460
+    fake_req.cseq = co_ev->r_cseq;
461
+    relayed_req[dlg.cseq - 1] = fake_req;
462
+    est_invite_other_cseq = co_ev->r_cseq;
463
+  }
464
+  else est_invite_other_cseq = 0;
465
+
466
+  if (!co_ev->body.empty()) {
467
+    saveSessionDescription(co_ev->body);
468
+  }
469
+
470
+  // save CSeq of establising INVITE
471
+  est_invite_cseq = dlg.cseq - 1;
472
+}
473
+
474
+void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
475
+{
476
+  if (!ev) {
477
+    ERROR("BUG: invalid argument given\n");
478
+    return;
479
+  }
480
+  TRACE("handling ReconnectLegEvent, other: %s, connect to %s\n", other_id.c_str(), ev->session_tag.c_str());
481
+
482
+  ev->markAsProcessed();
483
+
484
+  // release old signaling and media session
485
+  terminateOtherLeg();
486
+  resumeHeld(false);
487
+  clearRtpReceiverRelay();
488
+
489
+  other_id = ev->session_tag;
490
+  if (ev->role == ReconnectLegEvent::A) a_leg = true;
491
+  else a_leg = false;
492
+  // FIXME: What about calling SBC CC modules in this case? Original CC
493
+  // interface is called from A leg only and it might happen that we were call
494
+  // leg A before.
495
+
496
+  set_sip_relay_only(true); // we should relay everything to the other leg from now
497
+  updateCallStatus(NoReply);
498
+
499
+  // use new media session if given
500
+  if (ev->media) {
501
+    rtp_relay_mode = RTP_Relay;
502
+    setMediaSession(ev->media);
503
+    media_session->changeSession(a_leg, this);
504
+  }
505
+  else rtp_relay_mode = RTP_Direct;
506
+
507
+  MONITORING_LOG3(getLocalTag().c_str(),
508
+      "b2b_leg", other_id.c_str(),
509
+      "to", dlg.remote_party.c_str(),
510
+      "ruri", dlg.remote_uri.c_str());
511
+
512
+  AmMimeBody r_body(ev->body);
513
+  const AmMimeBody* body = &ev->body;
514
+  if (rtp_relay_mode == RTP_Relay) {
515
+    try {
516
+      body = ev->body.hasContentType(SIP_APPLICATION_SDP);
517
+      if (body && updateLocalBody(*body, *r_body.hasContentType(SIP_APPLICATION_SDP))) {
518
+        body = &r_body;
519
+      }
520
+      else {
521
+        body = &ev->body;
522
+      }
523
+    } catch (const string& s) {
524
+      relayError(SIP_METH_INVITE, ev->r_cseq, true, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
525
+      throw;
526
+    }
527
+  }
528
+
529
+  // generate re-INVITE
530
+  int res = dlg.sendRequest(SIP_METH_INVITE, body, ev->hdrs, SIP_FLAGS_VERBATIM);
531
+  if (res < 0) {
532
+    DBG("sending re-INVITE failed, relaying back error reply\n");
533
+    relayError(SIP_METH_INVITE, ev->r_cseq, true, res);
534
+
535
+    stopCall();
536
+    return;
537
+  }
538
+
539
+  if (ev->relayed_invite) {
540
+    AmSipRequest fake_req;
541
+    fake_req.method = SIP_METH_INVITE;
542
+    fake_req.cseq = ev->r_cseq;
543
+    relayed_req[dlg.cseq - 1] = fake_req;
544
+    est_invite_other_cseq = ev->r_cseq;
545
+  }
546
+  else est_invite_other_cseq = 0;
547
+
548
+  saveSessionDescription(ev->body);
549
+
550
+  // save CSeq of establising INVITE
551
+  est_invite_cseq = dlg.cseq - 1;
552
+}
553
+
554
+void CallLeg::onB2BReplace(ReplaceLegEvent *e)
555
+{
556
+  if (!e) {
557
+    ERROR("BUG: invalid argument given\n");
558
+    return;
559
+  }
560
+  e->markAsProcessed();
561
+
562
+  ReconnectLegEvent *reconnect = e->getReconnectEvent();
563
+  if (!reconnect) {
564
+    ERROR("BUG: invalid ReconnectLegEvent\n");
565
+    return;
566
+  }
567
+
568
+  TRACE("handling ReplaceLegEvent, other: %s, connect to %s\n", other_id.c_str(), reconnect->session_tag.c_str());
569
+
570
+  string id(other_id);
571
+  if (id.empty()) {
572
+    // try it with the first B leg?
573
+    if (other_legs.empty()) {
574
+      ERROR("BUG: there is no B leg to connect our replacement to\n");
575
+      return;
576
+    }
577
+    id = other_legs[0].id;
578
+  }
579
+
580
+  // send session ID of the other leg to the originator
581
+  AmSessionContainer::instance()->postEvent(reconnect->session_tag, new ReplaceInProgressEvent(id));
582
+
583
+  // send the ReconnectLegEvent to the other leg
584
+  AmSessionContainer::instance()->postEvent(id, reconnect);
585
+
586
+  // remove the B leg from our B leg list
587
+  removeOtherLeg(id);
588
+
589
+  // commit suicide if our last B leg is stolen
590
+  if (other_legs.empty() && other_id.empty()) stopCall();
591
+}
592
+
593
+void CallLeg::onB2BReplaceInProgress(ReplaceInProgressEvent *e)
594
+{
595
+  for (vector<OtherLegInfo>::iterator i = other_legs.begin(); i != other_legs.end(); ++i) {
596
+    if (i->id.empty()) {
597
+      // replace the temporary (invalid) session with the correct one
598
+      i->id = e->dst_session;
599
+      return;
600
+    }
601
+  }
602
+}
603
+
604
+void CallLeg::disconnect(bool hold_remote)
605
+{
606
+  TRACE("disconnecting call leg %s from the other\n", getLocalTag().c_str());
607
+
608
+  switch (call_status) {
609
+    case Disconnecting:
610
+    case Disconnected:
611
+      DBG("trying to disconnect already disconnected (or disconnecting) call leg\n");
612
+      return;
613
+
614
+    case NoReply:
615
+    case Ringing:
616
+      WARN("trying to disconnect in not connected state, terminating not connected legs in advance (was it intended?)\n");
617
+      terminateNotConnectedLegs();
618
+      break;
619
+
620
+    case Connected:
621
+      resumeHeld(false); // TODO: do this as part of clearRtpReceiverRelay
622
+      clearRtpReceiverRelay(); // we can't stay connected (at media level) with the other leg
623
+      break; // this is OK
624
+  }
625
+
626
+  clear_other();
627
+  set_sip_relay_only(false); // we can't relay once disconnected
628
+
629
+  if (!hold_remote || isOnHold()) updateCallStatus(Disconnected);
630
+  else {
631
+    updateCallStatus(Disconnecting);
632
+    putOnHold();
633
+  }
634
+}
635
+
636
+void CallLeg::createHoldRequest(AmSdp &sdp)
637
+{
638
+  if (media_session) {
639
+    media_session->mute(a_leg);
640
+    media_session->createHoldRequest(sdp, a_leg, false /*mark_zero_connection*/, true /*mark_sendonly*/);
641
+  }
642
+  else {
643
+    sdp.clear();
644
+
645
+    // FIXME: versioning
646
+    sdp.version = 0;
647
+    sdp.origin.user = "sems";
648
+    //offer.origin.sessId = 1;
649
+    //offer.origin.sessV = 1;
650
+    sdp.sessionName = "sems";
651
+    sdp.conn.network = NT_IN;
652
+    sdp.conn.addrType = AT_V4;
653
+    sdp.conn.address = "0.0.0.0";
654
+
655
+    // FIXME: use media line from stored body?
656
+    sdp.media.push_back(SdpMedia());
657
+    SdpMedia &m = sdp.media.back();
658
+    m.type = MT_AUDIO;
659
+    m.transport = TP_RTPAVP;
660
+    m.send = false;
661
+    m.recv = false;
662
+    m.payloads.push_back(SdpPayload(0));
663
+  }
664
+}
665
+
666
+void CallLeg::putOnHold()
667
+{
668
+  hold_status = HoldRequested;
669
+
670
+  if (isOnHold()) {
671
+    handleHoldReply(true); // really?
672
+    return;
673
+  }
674
+
675
+  TRACE("putting remote on hold (%s)\n",
676
+      rtp_relay_mode == RTP_Direct ? "direct RTP" : "RTP relay");
677
+
678
+  AmSdp sdp;
679
+  createHoldRequest(sdp);
680
+  if (media_session) updateLocalSdp(sdp);
681
+
682
+  // generate re-INVITE with hold request
683
+  //reinvite(sdp, hold_request_cseq);
684
+  AmMimeBody body;
685
+  string body_str;
686
+  sdp.print(body_str);
687
+  body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
688
+  if (dlg.reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
689
+    ERROR("re-INVITE failed\n");
690
+    handleHoldReply(false);
691
+  }
692
+  else hold_request_cseq = dlg.cseq - 1;
693
+}
694
+
695
+void CallLeg::resumeHeld(bool send_reinvite)
696
+{
697
+  hold_status = ResumeRequested;
698
+
699
+  if (!isOnHold()) {
700
+    handleHoldReply(true); // really?
701
+    return;
702
+  }
703
+
704
+  TRACE("resume held remote (%s)\n",
705
+      rtp_relay_mode == RTP_Direct ? "direct RTP" : "RTP relay");
706
+
707
+  if (!send_reinvite) {
708
+    // probably another SDP in progress
709
+    handleHoldReply(true);
710
+    return;
711
+  }
712
+
713
+  if (media_session) {
714
+    AmSdp sdp;
715
+    if (sdp.parse((const char *)established_body.getPayload()) == 0)
716
+      updateLocalSdp(sdp);
717
+  }
718
+
719
+  if (dlg.reinvite("", &established_body, SIP_FLAGS_VERBATIM) != 0) {
720
+    ERROR("re-INVITE failed\n");
721
+    handleHoldReply(false);
722
+  }
723
+  else hold_request_cseq = dlg.cseq - 1;
724
+}
725
+
726
+void CallLeg::handleHoldReply(bool succeeded)
727
+{
728
+  switch (hold_status) {
729
+    case HoldRequested:
730
+      // ignore the result (if hold request is not accepted that's a pitty but
731
+      // we are Disconnected anyway)
732
+      if (call_status == Disconnecting) updateCallStatus(Disconnected);
733
+
734
+      if (succeeded) hold_status = OnHold; // remote put on hold successfully
735
+      break;
736
+
737
+    case ResumeRequested:
738
+      if (succeeded) {
739
+        hold_status = NotHeld; // call resumed successfully
740
+        if (media_session) media_session->unmute(a_leg);
741
+      }
742
+      break;
743
+
744
+    case NotHeld:
745
+    case OnHold:
746
+      //DBG("handling hold reply but hold was not requested\n");
747
+      break;
748
+  }
749
+}
750
+
751
+// was for caller only
752
+void CallLeg::onInvite(const AmSipRequest& req)
753
+{
754
+  // do not call AmB2BSession::onInvite(req); we changed the behavior
755
+  // this method is not called for re-INVITEs because once connected we are in
756
+  // sip_relay_only mode and the re-INVITEs are relayed instead of processing
757
+  // (see AmB2BSession::onSipRequest)
758
+
759
+  if (call_status == Disconnected) { // for initial INVITE only
760
+    est_invite_cseq = req.cseq; // remember initial CSeq
761
+    // initialize RTP relay
762
+
763
+    // relayed INVITE - we need to add the original INVITE to
764
+    // list of received (relayed) requests
765
+    recvd_req.insert(std::make_pair(req.cseq, req));
766
+
767
+    initial_sdp_stored = false;
768
+    if (rtp_relay_mode == RTP_Relay) {
769
+      const AmMimeBody* sdp_body = req.body.hasContentType(SIP_APPLICATION_SDP);
770
+      DBG("SDP %sfound in initial INVITE\n", sdp_body ? "": "not ");
771
+      if (sdp_body && (initial_sdp.parse((const char *)sdp_body->getPayload()) == 0)) {
772
+        DBG("storing remote SDP for later\n");
773
+        initial_sdp_stored = true;
774
+      }
775
+    }
776
+  }
777
+}
778
+
779
+void CallLeg::onSipRequest(const AmSipRequest& req)
780
+{
781
+  TRACE("%s: SIP request %d %s received in %s state\n",
782
+      getLocalTag().c_str(),
783
+      req.cseq, req.method.c_str(), callStatus2str(call_status));
784
+
785
+  // we need to handle cases if there is no other leg (for example call parking)
786
+  // Note that setting sip_relay_only to false in this case doesn't solve the
787
+  // problem because AmB2BSession always tries to relay the request into the
788
+  // other leg.
789
+  if (getCallStatus() == Disconnected) {
790
+    TRACE("handling request %s in disconnected state", req.method.c_str());
791
+    // this is not correct but what is?
792
+    AmSession::onSipRequest(req);
793
+    if (req.method == SIP_METH_BYE) stopCall(); // is this needed?
794
+  }
795
+  else AmB2BSession::onSipRequest(req);
796
+}
797
+
798
+void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status)
799
+{
800
+  TransMap::iterator t = relayed_req.find(reply.cseq);
801
+  bool relayed_request = (t != relayed_req.end());
802
+
803
+  TRACE("%s: SIP reply %d/%d %s (%s) received in %s state\n",
804
+      getLocalTag().c_str(),
805
+      reply.code, reply.cseq, reply.cseq_method.c_str(),
806
+      (relayed_request ? "to relayed request" : "to locally generated request"),
807
+      callStatus2str(call_status));
808
+
809
+  if ((reply.cseq == hold_request_cseq) && (reply.cseq_method == SIP_METH_INVITE)) {
810
+    // hold request replied - handle it
811
+    if (reply.code >= 200) { // handle final replies only
812
+      hold_request_cseq = 0;
813
+      handleHoldReply(reply.code < 300);
814
+    }
815
+
816
+    // we don't want to relay this reply to the other leg! => do the necessary
817
+    // stuff here (copy & paste from AmB2BSession::onSipReply)
818
+    if (rtp_relay_mode == RTP_Relay && reply.code < 300) {
819
+      const AmMimeBody *sdp_part = reply.body.hasContentType(SIP_APPLICATION_SDP);
820
+      if (sdp_part) {
821
+        AmSdp sdp;
822
+        if (sdp.parse((const char *)sdp_part->getPayload()) == 0) updateRemoteSdp(sdp);
823
+      }
824
+    }
825
+    AmSession::onSipReply(req, reply, old_dlg_status);
826
+    return;
827
+  }
828
+
829
+  AmB2BSession::onSipReply(req, reply, old_dlg_status);
830
+
831
+  // update internal state and call related callbacks based on received reply
832
+  // (i.e. B leg in case of initial INVITE)
833
+  if (reply.cseq == est_invite_cseq && reply.cseq_method == SIP_METH_INVITE &&
834
+    (call_status == NoReply || call_status == Ringing)) {
835
+    // reply to the initial request
836
+    if ((reply.code > 100) && (reply.code < 200)) {
837
+      if (((call_status == NoReply)) && (!reply.to_tag.empty()))
838
+        updateCallStatus(Ringing);
839
+    }
840
+    else if ((reply.code >= 200) && (reply.code < 300)) {
841
+      onCallConnected(reply);
842
+      updateCallStatus(Connected);
843
+    }
844
+    else if (reply.code >= 300) {
845
+      terminateLeg(); // commit suicide (don't let the master to kill us)
846
+    }
847
+  }
848
+}
849
+
850
+// was for caller only
851
+void CallLeg::onInvite2xx(const AmSipReply& reply)
852
+{
853
+  // We don't want to remember reply.cseq as est_invite_cseq, do we? It was in
854
+  // AmB2BCallerSession but we already have initial INVITE cseq remembered and
855
+  // we don't need to change it to last reINVITE one, right? Otherwise we should
856
+  // remember UPDATE cseq as well because SDP may change by it as well (used
857
+  // when handling B2BSipReply in AmB2BSession to check if reINVITE should be
858
+  // sent).
859
+  // 
860
+  // est_invite_cseq = reply.cseq;
861
+
862
+  // we don't want to handle the 2xx using AmSession so the following may be
863
+  // unwanted for us:
864
+  // 
865
+  AmB2BSession::onInvite2xx(reply);
866
+}
867
+
868
+void CallLeg::onCancel(const AmSipRequest& req)
869
+{
870
+  // initial INVITE handling
871
+  if ((call_status == Ringing) || (call_status == NoReply)) {
872
+    if (a_leg) {
873
+      // terminate whole B2B call if the caller receives CANCEL
874
+      stopCall();
875
+    }
876
+    // else { } ... ignore for B leg
877
+  }
878
+  // FIXME: was it really expected to terminate the call for CANCELed re-INVITEs
879
+  // (in both directions) as well?
880
+}
881
+
882
+void CallLeg::terminateLeg()
883
+{
884
+  AmB2BSession::terminateLeg();
885
+  onCallStopped();
886
+}
887
+
888
+// was for caller only
889
+void CallLeg::onRemoteDisappeared(const AmSipReply& reply) 
890
+{
891
+  if (call_status == Connected) {
892
+    // only in case we are really connected (called on timeout or 481 from the remote)
893
+
894
+    DBG("remote unreachable, ending B2BUA call\n");
895
+    clearRtpReceiverRelay(); // FIXME: shouldn't be cleared in AmB2BSession as well?
896
+    AmB2BSession::onRemoteDisappeared(reply); // terminates the other leg
897
+    updateCallStatus(Disconnected);
898
+  }
899
+}
900
+
901
+// was for caller only
902
+void CallLeg::onBye(const AmSipRequest& req)
903
+{
904
+  clearRtpReceiverRelay(); // FIXME: shouldn't be cleared in AmB2BSession as well?
905
+  AmB2BSession::onBye(req);
906
+}
907
+
908
+void CallLeg::addNewCallee(CallLeg *callee, ConnectLegEvent *e, AmB2BSession::RTPRelayMode mode)
909
+{
910
+  OtherLegInfo b;
911
+  b.id = callee->getLocalTag();
912
+
913
+  callee->setRtpRelayMode(mode);
914
+  if ((mode == RTP_Relay)) {
915
+    // do not initialise the media session with A leg to avoid unnecessary A leg
916
+    // RTP stream creation in every B leg's media session
917
+    if (a_leg) b.media_session = new AmB2BMedia(NULL, callee);
918
+    else b.media_session = new AmB2BMedia(callee, NULL);
919
+    b.media_session->addReference(); // new reference for me
920
+    callee->setMediaSession(b.media_session);
921
+  }
922
+  else b.media_session = NULL;
923
+  other_legs.push_back(b);
924
+
925
+  if (AmConfig::LogSessions) {
926
+    TRACE("Starting B2B callee session %s\n",
927
+	 callee->getLocalTag().c_str()/*, invite_req.cmd.c_str()*/);
928
+  }
929
+
930
+  AmSipDialog& callee_dlg = callee->dlg;
931
+  MONITORING_LOG4(b.id.c_str(),
932
+		  "dir",  "out",
933
+		  "from", callee_dlg.local_party.c_str(),
934
+		  "to",   callee_dlg.remote_party.c_str(),
935
+		  "ruri", callee_dlg.remote_uri.c_str());
936
+
937
+  callee->start();
938
+
939
+  AmSessionContainer* sess_cont = AmSessionContainer::instance();
940
+  sess_cont->addSession(b.id, callee);
941
+
942
+  // generate connect event to the newly added leg
943
+  // Warning: correct callee's role must be already set (in constructor or so)
944
+  TRACE("relaying connect leg event to the new leg\n");
945
+  // other stuff than relayed INVITE should be set directly when creating callee
946
+  // (remote_uri, remote_party is not propagated and thus B2BConnectEvent is not
947
+  // used because it would just overwrite already set things. Note that in many
948
+  // classes derived from AmB2BCaller[Callee]Session was a lot of things set
949
+  // explicitly)
950
+  AmSessionContainer::instance()->postEvent(b.id, e);
951
+
952
+  if (call_status == Disconnected) updateCallStatus(NoReply);
953
+}
954
+
955
+void CallLeg::updateCallStatus(CallStatus new_status)
956
+{
957
+  if (new_status == Connected)
958
+    TRACE("%s leg %s changing status from %s to %s with %s\n",
959
+        a_leg ? "A" : "B",
960
+        getLocalTag().c_str(),
961
+        callStatus2str(call_status),
962
+        callStatus2str(new_status),
963
+        other_id.c_str());
964
+  else
965
+    TRACE("%s leg %s changing status from %s to %s\n",
966
+        a_leg ? "A" : "B",
967
+        getLocalTag().c_str(),
968
+        callStatus2str(call_status),
969
+        callStatus2str(new_status));
970
+
971
+  call_status = new_status;
972
+  onCallStatusChange();
973
+}
974
+
975
+void CallLeg::addExistingCallee(const string &session_tag, ReconnectLegEvent *ev)
976
+{
977
+  // add existing session as our B leg
978
+
979
+  OtherLegInfo b;
980
+  b.id = session_tag;
981
+  if ((rtp_relay_mode == RTP_Relay)) {
982
+    // do not initialise the media session with A leg to avoid unnecessary A leg
983
+    // RTP stream creation in every B leg's media session
984
+    b.media_session = new AmB2BMedia(NULL, NULL);
985
+    b.media_session->addReference(); // new reference for me
986
+  }
987
+  else b.media_session = NULL;
988
+
989
+  // generate connect event to the newly added leg
990
+  TRACE("relaying re-connect leg event to the B leg\n");
991
+  ev->setMedia(b.media_session);
992
+  // TODO: what about the RTP relay and other settings? send them as well?
993
+  if (!AmSessionContainer::instance()->postEvent(session_tag, ev)) {
994
+    // session doesn't exist - can't connect
995
+    INFO("the B leg to connect to (%s) doesn't exist\n", session_tag.c_str());
996
+    if (b.media_session) delete b.media_session;
997
+    return;
998
+  }
999
+
1000
+  other_legs.push_back(b);
1001
+  if (call_status == Disconnected) updateCallStatus(NoReply);
1002
+}
1003
+
1004
+void CallLeg::addCallee(const string &session_tag, const AmSipRequest &relayed_invite)
1005
+{
1006
+  addExistingCallee(session_tag, new ReconnectLegEvent(getLocalTag(), relayed_invite));
1007
+}
1008
+
1009
+void CallLeg::replaceExistingLeg(const string &session_tag, const AmSipRequest &relayed_invite)
1010
+{
1011
+  // add existing session as our B leg
1012
+
1013
+  OtherLegInfo b;
1014
+  b.id.clear(); // this is an invalid local tag (temporarily)
1015
+  if ((rtp_relay_mode == RTP_Relay)) {
1016
+    // let the other leg to set its part, we will set our once connected
1017
+    b.media_session = new AmB2BMedia(NULL, NULL);
1018
+    b.media_session->addReference(); // new reference for me
1019
+  }
1020
+  else b.media_session = NULL;
1021
+
1022
+  ReplaceLegEvent *ev = new ReplaceLegEvent(getLocalTag(), relayed_invite, b.media_session);
1023
+  // TODO: what about the RTP relay and other settings? send them as well?
1024
+  if (!AmSessionContainer::instance()->postEvent(session_tag, ev)) {
1025
+    // session doesn't exist - can't connect
1026
+    INFO("the call leg to be replaced (%s) doesn't exist\n", session_tag.c_str());
1027
+    if (b.media_session) delete b.media_session;
1028
+    return;
1029
+  }
1030
+
1031
+  other_legs.push_back(b);
1032
+  if (call_status == Disconnected) updateCallStatus(NoReply); // we are something like connected to another leg
1033
+}
1034
+
1035
+void CallLeg::replaceExistingLeg(const string &session_tag, const string &hdrs)
1036
+{
1037
+  // add existing session as our B leg
1038
+
1039
+  OtherLegInfo b;
1040
+  b.id.clear(); // this is an invalid local tag (temporarily)
1041
+  if ((rtp_relay_mode == RTP_Relay)) {
1042
+    // let the other leg to set its part, we will set our once connected
1043
+    b.media_session = new AmB2BMedia(NULL, NULL);
1044
+    b.media_session->addReference(); // new reference for me
1045
+  }
1046
+  else b.media_session = NULL;
1047
+
1048
+  ReconnectLegEvent *rev = new ReconnectLegEvent(a_leg ? ReconnectLegEvent::B : ReconnectLegEvent::A, getLocalTag(), hdrs, established_body);
1049
+  rev->setMedia(b.media_session);
1050
+  ReplaceLegEvent *ev = new ReplaceLegEvent(getLocalTag(), rev);
1051
+  // TODO: what about the RTP relay and other settings? send them as well?
1052
+  if (!AmSessionContainer::instance()->postEvent(session_tag, ev)) {
1053
+    // session doesn't exist - can't connect
1054
+    INFO("the call leg to be replaced (%s) doesn't exist\n", session_tag.c_str());
1055
+    if (b.media_session) delete b.media_session;
1056
+    return;
1057
+  }
1058
+
1059
+  other_legs.push_back(b);
1060
+  if (call_status == Disconnected) updateCallStatus(NoReply); // we are something like connected to another leg
1061
+}
1062
+
1063
+void CallLeg::clear_other()
1064
+{
1065
+  removeOtherLeg(other_id);
1066
+  AmB2BSession::clear_other();
1067
+}
1068
+
1069
+void CallLeg::stopCall() {
1070
+  terminateNotConnectedLegs();
1071
+  terminateOtherLeg();
1072
+  terminateLeg();
1073
+}
0 1074
new file mode 100644
... ...
@@ -0,0 +1,360 @@
1
+#ifndef __AMB2BCALL_H
2
+#define __AMB2BCALL_H
3
+
4
+#include "AmB2BSession.h"
5
+#include "AmSessionContainer.h"
6
+
7
+// TODO: global event numbering
8
+enum {
9
+  ConnectLeg = B2BMsgBody + 16,
10
+  ReconnectLeg,
11
+  ReplaceLeg,
12
+  ReplaceInProgress,
13
+  DisconnectLeg
14
+};
15
+
16
+#define LAST_B2B_CALL_LEG_EVENT_ID DisconnectLeg
17
+
18
+struct ConnectLegEvent: public B2BEvent
19
+{
20
+  AmMimeBody body;
21
+  string hdrs;
22
+
23
+  unsigned int r_cseq;
24
+  bool relayed_invite;
25
+
26
+  // constructor from relayed INVITE request
27
+  ConnectLegEvent(const AmSipRequest &_relayed_invite):
28
+    B2BEvent(ConnectLeg),
29
+    body(_relayed_invite.body),
30
+    hdrs(_relayed_invite.hdrs),
31
+    r_cseq(_relayed_invite.cseq),
32
+    relayed_invite(true)
33
+  { }
34
+
35
+  // constructor from generated INVITE (for example blind call transfer)
36
+  ConnectLegEvent(const string &_hdrs, const AmMimeBody &_body):
37
+    B2BEvent(ConnectLeg),
38
+    body(_body),
39
+    hdrs(_hdrs),
40
+    r_cseq(0),
41
+    relayed_invite(false)
42
+  { }
43
+};
44
+
45
+/** B2B event which sends another event back if it was or was not processed.
46
+ * (note that the back events need to be created in advance because we can not
47
+ * use overriden virtual methods in destructor (which is the only place which
48
+ * will be called for sure) */
49
+struct ReliableB2BEvent: public B2BEvent
50
+{
51
+  private:
52
+    bool processed;
53
+
54
+    B2BEvent *unprocessed_reply; //< reply to be sent back if the original event was not processed
55
+    B2BEvent *processed_reply; //< event sent back if the original event was processed
56
+    string sender; // sender will be filled when sending the event out
57
+
58
+  public:
59
+
60
+    ReliableB2BEvent(int ev_id, B2BEvent *_processed, B2BEvent *_unprocessed):
61
+      B2BEvent(ev_id), processed(false), processed_reply(_processed), unprocessed_reply(_unprocessed) { }
62
+    void markAsProcessed() { processed = true; }
63
+    void setSender(const string &tag) { sender = tag; }
64
+    virtual ~ReliableB2BEvent();
65
+};
66
+
67
+struct ReconnectLegEvent: public ReliableB2BEvent
68
+{
69
+  AmMimeBody body;
70
+  string hdrs;
71
+
72
+  unsigned int r_cseq;
73
+  bool relayed_invite;
74
+
75
+  AmB2BMedia *media; // avoid direct access to this
76
+  string session_tag;
77
+  enum Role { A, B } role; // reconnect as A or B leg
78
+
79
+  void setMedia(AmB2BMedia *m) { media = m; if (media) media->addReference(); }
80
+
81
+  ReconnectLegEvent(const string &tag, const AmSipRequest &relayed_invite):
82
+    ReliableB2BEvent(ReconnectLeg, NULL, new B2BEvent(B2BTerminateLeg) /* TODO: choose a better one */),
83
+    body(relayed_invite.body),
84
+    hdrs(relayed_invite.hdrs),
85
+    r_cseq(relayed_invite.cseq),
86
+    relayed_invite(true),
87
+    media(NULL),
88
+    session_tag(tag),
89
+    role(B) // we have relayed_invite (only in A leg) thus reconnect as regular B leg
90
+  { setSender(tag); }
91
+
92
+  ReconnectLegEvent(Role _role, const string &tag, const string &_hdrs, const AmMimeBody &_body):
93
+    ReliableB2BEvent(ReconnectLeg, NULL, new B2BEvent(B2BTerminateLeg) /* TODO: choose a better one */),
94
+    body(_body),
95
+    hdrs(_hdrs),
96
+    r_cseq(0),
97
+    relayed_invite(false),
98
+    media(NULL),
99
+    session_tag(tag),
100
+    role(_role)
101
+  { setSender(tag); }
102
+
103
+  virtual ~ReconnectLegEvent() { if (media && media->releaseReference()) delete media; }
104
+};
105
+
106
+
107
+/** Call leg receiving ReplaceLegEvent should replace itself with call leg from
108
+ * the event parameters. (it terminates itself and forwards ReconnectLegEvent to
109
+ * the call leg identified by other_id) */
110
+struct ReplaceLegEvent: public ReliableB2BEvent
111
+{
112
+  private:
113
+    ReconnectLegEvent *ev;
114
+
115
+  public:
116
+    ReplaceLegEvent(const string &tag, const AmSipRequest &relayed_invite, AmB2BMedia *m):
117
+      ReliableB2BEvent(ReplaceLeg, NULL, new B2BEvent(B2BTerminateLeg))
118
+    { ev = new ReconnectLegEvent(tag, relayed_invite); ev->setMedia(m); setSender(tag); }
119
+
120
+    ReplaceLegEvent(const string &tag, ReconnectLegEvent *e):
121
+      ReliableB2BEvent(ReplaceLeg, NULL, new B2BEvent(B2BTerminateLeg)),
122
+      ev(e)
123
+    { setSender(tag); }
124
+
125
+    ReconnectLegEvent *getReconnectEvent() { ReconnectLegEvent *e = ev; ev = NULL; return e; }
126
+    virtual ~ReplaceLegEvent() { if (ev) delete ev; }
127
+};
128
+
129
+struct ReplaceInProgressEvent: public B2BEvent
130
+{
131
+  string dst_session; // session to be connected to
132
+
133
+  ReplaceInProgressEvent(const string &_dst_session):
134
+      B2BEvent(ReplaceInProgress), dst_session(_dst_session) { }
135
+};
136
+
137
+struct DisconnectLegEvent: public B2BEvent
138
+{
139
+  bool put_remote_on_hold;
140
+  DisconnectLegEvent(bool _put_remote_on_hold): B2BEvent(DisconnectLeg), put_remote_on_hold(_put_remote_on_hold) { }
141
+};
142
+
143
+/** composed AmB2BCalleeSession & AmB2BCallerSession
144
+ * represents indepenedently A or B leg of a call,
145
+ * old clases left for compatibility
146
+ *
147
+ * Notes:
148
+ *
149
+ *  - we use the relayEvent implementation from AmB2BSession - it can happen
150
+ *  that we have no peer (i.e. we are a standalone call leg, for example parked
151
+ *  one) => do not create other call leg automatically
152
+ *
153
+ *  - we use onSystemEvent implementation from AmB2BSession - the other leg
154
+ *  receives and handles the same shutdown event, right?
155
+ *
156
+ *  - the role (A/B leg) can be changed during the CallLeg life and is
157
+ *  understood just this way
158
+ *
159
+ *    "A leg is the call leg created when handling initial INVITE request"
160
+ *
161
+ *  It is used
162
+ *    - as identification what part of media session is affected by operation
163
+ *    - when CANCEL is being processed only the CANCEL of initial INVITE is
164
+ *    important so it is explicitly verified that it is handled in A leg)
165
+ *
166
+ *  In other words - the B leg can create new 'b-like-legs' the same way the
167
+ *  A leg does.
168
+ * */
169
+class CallLeg: public AmB2BSession
170
+{
171
+  public:
172
+    /** B2B call status.
173
+     *
174
+     * This status need not to be related directly to SIP dialog status in
175
+     * appropriate call legs - for example the B2B call status can be
176
+     * "Connected" though the legs have received BYE replies. */
177
+    enum CallStatus {
178
+      Disconnected, //< there is no other call leg we are connected to
179
+      NoReply,      //< there is at least one call leg we are connected to but without any response
180
+      Ringing,      //< this leg or one of legs we are connected to rings
181
+      Connected,    //< there is exactly one call leg we are connected to, in this case AmB2BSession::other_id holds the other leg id
182
+      Disconnecting //< we were connected and now going to be disconnected (waiting for reINVITE reply for example)
183
+    };
184
+
185
+  private:
186
+
187
+    CallStatus call_status; //< status of the call (replaces callee's status)
188
+
189
+    AmSdp initial_sdp;
190
+    bool initial_sdp_stored;
191
+
192
+    /** information needed in A leg for a B leg */
193
+    struct OtherLegInfo {
194
+      /** local tag of the B leg */
195
+      string id;
196
+
197
+      /** once the B leg gets connected to the A leg A leg starts to use its
198
+       * corresponding media_session created when the B leg is added to the list
199
+       * of B legs */
200
+      AmB2BMedia *media_session;
201
+
202
+      void releaseMediaSession() { if (media_session) { if (media_session->releaseReference()) delete media_session; media_session = NULL; } }
203
+    };
204
+
205
+    /** List of legs which can be connected to this leg, it is valid for A leg until first
206
+     * 2xx response which moves the A leg to Connected state and terminates all
207
+     * other B legs.
208
+     *
209
+     * Please note that the A/B role may change during the call leg life. For
210
+     * example when a B leg is parked and then 'rings back on timer' it becomes
211
+     * A leg, i.e. it creates new B leg(s) for itself. */
212
+    std::vector<OtherLegInfo> other_legs;
213
+
214
+    unsigned hold_request_cseq; // CSeq of a request generated by us to hold the call
215
+    enum { NotHeld, OnHold, HoldRequested, ResumeRequested } hold_status; // remote is put on hold by us
216
+
217
+    // methods just for make this stuff more readable, not intended to be
218
+    // overriden, override onB2BEvent instead!
219
+    void onB2BReply(B2BSipReplyEvent *e);
220
+    void onB2BConnect(ConnectLegEvent *e);
221
+    void onB2BReconnect(ReconnectLegEvent *e);
222
+    void onB2BReplace(ReplaceLegEvent *e);
223
+    void onB2BReplaceInProgress(ReplaceInProgressEvent *e);
224
+
225
+    int relaySipReply(AmSipReply &reply);
226
+
227
+    /** terminate all other B legs than the connected one (should not be used
228
+     * directly by successors, right?) */
229
+    void terminateNotConnectedLegs();
230
+
231
+    /** remove given leg from the list of other legs */
232
+    void removeOtherLeg(const string &id);
233
+
234
+    void updateCallStatus(CallStatus new_status);
235
+
236
+    //////////////////////////////////////////////////////////////////////
237
+    // callbacks (intended to be redefined in successors but should not be
238
+    // called by them directly)
239
+
240
+    /* handler called when call status changes */
241
+    virtual void onCallStatusChange() { }
242
+
243
+    /** handler called when the second leg is connected (FIXME: this is a hack,
244
+     * use this method in SBCCallLeg only) */
245
+    virtual void onCallConnected(const AmSipReply& reply) { }
246
+
247
+    /** handler called when call is stopped (FIXME: this is a hack, use this
248
+     * method in SBCCallLeg only)  */
249
+    virtual void onCallStopped() { }
250
+
251
+    /** Method called if given B leg couldn't establish the call (refused with
252
+     * failure response)
253
+     *
254
+     * Redefine to implement serial fork or handle redirect. */
255
+    virtual void onBLegRefused(const AmSipReply& reply) { }
256
+
257
+    /** add newly created callee with prepared ConnectLegEvent */
258
+    void addNewCallee(CallLeg *callee, ConnectLegEvent *e) { addNewCallee(callee, e, rtp_relay_mode); }
259
+
260
+    /** add a newly created calee with prepared ConnectLegEvent and forced RTP
261
+     * relay mode (this is a hack to work around allowed temporary changes of
262
+     * RTP relay mode used for music on hold)
263
+     * FIXME: throw this out once MoH will use another method than temporary RTP
264
+     * Relay mode change */
265
+    void addNewCallee(CallLeg *callee, ConnectLegEvent *e, AmB2BSession::RTPRelayMode mode);
266
+
267
+    void addExistingCallee(const string &session_tag, ReconnectLegEvent *e);
268
+
269
+    /** Clears other leg, eventually removes it from the list of other legs if
270
+     * it is there. It neither updates call state nor sip_relay_only flag! */
271
+    virtual void clear_other();
272
+    virtual void terminateLeg();
273
+    virtual void terminateOtherLeg();
274
+
275
+  protected:
276
+
277
+    // functions offered to successors
278
+
279
+    CallStatus getCallStatus() { return call_status; }
280
+
281
+    // @see AmSession
282
+    virtual void onInvite(const AmSipRequest& req);
283
+    virtual void onInvite2xx(const AmSipReply& reply);
284
+    virtual void onCancel(const AmSipRequest& req);
285
+    virtual void onBye(const AmSipRequest& req);
286
+    virtual void onRemoteDisappeared(const AmSipReply& reply);
287
+
288
+    virtual void onSipRequest(const AmSipRequest& req);
289
+    virtual void onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status);
290
+
291
+    /* callback method called when hold/resume request is replied */
292
+    virtual void handleHoldReply(bool succeeded);
293
+
294
+    /* called to create SDP of hold request
295
+     * (note that resume request always uses established_body stored before) */
296
+    virtual void createHoldRequest(AmSdp &sdp);
297
+
298
+  public:
299
+    virtual void onB2BEvent(B2BEvent* ev);
300
+
301
+    /** does all the job around disconnecting from the other leg (updates call
302
+     * status, disconnects RTP from the other, puts remote on hold (if
303
+     * requested)
304
+     *
305
+     * The other leg is not affected by disconnect - it is neither terminated
306
+     * nor informed about the peer disconnection. */
307
+    virtual void disconnect(bool hold_remote);
308
+
309
+    /** Terminate the whole B2B call (if there is no other leg only this one is
310
+     * stopped). */
311
+    virtual void stopCall();
312
+
313
+
314
+    /** Put remote party on hold (may change RTP relay mode!). Note that this
315
+     * task is asynchronous so the remote is most probably NOT 'on hold' after
316
+     * calling this method
317
+     *
318
+     * This method calls handleHoldReply(false) directly if an error occurs. */
319
+    virtual void putOnHold();
320
+
321
+    /** resume call if the remote party is on hold */
322
+    virtual void resumeHeld(bool send_reinvite);
323
+
324
+    virtual bool isOnHold() { return hold_status == OnHold; }