mediasoup 3.20.2 → 3.20.4

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 (26) hide show
  1. package/npm-scripts.mjs +26 -0
  2. package/package.json +4 -3
  3. package/worker/include/RTC/SCTP/association/Association.hpp +16 -2
  4. package/worker/include/RTC/SCTP/association/HeartbeatHandler.hpp +3 -3
  5. package/worker/include/RTC/SCTP/association/StreamResetHandler.hpp +3 -3
  6. package/worker/include/RTC/SCTP/association/TransmissionControlBlock.hpp +19 -4
  7. package/worker/include/RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp +19 -2
  8. package/worker/include/RTC/SCTP/public/SctpTypes.hpp +2 -0
  9. package/worker/include/RTC/SCTP/rx/ReassemblyQueue.hpp +7 -0
  10. package/worker/include/RTC/SCTP/tx/RetransmissionErrorCounter.hpp +3 -4
  11. package/worker/meson.build +3 -0
  12. package/worker/mocks/include/RTC/SCTP/association/MockAssociationListener.hpp +68 -7
  13. package/worker/mocks/include/handles/MockBackoffTimerHandle.hpp +10 -4
  14. package/worker/mocks/src/handles/MockBackoffTimerHandle.cpp +7 -0
  15. package/worker/src/RTC/SCTP/association/Association.cpp +52 -12
  16. package/worker/src/RTC/SCTP/association/HeartbeatHandler.cpp +48 -18
  17. package/worker/src/RTC/SCTP/association/StreamResetHandler.cpp +26 -17
  18. package/worker/src/RTC/SCTP/association/TransmissionControlBlock.cpp +48 -15
  19. package/worker/src/RTC/SCTP/rx/ReassemblyQueue.cpp +17 -8
  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/TestHeartbeatHandler.cpp +4 -1
  23. package/worker/test/src/RTC/SCTP/association/TestPacketSender.cpp +121 -0
  24. package/worker/test/src/RTC/SCTP/association/TestStreamResetHandler.cpp +369 -0
  25. package/worker/test/src/RTC/SCTP/association/TestTransmissionControlBlock.cpp +16 -2
  26. package/worker/test/src/RTC/SCTP/rx/TestReassemblyQueue.cpp +24 -0
@@ -19,11 +19,11 @@ namespace RTC
19
19
  /* Instance methods. */
20
20
 
21
21
  HeartbeatHandler::HeartbeatHandler(
22
- AssociationListenerInterface& associationListener,
22
+ AssociationListenerDeferrer& associationListenerDeferrer,
23
23
  const SctpOptions& sctpOptions,
24
24
  SharedInterface* shared,
25
25
  TransmissionControlBlockContextInterface* tcbContext)
26
- : associationListener(associationListener),
26
+ : associationListenerDeferrer(associationListenerDeferrer),
27
27
  sctpOptions(sctpOptions),
28
28
  shared(shared),
29
29
  tcbContext(tcbContext),
@@ -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
  {
@@ -124,7 +124,7 @@ namespace RTC
124
124
 
125
125
  if (!heartbeatInfoParameter)
126
126
  {
127
- this->associationListener.OnAssociationError(
127
+ this->associationListenerDeferrer.OnAssociationError(
128
128
  Types::ErrorKind::PARSE_FAILED,
129
129
  "ignoring HEARTBEAT-ACK chunk without Heartbeat Info parameter");
130
130
 
@@ -136,14 +136,14 @@ namespace RTC
136
136
 
137
137
  if (!info)
138
138
  {
139
- this->associationListener.OnAssociationError(
139
+ this->associationListenerDeferrer.OnAssociationError(
140
140
  Types::ErrorKind::PARSE_FAILED, "ignoring Heartbeat Info parameter without info field");
141
141
 
142
142
  return;
143
143
  }
144
144
  else if (infoLen != HeartbeatInfoLength)
145
145
  {
146
- this->associationListener.OnAssociationError(
146
+ this->associationListenerDeferrer.OnAssociationError(
147
147
  Types::ErrorKind::PARSE_FAILED, "ignoring Heartbeat Info parameter with wrong length");
148
148
 
149
149
  return;
@@ -180,6 +180,23 @@ 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
+
195
+ // This is a top-level timer entry point (invoked by libuv outside any other
196
+ // SCTP API call), so it must establish the deferrer scope itself, just like
197
+ // Association does in its own timer handlers.
198
+ const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
199
+
183
200
  if (!this->tcbContext->IsAssociationEstablished())
184
201
  {
185
202
  MS_DEBUG_DEV("won't send HEARTBEAT-REQUEST when SCTP association is not established");
@@ -210,15 +227,37 @@ namespace RTC
210
227
  this->tcbContext->SendPacket(packet.get());
211
228
  }
212
229
 
213
- void HeartbeatHandler::OnTimeoutTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)
230
+ void HeartbeatHandler::OnTimeoutTimer(uint64_t& /*baseTimeoutMs*/, bool& stop)
214
231
  {
215
232
  MS_TRACE();
216
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
+
243
+ // This is a top-level timer entry point (invoked by libuv outside any other
244
+ // SCTP API call), so it must establish the deferrer scope itself, just like
245
+ // Association does in its own timer handlers.
246
+ const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
247
+
217
248
  // Note that the timeout timer is not restarted. It will be started again when
218
249
  // the interval timer expires.
219
250
  MS_ASSERT(!this->timeoutTimer->IsRunning(), "timeout timer shouldn't be running");
220
251
 
221
- 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
+ }
222
261
  }
223
262
 
224
263
  void HeartbeatHandler::OnBackoffTimer(
@@ -226,15 +265,6 @@ namespace RTC
226
265
  {
227
266
  MS_TRACE();
228
267
 
229
- const auto maxRestarts = backoffTimer->GetMaxRestarts();
230
-
231
- MS_DEBUG_TAG(
232
- sctp,
233
- "%s timer has expired [expìrations:%zu/%s]",
234
- backoffTimer->GetLabel().c_str(),
235
- backoffTimer->GetExpirationCount(),
236
- maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
237
-
238
268
  if (backoffTimer == this->intervalTimer.get())
239
269
  {
240
270
  OnIntervalTimer(baseTimeoutMs, stop);
@@ -13,13 +13,13 @@ namespace RTC
13
13
  /* Instance methods. */
14
14
 
15
15
  StreamResetHandler::StreamResetHandler(
16
- AssociationListenerInterface& associationListener,
16
+ AssociationListenerDeferrer& associationListenerDeferrer,
17
17
  SharedInterface* shared,
18
18
  TransmissionControlBlockContextInterface* tcbContext,
19
19
  DataTracker* dataTracker,
20
20
  ReassemblyQueue* reassemblyQueue,
21
21
  RetransmissionQueue* retransmissionQueue)
22
- : associationListener(associationListener),
22
+ : associationListenerDeferrer(associationListenerDeferrer),
23
23
  shared(shared),
24
24
  tcbContext(tcbContext),
25
25
  dataTracker(dataTracker),
@@ -89,7 +89,7 @@ namespace RTC
89
89
 
90
90
  if (!ValidateReceivedReConfigChunk(receivedReConfigChunk))
91
91
  {
92
- this->associationListener.OnAssociationError(
92
+ this->associationListenerDeferrer.OnAssociationError(
93
93
  Types::ErrorKind::PARSE_FAILED, "invalid RE-CONFIG command received");
94
94
 
95
95
  return;
@@ -334,7 +334,7 @@ namespace RTC
334
334
  this->reassemblyQueue->ResetStreamsAndLeaveDeferredReset(
335
335
  receivedOutgoingSsnResetRequestParameter->GetStreamIds());
336
336
 
337
- this->associationListener.OnAssociationInboundStreamsReset(
337
+ this->associationListenerDeferrer.OnAssociationInboundStreamsReset(
338
338
  receivedOutgoingSsnResetRequestParameter->GetStreamIds());
339
339
 
340
340
  this->lastProcessedReqResult = ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED;
@@ -412,7 +412,7 @@ namespace RTC
412
412
  MS_DEBUG_DEV(
413
413
  "reset stream success [reqSeqNbr:%" PRIu32 "]", this->currentRequest->GetReqSeqNbr());
414
414
 
415
- this->associationListener.OnAssociationStreamsResetPerformed(
415
+ this->associationListenerDeferrer.OnAssociationStreamsResetPerformed(
416
416
  this->currentRequest->GetStreamIds());
417
417
 
418
418
  this->currentRequest = std::nullopt;
@@ -450,7 +450,7 @@ namespace RTC
450
450
  receivedReconfigurationResponseParameter->GetResult())
451
451
  .c_str());
452
452
 
453
- this->associationListener.OnAssociationStreamsResetFailed(
453
+ this->associationListenerDeferrer.OnAssociationStreamsResetFailed(
454
454
  this->currentRequest->GetStreamIds(),
455
455
  ReconfigurationResponseParameter::ResultToString(
456
456
  receivedReconfigurationResponseParameter->GetResult()));
@@ -465,10 +465,24 @@ namespace RTC
465
465
  }
466
466
  }
467
467
 
468
- void StreamResetHandler::OnReConfigTimer(uint64_t& baseTimeoutMs, bool& /*stop*/)
468
+ void StreamResetHandler::OnReConfigTimer(uint64_t& baseTimeoutMs, bool& stop)
469
469
  {
470
470
  MS_TRACE();
471
471
 
472
+ const auto maxRestarts = this->reConfigTimer->GetMaxRestarts();
473
+
474
+ MS_DEBUG_TAG(
475
+ sctp,
476
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
477
+ this->reConfigTimer->GetLabel().c_str(),
478
+ this->reConfigTimer->GetExpirationCount(),
479
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
480
+
481
+ // This is a top-level timer entry point (invoked by libuv outside any other
482
+ // SCTP API call), so it must establish the deferrer scope itself, just like
483
+ // Association does in its own timer handlers.
484
+ const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
485
+
472
486
  if (this->currentRequest && this->currentRequest->HasBeenSent())
473
487
  {
474
488
  // The request was deferred (received "In Progress"). This is not a
@@ -481,7 +495,11 @@ namespace RTC
481
495
  // response.
482
496
  else if (!this->tcbContext->IncrementTxErrorCounter("RECONFIG timeout"))
483
497
  {
484
- // Timed out. The connection will close after processing the timers.
498
+ // `IncrementTxErrorCounter()` has closed (and destroyed) the TCB (and
499
+ // hence this StreamResetHandler and its timer). Signal the firing timer
500
+ // to stop and don't touch any member afterwards.
501
+ stop = true;
502
+
485
503
  return;
486
504
  }
487
505
  }
@@ -506,15 +524,6 @@ namespace RTC
506
524
  {
507
525
  MS_TRACE();
508
526
 
509
- const auto maxRestarts = backoffTimer->GetMaxRestarts();
510
-
511
- MS_DEBUG_TAG(
512
- sctp,
513
- "%s timer has expired [expìrations:%zu/%s]",
514
- backoffTimer->GetLabel().c_str(),
515
- backoffTimer->GetExpirationCount(),
516
- maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
517
-
518
527
  if (backoffTimer == this->reConfigTimer.get())
519
528
  {
520
529
  OnReConfigTimer(baseTimeoutMs, stop);
@@ -21,7 +21,8 @@ namespace RTC
21
21
  /* Instance methods. */
22
22
 
23
23
  TransmissionControlBlock::TransmissionControlBlock(
24
- AssociationListenerInterface& associationListener,
24
+ TransmissionControlBlockContextInterface::Listener* listener,
25
+ AssociationListenerDeferrer& associationListenerDeferrer,
25
26
  const SctpOptions& sctpOptions,
26
27
  SharedInterface* shared,
27
28
  SendQueueInterface& sendQueue,
@@ -35,7 +36,8 @@ namespace RTC
35
36
  const NegotiatedCapabilities& negotiatedCapabilities,
36
37
  size_t maxPacketLength,
37
38
  std::function<bool()> isAssociationEstablished)
38
- : associationListener(associationListener),
39
+ : listener(listener),
40
+ associationListenerDeferrer(associationListenerDeferrer),
39
41
  sctpOptions(sctpOptions),
40
42
  shared(shared),
41
43
  packetSender(packetSender),
@@ -71,7 +73,7 @@ namespace RTC
71
73
  sctpOptions.maxReceiverWindowBufferSize, negotiatedCapabilities.messageInterleaving),
72
74
  retransmissionQueue(
73
75
  this,
74
- this->associationListener,
76
+ this->associationListenerDeferrer,
75
77
  localInitialTsn,
76
78
  remoteAdvertisedReceiverWindowCredit,
77
79
  sendQueue,
@@ -80,13 +82,13 @@ namespace RTC
80
82
  negotiatedCapabilities.partialReliability,
81
83
  negotiatedCapabilities.messageInterleaving),
82
84
  streamResetHandler(
83
- this->associationListener,
85
+ this->associationListenerDeferrer,
84
86
  this->shared,
85
87
  this,
86
88
  std::addressof(this->dataTracker),
87
89
  std::addressof(this->reassemblyQueue),
88
90
  std::addressof(this->retransmissionQueue)),
89
- heartbeatHandler(this->associationListener, sctpOptions, this->shared, this)
91
+ heartbeatHandler(this->associationListenerDeferrer, sctpOptions, this->shared, this)
90
92
  {
91
93
  MS_TRACE();
92
94
 
@@ -407,10 +409,24 @@ namespace RTC
407
409
  }
408
410
  }
409
411
 
410
- void TransmissionControlBlock::OnT3RtxTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)
412
+ void TransmissionControlBlock::OnT3RtxTimer(uint64_t& /*baseTimeoutMs*/, bool& stop)
411
413
  {
412
414
  MS_TRACE();
413
415
 
416
+ const auto maxRestarts = this->t3RtxTimer->GetMaxRestarts();
417
+
418
+ MS_DEBUG_TAG(
419
+ sctp,
420
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
421
+ this->t3RtxTimer->GetLabel().c_str(),
422
+ this->t3RtxTimer->GetExpirationCount(),
423
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
424
+
425
+ // This is a top-level timer entry point (invoked by libuv outside any other
426
+ // SCTP API call), so it must establish the deferrer scope itself, just like
427
+ // Association does in its own timer handlers.
428
+ const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
429
+
414
430
  // In the COOKIE-ECHO state, let the T1-COOKIE timer trigger
415
431
  // retransmissions, to avoid having two timers doing that.
416
432
  if (this->remoteStateCookie.has_value())
@@ -427,6 +443,15 @@ namespace RTC
427
443
 
428
444
  SendBufferedPackets(nowMs);
429
445
  }
446
+ else
447
+ {
448
+ // `IncrementTxErrorCounter()` has closed (and destroyed) this TCB and
449
+ // its timers. Signal the firing timer to stop and don't touch any
450
+ // member afterwards.
451
+ stop = true;
452
+
453
+ return;
454
+ }
430
455
  }
431
456
  }
432
457
 
@@ -434,6 +459,23 @@ namespace RTC
434
459
  {
435
460
  MS_TRACE();
436
461
 
462
+ #if MS_LOG_DEV_LEVEL == 3
463
+ const auto maxRestarts = this->delayedAckTimer->GetMaxRestarts();
464
+ #endif
465
+
466
+ // NOTE: This timer expires very frequently (whenever received data is
467
+ // pending to be acked), so it's logged at dev level to avoid being noisy.
468
+ MS_DEBUG_DEV(
469
+ "%s timer has expired [expirations:%zu, maxRestarts:%s]",
470
+ this->delayedAckTimer->GetLabel().c_str(),
471
+ this->delayedAckTimer->GetExpirationCount(),
472
+ maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
473
+
474
+ // This is a top-level timer entry point (invoked by libuv outside any other
475
+ // SCTP API call), so it must establish the deferrer scope itself, just like
476
+ // Association does in its own timer handlers.
477
+ const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
478
+
437
479
  this->dataTracker.HandleDelayedAckTimerExpiry();
438
480
 
439
481
  MaySendSackChunk();
@@ -444,15 +486,6 @@ namespace RTC
444
486
  {
445
487
  MS_TRACE();
446
488
 
447
- const auto maxRestarts = backoffTimer->GetMaxRestarts();
448
-
449
- MS_DEBUG_TAG(
450
- sctp,
451
- "%s timer has expired [expìrations:%zu/%s]",
452
- backoffTimer->GetLabel().c_str(),
453
- backoffTimer->GetExpirationCount(),
454
- maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
455
-
456
489
  if (backoffTimer == this->t3RtxTimer.get())
457
490
  {
458
491
  OnT3RtxTimer(baseTimeoutMs, stop);
@@ -140,12 +140,16 @@ namespace RTC
140
140
  {
141
141
  MS_DEBUG_DEV("forward TSN to %" PRIu32 ", deferring", tsn.Wrap());
142
142
 
143
+ this->queuedBytes += ForwardTsnCost(skippedStreams.size());
144
+
143
145
  this->deferredResetStreams->deferredActions.emplace_back(
144
146
  [this,
145
147
  newCumulativeTsn,
146
148
  skippedStreams2 = std::vector<AnyForwardTsnChunk::SkippedStream>(
147
149
  skippedStreams.begin(), skippedStreams.end())]
148
150
  {
151
+ this->queuedBytes -= ForwardTsnCost(skippedStreams2.size());
152
+
149
153
  HandleForwardTsn(newCumulativeTsn, skippedStreams2);
150
154
  });
151
155
 
@@ -216,15 +220,13 @@ namespace RTC
216
220
 
217
221
  if (!this->deferredResetStreams.has_value())
218
222
  {
219
- return;
220
- }
221
-
222
- MS_DEBUG_DEV(
223
- "entering deferred reset [senderLastAssignedTsn:%" PRIu32 "]", senderLastAssignedTsn);
223
+ MS_DEBUG_DEV(
224
+ "entering deferred reset [senderLastAssignedTsn:%" PRIu32 "]", senderLastAssignedTsn);
224
225
 
225
- this->deferredResetStreams = std::make_optional<DeferredResetStreams>(
226
- this->tsnUnwrapper.Unwrap(senderLastAssignedTsn),
227
- std::set<uint16_t>(streamIds.begin(), streamIds.end()));
226
+ this->deferredResetStreams = std::make_optional<DeferredResetStreams>(
227
+ this->tsnUnwrapper.Unwrap(senderLastAssignedTsn),
228
+ std::set<uint16_t>(streamIds.begin(), streamIds.end()));
229
+ }
228
230
 
229
231
  AssertIsConsistent();
230
232
  }
@@ -244,6 +246,13 @@ namespace RTC
244
246
  }
245
247
  }
246
248
 
249
+ size_t ReassemblyQueue::ForwardTsnCost(size_t numStreams)
250
+ {
251
+ MS_TRACE();
252
+
253
+ return (1 + numStreams) * 4;
254
+ }
255
+
247
256
  void ReassemblyQueue::AddReassembledMessage(std::span<const Types::UnwrappedTsn> tsns, Message message)
248
257
  {
249
258
  MS_TRACE();
@@ -91,7 +91,7 @@ namespace RTC
91
91
  };
92
92
 
93
93
  this->sctpAssociation = std::make_unique<RTC::SCTP::Association>(
94
- sctpOptions, this, this->shared, options->isDataChannel());
94
+ sctpOptions, this, this->shared, options->isDataChannel(), /*mayConnectOnReceivedSctpData*/ true);
95
95
  }
96
96
 
97
97
  // Create the RTCP timer.