mediasoup 3.20.3 → 3.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +2 -2
  2. package/worker/include/RTC/SCTP/association/Association.hpp +16 -2
  3. package/worker/include/RTC/SCTP/association/TransmissionControlBlock.hpp +16 -1
  4. package/worker/include/RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp +19 -2
  5. package/worker/include/RTC/SCTP/public/SctpTypes.hpp +2 -0
  6. package/worker/include/RTC/SCTP/rx/ReassemblyQueue.hpp +7 -0
  7. package/worker/include/RTC/SCTP/tx/RetransmissionErrorCounter.hpp +3 -4
  8. package/worker/include/RTC/SeqManager.hpp +2 -2
  9. package/worker/meson.build +3 -0
  10. package/worker/mocks/include/RTC/SCTP/association/MockAssociationListener.hpp +68 -7
  11. package/worker/mocks/include/handles/MockBackoffTimerHandle.hpp +10 -4
  12. package/worker/mocks/src/handles/MockBackoffTimerHandle.cpp +7 -0
  13. package/worker/src/RTC/SCTP/association/Association.cpp +56 -14
  14. package/worker/src/RTC/SCTP/association/HeartbeatHandler.cpp +33 -13
  15. package/worker/src/RTC/SCTP/association/StreamResetHandler.cpp +19 -11
  16. package/worker/src/RTC/SCTP/association/TransmissionControlBlock.cpp +34 -11
  17. package/worker/src/RTC/SCTP/rx/DataTracker.cpp +4 -1
  18. package/worker/src/RTC/SCTP/rx/ReassemblyQueue.cpp +17 -8
  19. package/worker/src/RTC/SeqManager.cpp +42 -29
  20. package/worker/src/RTC/Transport.cpp +1 -1
  21. package/worker/test/src/RTC/SCTP/association/TestAssociation.cpp +1755 -0
  22. package/worker/test/src/RTC/SCTP/association/TestPacketSender.cpp +121 -0
  23. package/worker/test/src/RTC/SCTP/association/TestStreamResetHandler.cpp +369 -0
  24. package/worker/test/src/RTC/SCTP/association/TestTransmissionControlBlock.cpp +11 -0
  25. package/worker/test/src/RTC/SCTP/rx/TestReassemblyQueue.cpp +24 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mediasoup",
3
- "version": "3.20.3",
3
+ "version": "3.20.5",
4
4
  "description": "Cutting Edge WebRTC Video Conferencing",
5
5
  "contributors": [
6
6
  "Iñaki Baz Castillo <ibc@aliax.net> (https://inakibaz.me)",
@@ -122,7 +122,7 @@
122
122
  "globals": "^17.6.0",
123
123
  "ini": "^6.0.0",
124
124
  "jest": "^30.4.2",
125
- "knip": "^6.15.0",
125
+ "knip": "^6.16.1",
126
126
  "marked": "^18.0.5",
127
127
  "open-cli": "^9.0.0",
128
128
  "pick-port": "^2.2.0",
@@ -53,7 +53,8 @@ namespace RTC
53
53
  */
54
54
  class Association : public AssociationInterface,
55
55
  public PacketSender::Listener,
56
- public BackoffTimerHandleInterface::Listener
56
+ public BackoffTimerHandleInterface::Listener,
57
+ public TransmissionControlBlockContextInterface::Listener
57
58
  {
58
59
  public:
59
60
  /**
@@ -172,7 +173,13 @@ namespace RTC
172
173
  const SctpOptions& sctpOptions,
173
174
  AssociationListenerInterface* listener,
174
175
  SharedInterface* shared,
175
- bool isDataChannel);
176
+ bool isDataChannel,
177
+ // Whether `MayConnect()` should be called when SCTP data is received.
178
+ // This is always true in production (the association auto-initiates the
179
+ // connection as soon as the transport is ready or data arrives). It's
180
+ // only set to false in tests that need a purely passive peer to mimic
181
+ // dcsctp's asymmetric handshake.
182
+ bool mayConnectOnReceivedSctpData);
176
183
 
177
184
  ~Association() override;
178
185
 
@@ -494,6 +501,10 @@ namespace RTC
494
501
  void OnBackoffTimer(
495
502
  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override;
496
503
 
504
+ /* Pure virtual methods inherited from TransmissionControlBlockContextInterface::Listener. */
505
+ public:
506
+ void OnTransmissionControlBlockTooManyTxErrors() override;
507
+
497
508
  private:
498
509
  // SCTP options given in the constructor.
499
510
  SctpOptions sctpOptions;
@@ -526,6 +537,9 @@ namespace RTC
526
537
  const size_t maxPacketLength;
527
538
  // Whether this is DataChannel based SCTP.
528
539
  bool isDataChannel;
540
+ // Whether `MayConnect()` should be called when SCTP data is received.
541
+ // See the constructor for details.
542
+ bool mayConnectOnReceivedSctpData;
529
543
  };
530
544
  } // namespace SCTP
531
545
  } // namespace RTC
@@ -37,6 +37,7 @@ namespace RTC
37
37
  {
38
38
  public:
39
39
  TransmissionControlBlock(
40
+ TransmissionControlBlockContextInterface::Listener* listener,
40
41
  AssociationListenerDeferrer& associationListenerDeferrer,
41
42
  const SctpOptions& sctpOptions,
42
43
  SharedInterface* shared,
@@ -250,7 +251,20 @@ namespace RTC
250
251
  */
251
252
  bool IncrementTxErrorCounter(std::string_view reason) override
252
253
  {
253
- return this->txErrorCounter.Increment(reason);
254
+ const bool withinLimit = this->txErrorCounter.Increment(reason);
255
+
256
+ if (!withinLimit)
257
+ {
258
+ // NOTE: This closes (and destroys) this TCB synchronously. It's safe to
259
+ // do so from within a timer handler because the handler sets the
260
+ // BackoffTimerHandle `stop` flag and doesn't touch any member
261
+ // afterwards, so the (now destroyed) firing timer won't be accessed.
262
+ this->listener->OnTransmissionControlBlockTooManyTxErrors();
263
+ }
264
+
265
+ // NOTE: `withinLimit` is a local, so this is safe even if `this` was
266
+ // destroyed above.
267
+ return withinLimit;
254
268
  }
255
269
 
256
270
  /**
@@ -288,6 +302,7 @@ namespace RTC
288
302
  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override;
289
303
 
290
304
  private:
305
+ TransmissionControlBlockContextInterface::Listener* listener;
291
306
  AssociationListenerDeferrer& associationListenerDeferrer;
292
307
  const SctpOptions sctpOptions;
293
308
  SharedInterface* shared;
@@ -11,6 +11,23 @@ namespace RTC
11
11
  {
12
12
  class TransmissionControlBlockContextInterface
13
13
  {
14
+ public:
15
+ class Listener
16
+ {
17
+ public:
18
+ virtual ~Listener() = default;
19
+
20
+ public:
21
+ /**
22
+ * Called when the transmission error counter has exceeded its limit and
23
+ * the association must therefore be closed.
24
+ *
25
+ * @remarks
26
+ * - It mirrors dcsctp's `CloseConnectionBecauseOfTooManyTransmissionErrors()`.
27
+ */
28
+ virtual void OnTransmissionControlBlockTooManyTxErrors() = 0;
29
+ };
30
+
14
31
  public:
15
32
  virtual ~TransmissionControlBlockContextInterface() = default;
16
33
 
@@ -44,8 +61,8 @@ namespace RTC
44
61
 
45
62
  /**
46
63
  * Increments the transmission error counter, given a human readable
47
- * reason. Returns `true` if the maximum error count has been reached,
48
- * `false` will be returned.
64
+ * reason. Returns `false` if the maximum error count has been reached,
65
+ * `true` otherwise.
49
66
  */
50
67
  virtual bool IncrementTxErrorCounter(std::string_view reason) = 0;
51
68
 
@@ -331,6 +331,8 @@ namespace RTC
331
331
  {
332
332
  return "ERROR_SHUTTING_DOWN";
333
333
  }
334
+
335
+ NO_DEFAULT_GCC();
334
336
  }
335
337
  }
336
338
 
@@ -178,6 +178,13 @@ namespace RTC
178
178
  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage,
179
179
  bool useMessageInterleaving);
180
180
 
181
+ /**
182
+ * Treat Forward-TSN message as payload. size is calculated based on wire
183
+ * size rather than used memory size: 32bit for TSN + 16+16 bits for each
184
+ * skipped stream entry.
185
+ */
186
+ size_t ForwardTsnCost(size_t numStreams);
187
+
181
188
  void AddReassembledMessage(std::span<const Types::UnwrappedTsn> tsns, Message message);
182
189
 
183
190
  void AssertIsConsistent() const;
@@ -18,7 +18,7 @@ namespace RTC
18
18
  class RetransmissionErrorCounter
19
19
  {
20
20
  public:
21
- RetransmissionErrorCounter(const SctpOptions& sctpOptions);
21
+ explicit RetransmissionErrorCounter(const SctpOptions& sctpOptions);
22
22
 
23
23
  ~RetransmissionErrorCounter();
24
24
 
@@ -26,14 +26,13 @@ namespace RTC
26
26
  void Dump(int indentation = 0) const;
27
27
 
28
28
  /**
29
- * Increments the retransmission timer. Returns `true` if the maximum
30
- * error count has been reached, `false` will be returned.
29
+ * Increments the retransmission timer. Returns `false` if the maximum
30
+ * error count has been reached, `true` otherwise.
31
31
  */
32
32
  bool Increment(std::string_view reason);
33
33
 
34
34
  /**
35
35
  * Whether maximum error count has been reached.
36
- * @return [description]
37
36
  */
38
37
  bool IsExhausted() const
39
38
  {
@@ -3,7 +3,7 @@
3
3
 
4
4
  #include "common.hpp"
5
5
  #include <limits> // std::numeric_limits
6
- #include <set>
6
+ #include <vector>
7
7
 
8
8
  namespace RTC
9
9
  {
@@ -53,7 +53,7 @@ namespace RTC
53
53
  T maxInput{ 0 };
54
54
  T maxDropped{ 0 };
55
55
  T maxForwarded{ 0 };
56
- std::set<T, SeqLowerThan> dropped;
56
+ std::vector<T> dropped;
57
57
  };
58
58
  } // namespace RTC
59
59
 
@@ -494,9 +494,12 @@ test_sources = [
494
494
  'test/src/RTC/RTCP/TestPacket.cpp',
495
495
  'test/src/RTC/RTCP/TestXr.cpp',
496
496
  'test/src/RTC/SCTP/sctpCommon.cpp',
497
+ 'test/src/RTC/SCTP/association/TestAssociation.cpp',
497
498
  'test/src/RTC/SCTP/association/TestHeartbeatHandler.cpp',
498
499
  'test/src/RTC/SCTP/association/TestNegotiatedCapabilities.cpp',
500
+ 'test/src/RTC/SCTP/association/TestPacketSender.cpp',
499
501
  'test/src/RTC/SCTP/association/TestStateCookie.cpp',
502
+ 'test/src/RTC/SCTP/association/TestStreamResetHandler.cpp',
500
503
  'test/src/RTC/SCTP/association/TestTransmissionControlBlock.cpp',
501
504
  'test/src/RTC/SCTP/rx/TestDataTracker.cpp',
502
505
  'test/src/RTC/SCTP/rx/TestInterleavedReassemblyStreams.cpp',
@@ -21,7 +21,7 @@ namespace mocks
21
21
  {
22
22
  this->sentPackets.emplace_back(data, data + len);
23
23
 
24
- return true;
24
+ return this->sendDataResult;
25
25
  }
26
26
 
27
27
  void OnAssociationConnecting() override
@@ -75,20 +75,35 @@ namespace mocks
75
75
  this->receivedMessages.emplace_back(std::move(message));
76
76
  }
77
77
 
78
- void OnAssociationStreamsResetPerformed(std::span<const uint16_t> /*outboundStreamIds*/) override
78
+ void OnAssociationStreamsResetPerformed(std::span<const uint16_t> outboundStreamIds) override
79
79
  {
80
- // TODO: Do something here for tests.
80
+ ++this->onStreamsResetPerformedCalls;
81
+
82
+ for (const auto streamId : outboundStreamIds)
83
+ {
84
+ this->streamsResetPerformed.insert(streamId);
85
+ }
81
86
  }
82
87
 
83
88
  void OnAssociationStreamsResetFailed(
84
- std::span<const uint16_t> /*outboundStreamIds*/, std::string_view /*errorMessage*/) override
89
+ std::span<const uint16_t> outboundStreamIds, std::string_view /*errorMessage*/) override
85
90
  {
86
- // TODO: Do something here for tests.
91
+ ++this->onStreamsResetFailedCalls;
92
+
93
+ for (const auto streamId : outboundStreamIds)
94
+ {
95
+ this->streamsResetFailed.insert(streamId);
96
+ }
87
97
  }
88
98
 
89
- void OnAssociationInboundStreamsReset(std::span<const uint16_t> /*inboundStreamIds*/) override
99
+ void OnAssociationInboundStreamsReset(std::span<const uint16_t> inboundStreamIds) override
90
100
  {
91
- // TODO: Do something here for tests.
101
+ ++this->onInboundStreamsResetCalls;
102
+
103
+ for (const auto streamId : inboundStreamIds)
104
+ {
105
+ this->inboundStreamsReset.insert(streamId);
106
+ }
92
107
  }
93
108
 
94
109
  void OnAssociationStreamBufferedAmountLow(uint16_t streamId) override
@@ -208,6 +223,45 @@ namespace mocks
208
223
  return this->onTotalBufferedAmountLowCalls;
209
224
  }
210
225
 
226
+ size_t CountOnStreamsResetPerformedCalls() const
227
+ {
228
+ return this->onStreamsResetPerformedCalls;
229
+ }
230
+
231
+ bool HasStreamsResetPerformedForStreamId(uint16_t streamId) const
232
+ {
233
+ return this->streamsResetPerformed.contains(streamId);
234
+ }
235
+
236
+ size_t CountOnStreamsResetFailedCalls() const
237
+ {
238
+ return this->onStreamsResetFailedCalls;
239
+ }
240
+
241
+ bool HasStreamsResetFailedForStreamId(uint16_t streamId) const
242
+ {
243
+ return this->streamsResetFailed.contains(streamId);
244
+ }
245
+
246
+ size_t CountOnInboundStreamsResetCalls() const
247
+ {
248
+ return this->onInboundStreamsResetCalls;
249
+ }
250
+
251
+ bool HasInboundStreamsResetForStreamId(uint16_t streamId) const
252
+ {
253
+ return this->inboundStreamsReset.contains(streamId);
254
+ }
255
+
256
+ /**
257
+ * Sets the value that `OnAssociationSendData()` will return, allowing
258
+ * tests to simulate a failed send.
259
+ */
260
+ void SetSendDataResult(bool sendDataResult)
261
+ {
262
+ this->sendDataResult = sendDataResult;
263
+ }
264
+
211
265
  bool HasSentPackets() const
212
266
  {
213
267
  return !this->sentPackets.empty();
@@ -293,6 +347,13 @@ namespace mocks
293
347
  std::string erroredErrorMessage;
294
348
  std::map<uint16_t /*streamId*/, size_t /*count*/> onStreamBufferedAmountLowCalls;
295
349
  size_t onTotalBufferedAmountLowCalls{ 0 };
350
+ size_t onStreamsResetPerformedCalls{ 0 };
351
+ std::set<uint16_t /*streamId*/> streamsResetPerformed;
352
+ size_t onStreamsResetFailedCalls{ 0 };
353
+ std::set<uint16_t /*streamId*/> streamsResetFailed;
354
+ size_t onInboundStreamsResetCalls{ 0 };
355
+ std::set<uint16_t /*streamId*/> inboundStreamsReset;
356
+ bool sendDataResult{ true };
296
357
  std::deque<std::vector<uint8_t>> sentPackets;
297
358
  std::deque<::RTC::SCTP::Message> receivedMessages;
298
359
  bool transportReady{ true };
@@ -36,14 +36,20 @@ namespace mocks
36
36
 
37
37
  void Start() override
38
38
  {
39
- this->running = true;
40
- this->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs();
39
+ this->running = true;
40
+ // NOTE: Reset the expiration count, just like the real BackoffTimerHandle
41
+ // does, so that the backoff starts over from the base timeout.
42
+ this->expirationCount = 0;
43
+ this->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs();
41
44
  }
42
45
 
43
46
  void Stop() override
44
47
  {
45
- this->running = false;
46
- this->expiresAtMs = std::numeric_limits<uint64_t>::max();
48
+ this->running = false;
49
+ // NOTE: Reset the expiration count, just like the real BackoffTimerHandle
50
+ // does.
51
+ this->expirationCount = 0;
52
+ this->expiresAtMs = std::numeric_limits<uint64_t>::max();
47
53
  }
48
54
 
49
55
  /**
@@ -123,5 +123,12 @@ namespace mocks
123
123
  {
124
124
  this->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs();
125
125
  }
126
+ // Once the timer is no longer running (e.g. max restarts reached), the real
127
+ // BackoffTimerHandle doesn't restart the underlying timer, so it won't fire
128
+ // again. Mirror that here so a stopped timer doesn't keep expiring.
129
+ else
130
+ {
131
+ this->expiresAtMs = std::numeric_limits<uint64_t>::max();
132
+ }
126
133
  }
127
134
  } // namespace mocks
@@ -40,7 +40,8 @@ namespace RTC
40
40
  const SctpOptions& sctpOptions,
41
41
  AssociationListenerInterface* listener,
42
42
  SharedInterface* shared,
43
- bool isDataChannel)
43
+ bool isDataChannel,
44
+ bool mayConnectOnReceivedSctpData)
44
45
  : sctpOptions(sctpOptions),
45
46
  // Our `listener` member is a `AssociationListenerDeferrer` which takes
46
47
  // `listener` argument as constructor argument.
@@ -78,7 +79,8 @@ namespace RTC
78
79
  .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,
79
80
  .maxRestarts = sctpOptions.maxRetransmissions })),
80
81
  maxPacketLength(Utils::Byte::PadDownTo4Bytes(this->sctpOptions.mtu)),
81
- isDataChannel(isDataChannel)
82
+ isDataChannel(isDataChannel),
83
+ mayConnectOnReceivedSctpData(mayConnectOnReceivedSctpData)
82
84
  {
83
85
  MS_TRACE();
84
86
  }
@@ -542,7 +544,13 @@ namespace RTC
542
544
 
543
545
  // If we are received SCTP data from the remote peer it means that we may
544
546
  // initiate the SCTP association (if not already connected).
545
- MayConnect();
547
+ //
548
+ // NOTE: This is disabled in tests that need a purely passive peer to
549
+ // mimic dcsctp's asymmetric handshake.
550
+ if (this->mayConnectOnReceivedSctpData)
551
+ {
552
+ MayConnect();
553
+ }
546
554
 
547
555
  // NOTE: It's important to create the deferrer here, otherwise it may
548
556
  // happen that MayConnect() ends calling to Connect() so we end with two
@@ -749,6 +757,7 @@ namespace RTC
749
757
  MS_TRACE();
750
758
 
751
759
  this->tcb = std::make_unique<TransmissionControlBlock>(
760
+ this,
752
761
  this->associationListenerDeferrer,
753
762
  this->sctpOptions,
754
763
  this->shared,
@@ -1453,7 +1462,9 @@ namespace RTC
1453
1462
 
1454
1463
  case State::CLOSED:
1455
1464
  {
1456
- MS_WARN_TAG(sctp, "ignoring INIT chunk received in CLOSED state)");
1465
+ MS_WARN_TAG(sctp, "ignoring INIT chunk received in CLOSED state");
1466
+
1467
+ return;
1457
1468
  }
1458
1469
 
1459
1470
  // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.1
@@ -1803,7 +1814,7 @@ namespace RTC
1803
1814
  else if (
1804
1815
  receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() &&
1805
1816
  cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag() &&
1806
- cookie->GetTieTag() == this->tcb->GetTieTag())
1817
+ cookie->GetTieTag() == 0)
1807
1818
  {
1808
1819
  MS_DEBUG_DEV("received COOKIE-ECHO indicating a late COOKIE-ECHO, discarding");
1809
1820
 
@@ -2425,6 +2436,15 @@ namespace RTC
2425
2436
  {
2426
2437
  MS_TRACE();
2427
2438
 
2439
+ const auto maxRestarts = this->t1InitTimer->GetMaxRestarts();
2440
+
2441
+ MS_DEBUG_TAG(
2442
+ sctp,
2443
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
2444
+ this->t1InitTimer->GetLabel().c_str(),
2445
+ this->t1InitTimer->GetExpirationCount(),
2446
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
2447
+
2428
2448
  const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
2429
2449
 
2430
2450
  AssertState(State::COOKIE_WAIT);
@@ -2445,6 +2465,15 @@ namespace RTC
2445
2465
  {
2446
2466
  MS_TRACE();
2447
2467
 
2468
+ const auto maxRestarts = this->t1CookieTimer->GetMaxRestarts();
2469
+
2470
+ MS_DEBUG_TAG(
2471
+ sctp,
2472
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
2473
+ this->t1CookieTimer->GetLabel().c_str(),
2474
+ this->t1CookieTimer->GetExpirationCount(),
2475
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
2476
+
2448
2477
  const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
2449
2478
 
2450
2479
  AssertState(State::COOKIE_ECHOED);
@@ -2467,6 +2496,15 @@ namespace RTC
2467
2496
  {
2468
2497
  MS_TRACE();
2469
2498
 
2499
+ const auto maxRestarts = this->t2ShutdownTimer->GetMaxRestarts();
2500
+
2501
+ MS_DEBUG_TAG(
2502
+ sctp,
2503
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
2504
+ this->t2ShutdownTimer->GetLabel().c_str(),
2505
+ this->t2ShutdownTimer->GetExpirationCount(),
2506
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
2507
+
2470
2508
  AssertState(State::SHUTDOWN_SENT, State::SHUTDOWN_ACK_SENT);
2471
2509
  AssertHasTcb();
2472
2510
 
@@ -2811,15 +2849,6 @@ namespace RTC
2811
2849
  {
2812
2850
  MS_TRACE();
2813
2851
 
2814
- const auto maxRestarts = backoffTimer->GetMaxRestarts();
2815
-
2816
- MS_DEBUG_TAG(
2817
- sctp,
2818
- "%s timer has expired [expìrations:%zu/%s]",
2819
- backoffTimer->GetLabel().c_str(),
2820
- backoffTimer->GetExpirationCount(),
2821
- maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
2822
-
2823
2852
  if (backoffTimer == this->t1InitTimer.get())
2824
2853
  {
2825
2854
  OnT1InitTimer(baseTimeoutMs, stop);
@@ -2833,5 +2862,18 @@ namespace RTC
2833
2862
  OnT2ShutdownTimer(baseTimeoutMs, stop);
2834
2863
  }
2835
2864
  }
2865
+
2866
+ void Association::OnTransmissionControlBlockTooManyTxErrors()
2867
+ {
2868
+ MS_TRACE();
2869
+
2870
+ // NOTE: This is invoked from within a TCB timer handler (t3-rtx, heartbeat
2871
+ // or RE-CONFIG timeout). `InternalClose()` destroys the TCB synchronously,
2872
+ // which is safe because the calling timer handler sets the BackoffTimerHandle
2873
+ // `stop` flag and doesn't touch any member afterwards. `InternalClose()` does
2874
+ // not establish its own deferrer scope, so it relies on the active one set up
2875
+ // by the timer handler.
2876
+ InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "too many transmission errors");
2877
+ }
2836
2878
  } // namespace SCTP
2837
2879
  } // namespace RTC
@@ -34,7 +34,7 @@ namespace RTC
34
34
  .listener = this,
35
35
  .label = "sctp-heartbeat-interval",
36
36
  .baseTimeoutMs = sctpOptions.initialRtoMs,
37
- .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,
37
+ .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::FIXED,
38
38
  .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,
39
39
  .maxRestarts = std::nullopt })),
40
40
  timeoutTimer(this->shared->CreateBackoffTimer(
@@ -42,7 +42,7 @@ namespace RTC
42
42
  .listener = this,
43
43
  .label = "sctp-heartbeat-timeout",
44
44
  .baseTimeoutMs = sctpOptions.initialRtoMs,
45
- .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::FIXED,
45
+ .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,
46
46
  .maxBackoffTimeoutMs = std::nullopt,
47
47
  .maxRestarts = 0 }))
48
48
  {
@@ -180,6 +180,18 @@ namespace RTC
180
180
  {
181
181
  MS_TRACE();
182
182
 
183
+ #if MS_LOG_DEV_LEVEL == 3
184
+ const auto maxRestarts = this->intervalTimer->GetMaxRestarts();
185
+ #endif
186
+
187
+ // NOTE: This timer expires periodically on idle connections (forever), so
188
+ // it's logged at dev level to avoid being noisy.
189
+ MS_DEBUG_DEV(
190
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
191
+ this->intervalTimer->GetLabel().c_str(),
192
+ this->intervalTimer->GetExpirationCount(),
193
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
194
+
183
195
  // This is a top-level timer entry point (invoked by libuv outside any other
184
196
  // SCTP API call), so it must establish the deferrer scope itself, just like
185
197
  // Association does in its own timer handlers.
@@ -215,10 +227,19 @@ namespace RTC
215
227
  this->tcbContext->SendPacket(packet.get());
216
228
  }
217
229
 
218
- void HeartbeatHandler::OnTimeoutTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)
230
+ void HeartbeatHandler::OnTimeoutTimer(uint64_t& /*baseTimeoutMs*/, bool& stop)
219
231
  {
220
232
  MS_TRACE();
221
233
 
234
+ const auto maxRestarts = this->timeoutTimer->GetMaxRestarts();
235
+
236
+ MS_DEBUG_TAG(
237
+ sctp,
238
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
239
+ this->timeoutTimer->GetLabel().c_str(),
240
+ this->timeoutTimer->GetExpirationCount(),
241
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
242
+
222
243
  // This is a top-level timer entry point (invoked by libuv outside any other
223
244
  // SCTP API call), so it must establish the deferrer scope itself, just like
224
245
  // Association does in its own timer handlers.
@@ -228,7 +249,15 @@ namespace RTC
228
249
  // the interval timer expires.
229
250
  MS_ASSERT(!this->timeoutTimer->IsRunning(), "timeout timer shouldn't be running");
230
251
 
231
- this->tcbContext->IncrementTxErrorCounter("hearbeat timeout");
252
+ if (!this->tcbContext->IncrementTxErrorCounter("hearbeat timeout"))
253
+ {
254
+ // `IncrementTxErrorCounter()` has closed (and destroyed) the TCB (and
255
+ // hence this HeartbeatHandler and its timers). Signal the firing timer to
256
+ // stop and don't touch any member afterwards.
257
+ stop = true;
258
+
259
+ return;
260
+ }
232
261
  }
233
262
 
234
263
  void HeartbeatHandler::OnBackoffTimer(
@@ -236,15 +265,6 @@ namespace RTC
236
265
  {
237
266
  MS_TRACE();
238
267
 
239
- const auto maxRestarts = backoffTimer->GetMaxRestarts();
240
-
241
- MS_DEBUG_TAG(
242
- sctp,
243
- "%s timer has expired [expìrations:%zu/%s]",
244
- backoffTimer->GetLabel().c_str(),
245
- backoffTimer->GetExpirationCount(),
246
- maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
247
-
248
268
  if (backoffTimer == this->intervalTimer.get())
249
269
  {
250
270
  OnIntervalTimer(baseTimeoutMs, stop);
@@ -376,6 +376,10 @@ namespace RTC
376
376
  ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO);
377
377
 
378
378
  reconfigurationResponseParameter->Consolidate();
379
+
380
+ this->lastProcessedReqSeqNbr = requestSn;
381
+ this->lastProcessedReqResult =
382
+ ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO;
379
383
  }
380
384
  else
381
385
  {
@@ -465,10 +469,19 @@ namespace RTC
465
469
  }
466
470
  }
467
471
 
468
- void StreamResetHandler::OnReConfigTimer(uint64_t& baseTimeoutMs, bool& /*stop*/)
472
+ void StreamResetHandler::OnReConfigTimer(uint64_t& baseTimeoutMs, bool& stop)
469
473
  {
470
474
  MS_TRACE();
471
475
 
476
+ const auto maxRestarts = this->reConfigTimer->GetMaxRestarts();
477
+
478
+ MS_DEBUG_TAG(
479
+ sctp,
480
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
481
+ this->reConfigTimer->GetLabel().c_str(),
482
+ this->reConfigTimer->GetExpirationCount(),
483
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
484
+
472
485
  // This is a top-level timer entry point (invoked by libuv outside any other
473
486
  // SCTP API call), so it must establish the deferrer scope itself, just like
474
487
  // Association does in its own timer handlers.
@@ -486,7 +499,11 @@ namespace RTC
486
499
  // response.
487
500
  else if (!this->tcbContext->IncrementTxErrorCounter("RECONFIG timeout"))
488
501
  {
489
- // Timed out. The connection will close after processing the timers.
502
+ // `IncrementTxErrorCounter()` has closed (and destroyed) the TCB (and
503
+ // hence this StreamResetHandler and its timer). Signal the firing timer
504
+ // to stop and don't touch any member afterwards.
505
+ stop = true;
506
+
490
507
  return;
491
508
  }
492
509
  }
@@ -511,15 +528,6 @@ namespace RTC
511
528
  {
512
529
  MS_TRACE();
513
530
 
514
- const auto maxRestarts = backoffTimer->GetMaxRestarts();
515
-
516
- MS_DEBUG_TAG(
517
- sctp,
518
- "%s timer has expired [expìrations:%zu/%s]",
519
- backoffTimer->GetLabel().c_str(),
520
- backoffTimer->GetExpirationCount(),
521
- maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
522
-
523
531
  if (backoffTimer == this->reConfigTimer.get())
524
532
  {
525
533
  OnReConfigTimer(baseTimeoutMs, stop);