Browse code

Merge branch kubartv/error_recovery

Conflicts:
core/AmBasicSipDialog.cpp

Václav Kubart authored on 11/04/2014 11:03:40
Showing 10 changed files
... ...
@@ -260,6 +260,12 @@ CallLeg::~CallLeg()
260 260
   for (vector<OtherLegInfo>::iterator i = other_legs.begin(); i != other_legs.end(); ++i) {
261 261
     i->releaseMediaSession();
262 262
   }
263
+
264
+  while (!pending_updates.empty()) {
265
+    SessionUpdate *u = pending_updates.front();
266
+    pending_updates.pop_front();
267
+    delete u;
268
+  }
263 269
 }
264 270
 
265 271
 void CallLeg::terminateOtherLeg()
... ...
@@ -355,7 +361,7 @@ void CallLeg::onB2BEvent(B2BEvent* ev)
355 361
       }
356 362
       break;
357 363
 
358
-    case ResumeHeld:
364
+    case ResumeHeldLeg:
359 365
       {
360 366
         ResumeHeldEvent *e = dynamic_cast<ResumeHeldEvent*>(ev);
361 367
         if (e) resumeHeld();
... ...
@@ -369,6 +375,11 @@ void CallLeg::onB2BEvent(B2BEvent* ev)
369 375
       }
370 376
       break;
371 377
 
378
+      case ApplyPendingUpdatesEventId:
379
+        if (dynamic_cast<ApplyPendingUpdatesEvent*>(ev)) applyPendingUpdate();
380
+        break;
381
+
382
+
372 383
     case B2BSipRequest:
373 384
       if (!sip_relay_only) {
374 385
         // disable forwarding of relayed request if we are not connected [yet]
... ...
@@ -692,12 +703,8 @@ void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
692 703
 		  "to", dlg->getRemoteParty().c_str(),
693 704
 		  "ruri", dlg->getRemoteUri().c_str());
694 705
 
695
-  if (invite) {
696
-    // there is pending INVITE, replied just above but we need to wait for ACK
697
-    // before sending re-INVITE with real body
698
-    queueReinvite(ev->hdrs, ev->body, /* establishing = */ true, ev->relayed_invite, ev->r_cseq);  
699
-  }
700
-  else reinvite(ev->hdrs, ev->body, ev->relayed_invite, ev->r_cseq, true);
706
+  updateSession(new Reinvite(ev->hdrs, ev->body,
707
+        /* establishing = */ true, ev->relayed_invite, ev->r_cseq));
701 708
 }
702 709
 
703 710
 void CallLeg::onB2BReplace(ReplaceLegEvent *e)
... ...
@@ -799,9 +806,9 @@ static void sdp2body(const AmSdp &sdp, AmMimeBody &body)
799 806
   else body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
800 807
 }
801 808
 
802
-void CallLeg::putOnHold()
809
+int CallLeg::putOnHoldImpl()
803 810
 {
804
-  if (on_hold) return;
811
+  if (on_hold) return -1; // no request went out
805 812
 
806 813
   TRACE("putting remote on hold\n");
807 814
   hold = HoldRequested;
... ...
@@ -814,20 +821,17 @@ void CallLeg::putOnHold()
814 821
 
815 822
   AmMimeBody body;
816 823
   sdp2body(sdp, body);
817
-  if (dlg->getUACInvTransPending()) {
818
-    // there is pending INVITE, add reinvite to waiting requests
819
-    DBG("INVITE pending, queueing hold Re-Invite\n");
820
-    queueReinvite("", body);
821
-  } else if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
824
+  if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
822 825
     ERROR("re-INVITE failed\n");
823 826
     offerRejected();
827
+    return -1;
824 828
   }
825
-  //else hold_request_cseq = dlg->cseq - 1;
829
+  return dlg->cseq - 1;
826 830
 }
827 831
 
828
-void CallLeg::resumeHeld(/*bool send_reinvite*/)
832
+int CallLeg::resumeHeldImpl()
829 833
 {
830
-  if (!on_hold) return;
834
+  if (!on_hold) return -1;
831 835
 
832 836
   try {
833 837
     TRACE("resume held remote\n");
... ...
@@ -840,24 +844,22 @@ void CallLeg::resumeHeld(/*bool send_reinvite*/)
840 844
     if (sdp.media.empty()) {
841 845
       ERROR("invalid un-hold SDP, can't unhold\n");
842 846
       offerRejected();
843
-      return;
847
+      return -1;
844 848
     }
845 849
     updateLocalSdp(sdp);
846 850
 
847 851
     AmMimeBody body(established_body);
848 852
     sdp2body(sdp, body);
849
-    if (dlg->getUACInvTransPending()) {
850
-      // there is a pending INVITE, add reinvite to waiting requests
851
-      DBG("INVITE pending, queueing un-hold Re-Invite\n");
852
-      queueReinvite("", body);
853
-    } else if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
853
+    if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) {
854 854
       ERROR("re-INVITE failed\n");
855 855
       offerRejected();
856
+      return -1;
856 857
     }
857
-    //else hold_request_cseq = dlg->cseq - 1;
858
+    return dlg->cseq - 1;
858 859
   }
859 860
   catch (...) {
860 861
     offerRejected();
862
+    return -1;
861 863
   }
862 864
 }
863 865
 
... ...
@@ -923,8 +925,7 @@ void CallLeg::onSipRequest(const AmSipRequest& req)
923 925
     // handle reINVITEs within B2B call with no other leg
924 926
     if (req.method == SIP_METH_INVITE && dlg->getStatus() == AmBasicSipDialog::Connected) {
925 927
       try {
926
-        AmSession::onInvite(req);
927
-        //or dlg->reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR); ?
928
+        dlg->reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
928 929
       }
929 930
       catch(...) {
930 931
         ERROR("exception when handling INVITE in disconnected state");
... ...
@@ -949,13 +950,6 @@ void CallLeg::onSipRequest(const AmSipRequest& req)
949 950
     else
950 951
       AmB2BSession::onSipRequest(req);
951 952
   }
952
-
953
-  if (req.method == SIP_METH_ACK && !pending_reinvites.empty()) {
954
-    TRACE("ACK received, we can send a queued re-INVITE\n");
955
-    PendingReinvite p = pending_reinvites.front();
956
-    pending_reinvites.pop();
957
-    reinvite(p.hdrs, p.body, p.relayed_invite, p.r_cseq, p.establishing);
958
-  }
959 953
 }
960 954
 
961 955
 void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSipDialog::Status old_dlg_status)
... ...
@@ -986,6 +980,22 @@ void CallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply, AmSip
986 980
     return;
987 981
   }
988 982
 #endif
983
+  if (reply.code >= 300 && reply.cseq_method == SIP_METH_INVITE) offerRejected();
984
+
985
+  // handle final replies of session updates in progress
986
+  if (!pending_updates.empty() && reply.code >= 200 && pending_updates.front()->hasCSeq(reply.cseq)) {
987
+    if (reply.code == 491) {
988
+      pending_updates.front()->reset();
989
+      double t = get491RetryTime();
990
+      pending_updates_timer.start(getLocalTag(), t);
991
+      TRACE("planning to retry update operation in %gs", t);
992
+    }
993
+    else {
994
+      // TODO: 503, ...
995
+      delete pending_updates.front();
996
+      pending_updates.pop_front();
997
+    }
998
+  }
989 999
 
990 1000
   AmB2BSession::onSipReply(req, reply, old_dlg_status);
991 1001
 
... ...
@@ -1313,17 +1323,6 @@ void CallLeg::replaceExistingLeg(const string &session_tag, const string &hdrs)
1313 1323
   if (call_status == Disconnected) updateCallStatus(NoReply); // we are something like connected to another leg
1314 1324
 }
1315 1325
 
1316
-void CallLeg::queueReinvite(const string& hdrs, const AmMimeBody& body, bool establishing,
1317
-			    bool relayed_invite, unsigned int r_cseq) {
1318
-  PendingReinvite p;
1319
-  p.hdrs = hdrs;
1320
-  p.body = body;
1321
-  p.relayed_invite = relayed_invite;
1322
-  p.r_cseq = r_cseq;
1323
-  p.establishing = establishing;
1324
-  pending_reinvites.push(p);
1325
-}
1326
-
1327 1326
 void CallLeg::clear_other()
1328 1327
 {
1329 1328
   removeOtherLeg(getOtherId());
... ...
@@ -1522,7 +1521,7 @@ void CallLeg::acceptPendingInvite(AmSipRequest *invite)
1522 1521
   if (getCallStatus() != Connected) updateCallStatus(Connected);
1523 1522
 }
1524 1523
 
1525
-void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing)
1524
+int CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing)
1526 1525
 {
1527 1526
   int res;
1528 1527
   try {
... ...
@@ -1539,7 +1538,7 @@ void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed,
1539 1538
 
1540 1539
     DBG("sending re-INVITE failed, terminating the call\n");
1541 1540
     stopCall(StatusChangeCause::InternalError);
1542
-    return;
1541
+    return -1;
1543 1542
   }
1544 1543
 
1545 1544
   if (relayed) {
... ...
@@ -1557,6 +1556,7 @@ void CallLeg::reinvite(const string &hdrs, const AmMimeBody &body, bool relayed,
1557 1556
     // save CSeq of establishing INVITE
1558 1557
     est_invite_cseq = dlg->cseq - 1;
1559 1558
   }
1559
+  return dlg->cseq - 1;
1560 1560
 }
1561 1561
 
1562 1562
 void CallLeg::adjustOffer(AmSdp &sdp)
... ...
@@ -1615,11 +1615,13 @@ void CallLeg::updateLocalSdp(AmSdp &sdp)
1615 1615
 
1616 1616
 void CallLeg::offerRejected()
1617 1617
 {
1618
+  TRACE("%s: offer rejected! (hold status: %d)", getLocalTag().c_str(), hold);
1618 1619
   switch (hold) {
1619 1620
     case HoldRequested: holdRejected(); break;
1620 1621
     case ResumeRequested: resumeRejected(); break;
1621 1622
     case PreserveHoldStatus: break;
1622 1623
   }
1624
+  hold = PreserveHoldStatus;
1623 1625
 }
1624 1626
 
1625 1627
 void CallLeg::createResumeRequest(AmSdp &sdp)
... ...
@@ -1664,3 +1666,72 @@ int CallLeg::onSdpCompleted(const AmSdp& offer, const AmSdp& answer)
1664 1666
   hold = PreserveHoldStatus;
1665 1667
   return AmB2BSession::onSdpCompleted(offer, answer);
1666 1668
 }
1669
+
1670
+void CallLeg::applyPendingUpdate()
1671
+{
1672
+  TRACE("going to apply pending updates");
1673
+
1674
+  if (pending_updates.empty()) return;
1675
+
1676
+  if (!canUpdateSession()) {
1677
+    TRACE("can't apply pending updates now");
1678
+    return;
1679
+  }
1680
+
1681
+  TRACE("applying pending updates");
1682
+
1683
+  do {
1684
+    SessionUpdate *u = pending_updates.front();
1685
+    u->apply(this);
1686
+    if (u->hasCSeq()) {
1687
+      // SIP transaction started, wait for finishing it
1688
+      break;
1689
+    }
1690
+    else {
1691
+      // the update operation hasn't started a SIP transaction so it can be
1692
+      // understood as finished
1693
+      pending_updates.pop_front();
1694
+      delete u;
1695
+    }
1696
+  } while (!pending_updates.empty());
1697
+}
1698
+
1699
+void CallLeg::onTransFinished()
1700
+{
1701
+  TRACE("UAC/UAS transaction finished");
1702
+  AmB2BSession::onTransFinished();
1703
+
1704
+  if (pending_updates.empty() || !canUpdateSession()) return; // there is nothing we can do now
1705
+
1706
+  if (pending_updates_timer.started()) {
1707
+    TRACE("UAC/UAS transaction finished, but waiting for planned updates");
1708
+    return; // it is planned to apply the updates later on
1709
+  }
1710
+
1711
+  TRACE("UAC/UAS transaction finished, try to apply pending updates");
1712
+  AmSessionContainer::instance()->postEvent(getLocalTag(), new ApplyPendingUpdatesEvent());
1713
+}
1714
+
1715
+void CallLeg::updateSession(SessionUpdate *u)
1716
+{
1717
+  if (!canUpdateSession() || !pending_updates.empty()) {
1718
+    TRACE("planning session update for later");
1719
+    pending_updates.push_back(u);
1720
+  }
1721
+  else {
1722
+    u->apply(this);
1723
+
1724
+    if (u->hasCSeq()) pending_updates.push_back(u); // store for failover
1725
+    else delete u; // finished
1726
+  }
1727
+}
1728
+
1729
+void CallLeg::putOnHold()
1730
+{
1731
+  updateSession(new PutOnHold());
1732
+}
1733
+
1734
+void CallLeg::resumeHeld()
1735
+{
1736
+  updateSession(new ResumeHeld());
1737
+}
... ...
@@ -29,18 +29,10 @@
29 29
 #include "AmB2BSession.h"
30 30
 #include "AmSessionContainer.h"
31 31
 #include "CallLegEvents.h"
32
+#include "SessionUpdate.h"
32 33
 
33 34
 #include <queue>
34 35
 
35
-struct PendingReinvite
36
-{
37
-  string hdrs;
38
-  AmMimeBody body;
39
-  unsigned r_cseq;
40
-  bool relayed_invite;
41
-  bool establishing;
42
-};
43
-
44 36
 /** composed AmB2BCalleeSession & AmB2BCallerSession
45 37
  * represents indepenedently A or B leg of a call,
46 38
  * old clases left for compatibility
... ...
@@ -151,12 +143,23 @@ class CallLeg: public AmB2BSession
151 143
     AmSdp non_hold_sdp;
152 144
     enum { HoldRequested, ResumeRequested, PreserveHoldStatus } hold;
153 145
 
154
-    std::queue<PendingReinvite> pending_reinvites;
155
-
146
+    // queue of session update operations, first element is possibly the one
147
+    // being in progress
148
+    std::list<SessionUpdate *> pending_updates;
149
+    class SessionUpdateTimer pending_updates_timer;
156 150
 
157 151
     // generate re-INVITE with given parameters (establishing means that the
158 152
     // INVITE is establishing a connection between two legs)
159
-    void reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing);
153
+    // returns the request CSeq or -1 upon error
154
+    int reinvite(const string &hdrs, const AmMimeBody &body, bool relayed, unsigned r_cseq, bool establishing);
155
+
156
+    // put on hold directly, returns CSeq of hold request or -1 if no request
157
+    // was sent out (either error or on hold already)
158
+    int putOnHoldImpl();
159
+
160
+    // resume held call directly; returns CSeq of unhold request or -1 if no request
161
+    // was sent out (either error or not on hold)
162
+    int resumeHeldImpl();
160 163
 
161 164
     // generate 200 reply on a pending INVITE (uses fake body)
162 165
     void acceptPendingInvite(AmSipRequest *invite);
... ...
@@ -234,6 +237,14 @@ class CallLeg: public AmB2BSession
234 237
       * doesn't cause calling this */
235 238
      void offerRejected();
236 239
 
240
+    // returns false if there is a pending INVITE so the session can not be
241
+    // changed right now
242
+    bool canUpdateSession() { return !(dlg->getUACInvTransPending() || dlg->getUASPendingInv()); }
243
+    void applyPendingUpdate();
244
+    void updateSession(SessionUpdate *op);
245
+
246
+    virtual void onTransFinished();
247
+
237 248
   protected:
238 249
 
239 250
     // functions offered to successors
... ...
@@ -241,9 +252,6 @@ class CallLeg: public AmB2BSession
241 252
     virtual void setCallStatus(CallStatus new_status);
242 253
     CallStatus getCallStatus() { return call_status; }
243 254
 
244
-    void queueReinvite(const string& hdrs, const AmMimeBody& body, bool establishing = false,
245
-		       bool relayed_invite=false, unsigned int r_cseq = 0);
246
-
247 255
     // @see AmSession
248 256
     virtual void onInvite(const AmSipRequest& req);
249 257
     virtual void onInvite2xx(const AmSipReply& reply);
... ...
@@ -305,6 +313,9 @@ class CallLeg: public AmB2BSession
305 313
 
306 314
     virtual void updateLocalSdp(AmSdp &sdp);
307 315
 
316
+    // return retry time for 491 reply in seconds
317
+    virtual double get491RetryTime() { return (get_random() % 200) / 100.0; }
318
+
308 319
   public:
309 320
     virtual void onB2BEvent(B2BEvent* ev);
310 321
 
... ...
@@ -382,6 +393,10 @@ class CallLeg: public AmB2BSession
382 393
     virtual void onEarlySessionStart() { }
383 394
     virtual void onSessionStart() { }
384 395
 
396
+    // classes doing session update are allowed to access our internals:
397
+    friend class Reinvite;
398
+    friend class PutOnHold;
399
+    friend class ResumeHeld;
385 400
 };
386 401
 
387 402
 
... ...
@@ -9,10 +9,11 @@ enum {
9 9
   ReplaceInProgress,
10 10
   DisconnectLeg,
11 11
   ChangeRtpModeEventId,
12
-  ResumeHeld
12
+  ResumeHeldLeg,
13
+  ApplyPendingUpdatesEventId
13 14
 };
14 15
 
15
-#define LAST_B2B_CALL_LEG_EVENT_ID ResumeHeld
16
+#define LAST_B2B_CALL_LEG_EVENT_ID ApplyPendingUpdatesEventId
16 17
 
17 18
 struct ConnectLegEvent: public B2BEvent
18 19
 {
... ...
@@ -164,7 +165,12 @@ struct ChangeRtpModeEvent: public B2BEvent
164 165
 
165 166
 struct ResumeHeldEvent: public B2BEvent
166 167
 {
167
-  ResumeHeldEvent(): B2BEvent(ResumeHeld) { }
168
+  ResumeHeldEvent(): B2BEvent(ResumeHeldLeg) { }
169
+};
170
+
171
+struct ApplyPendingUpdatesEvent: public B2BEvent
172
+{
173
+  ApplyPendingUpdatesEvent(): B2BEvent(ApplyPendingUpdatesEventId) { }
168 174
 };
169 175
 
170 176
 #endif
... ...
@@ -251,6 +251,8 @@ class SBCCallLeg : public CallLeg, public CredentialHolder
251 251
 
252 252
   bool openLogger(const std::string &path);
253 253
   msg_logger *getLogger() { return logger; }
254
+
255
+  virtual double get491RetryTime() { return (get_random() % call_profile.max_491_retry_time) / 1000.0; }
254 256
 };
255 257
 
256 258
 #endif
... ...
@@ -388,6 +388,8 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
388 388
   min_reg_expires = cfg.getParameterInt("min_reg_expires",0);
389 389
   max_ua_expires = cfg.getParameterInt("max_ua_expires",0);
390 390
 
391
+  max_491_retry_time = cfg.getParameterInt("max_491_retry_time", 2000);
392
+
391 393
   md5hash = "<unknown>";
392 394
   if (!cfg.getMD5(profile_file_name, md5hash)){
393 395
     ERROR("calculating MD5 of file %s\n", profile_file_name.c_str());
... ...
@@ -314,6 +314,10 @@ struct SBCCallProfile
314 314
       bool evaluate(ParamReplacerCtx& ctx, const AmSipRequest& req);
315 315
   } hold_settings;
316 316
 
317
+  // maximum retry time for repeating reINVITE after 491 response (in
318
+  // milliseconds), according to RFC 3261 should be 2000 ms
319
+  int max_491_retry_time;
320
+
317 321
  private:
318 322
   // message logging feature
319 323
   string msg_logger_path;
... ...
@@ -352,7 +356,8 @@ struct SBCCallProfile
352 356
     patch_ruri_next_hop(false),
353 357
     next_hop_1st_req(false),
354 358
     next_hop_fixed(false),
355
-    allow_subless_notify(false)
359
+    allow_subless_notify(false),
360
+    max_491_retry_time(2000)
356 361
   { }
357 362
 
358 363
   ~SBCCallProfile()
359 364
new file mode 100644
... ...
@@ -0,0 +1,38 @@
1
+#include "SessionUpdate.h"
2
+#include "CallLeg.h"
3
+
4
+void PutOnHold::apply(CallLeg *call)
5
+{
6
+  setCSeq(call->putOnHoldImpl());
7
+}
8
+
9
+///////////////////////////////////////////////////////////////////////////////////////
10
+
11
+void ResumeHeld::apply(CallLeg *call)
12
+{
13
+  setCSeq(call->resumeHeldImpl());
14
+}
15
+
16
+///////////////////////////////////////////////////////////////////////////////////////
17
+
18
+void Reinvite::apply(CallLeg *call)
19
+{
20
+  setCSeq(call->reinvite(hdrs, body, relayed_invite, r_cseq, establishing));
21
+}
22
+
23
+///////////////////////////////////////////////////////////////////////////////////////
24
+
25
+void SessionUpdateTimer::fire()
26
+{
27
+  DBG("session update timer fired");
28
+  has_started = false;
29
+  AmSessionContainer::instance()->postEvent(ltag, new ApplyPendingUpdatesEvent());
30
+}
31
+
32
+void SessionUpdateTimer::start(const std::string &_ltag, double delay)
33
+{
34
+  has_started = true;
35
+  ltag = _ltag; // not nice here, needed to find a place where the local tag is set finally
36
+  AmAppTimer::instance()->setTimer(this, delay);
37
+}
38
+
0 39
new file mode 100644
... ...
@@ -0,0 +1,94 @@
1
+#ifndef __SESSION_UPDATE_H
2
+#define __SESSION_UPDATE_H
3
+
4
+#include <string>
5
+#include "AmMimeBody.h"
6
+#include "AmAppTimer.h"
7
+
8
+class CallLeg;
9
+
10
+/* interface to be implemented by re-applicable operations on a session
11
+ * expected usage: the operation generates a reINVITE and if this reINVITE fails
12
+ * with specific error code (491, ...) the operation can be re-applied
13
+ *
14
+ * Note that only one session update operation may be running, others need
15
+ * to wait for finishing the current one.
16
+ * */
17
+class SessionUpdate
18
+{
19
+  private:
20
+    int request_cseq;
21
+
22
+  protected:
23
+    void setCSeq(int cseq) { request_cseq = cseq; }
24
+
25
+    SessionUpdate(): request_cseq(-1) { }
26
+
27
+  public:
28
+    // used to (re)apply session update
29
+    // returns the CSeq of generated request (reINVITE/...)
30
+    virtual void apply(CallLeg *call) = 0;
31
+
32
+//    virtual void onSipReply(CallLeg *call, AmSipReply &reply);
33
+    virtual ~SessionUpdate() { }
34
+
35
+    // check whether update request was sent out
36
+    bool hasCSeq() const { return request_cseq >= 0; }
37
+
38
+    // check the request cseq value
39
+    bool hasCSeq(int cseq) const { return request_cseq == cseq; }
40
+
41
+    // reset internal state to be prepared for retrying the update operation
42
+    virtual void reset() { setCSeq(-1); }
43
+
44
+};
45
+
46
+class PutOnHold: public SessionUpdate
47
+{
48
+  public:
49
+    virtual void apply(CallLeg *call);
50
+};
51
+
52
+class ResumeHeld: public SessionUpdate
53
+{
54
+  public:
55
+    virtual void apply(CallLeg *call);
56
+};
57
+
58
+class Reinvite: public SessionUpdate
59
+{
60
+    std::string hdrs;
61
+    AmMimeBody body;
62
+    unsigned r_cseq;
63
+    bool relayed_invite;
64
+    bool establishing;
65
+
66
+  public:
67
+    virtual void apply(CallLeg *call);
68
+
69
+    Reinvite(const std::string& _hdrs, const AmMimeBody& _body,
70
+        bool _establishing = false,
71
+        bool _relayed_invite = false, unsigned int _r_cseq = 0):
72
+          hdrs(_hdrs), body(_body), establishing(_establishing),
73
+          relayed_invite(_relayed_invite), r_cseq(_r_cseq) { }
74
+};
75
+
76
+class SessionUpdateTimer: public DirectAppTimer
77
+{
78
+  private:
79
+    std::string ltag;
80
+    bool has_started;
81
+
82
+  public:
83
+    SessionUpdateTimer(): has_started(false) { }
84
+    ~SessionUpdateTimer() { if (has_started) AmAppTimer::instance()->removeTimer(this); }
85
+
86
+    void fire();
87
+    bool started() { return has_started; }
88
+
89
+    // start the timer (local tag is supplied here because it may change during
90
+    // call legs's lifetime)
91
+    void start(const std::string &_ltag, double delay);
92
+};
93
+
94
+#endif
... ...
@@ -459,6 +459,7 @@ void AmBasicSipDialog::onRxReply(const AmSipReply& reply)
459 459
       (reply.code >= 300))) {
460 460
        
461 461
     uac_trans.erase(reply.cseq);
462
+    if (hdl) hdl->onTransFinished();
462 463
   }
463 464
 }
464 465
 
... ...
@@ -540,6 +541,7 @@ void AmBasicSipDialog::onReplyTxed(const AmSipRequest& req,
540 541
       (reply.cseq_method != SIP_METH_CANCEL)) {
541 542
     
542 543
     uas_trans.erase(reply.cseq);
544
+    if (hdl) hdl->onTransFinished();
543 545
   }
544 546
 }
545 547
 
... ...
@@ -553,6 +555,7 @@ void AmBasicSipDialog::onRequestTxed(const AmSipRequest& req)
553 555
   }
554 556
   else {
555 557
     uac_trans.erase(req.cseq);
558
+    if (hdl) hdl->onTransFinished();
556 559
   }
557 560
 }
558 561
 
... ...
@@ -455,6 +455,10 @@ class AmBasicSipEventHandler
455 455
    */
456 456
   virtual void onFailure() {}
457 457
 
458
+  // called upon finishing either UAC or UAS transaction
459
+  virtual void onTransFinished() { }
460
+
461
+
458 462
   virtual ~AmBasicSipEventHandler() {}
459 463
 };
460 464