mediasoup 3.19.21 → 3.19.22

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 (130) hide show
  1. package/node/lib/Worker.d.ts +1 -0
  2. package/node/lib/Worker.d.ts.map +1 -1
  3. package/node/lib/Worker.js +14 -0
  4. package/package.json +4 -2
  5. package/worker/fuzzer/src/RTC/FuzzerDtlsTransport.cpp +9 -3
  6. package/worker/fuzzer/src/RTC/RTP/FuzzerRtpStreamSend.cpp +9 -1
  7. package/worker/include/Channel/ChannelMessageRegistrator.hpp +39 -0
  8. package/worker/include/Channel/ChannelMessageRegistratorInterface.hpp +32 -0
  9. package/worker/include/Channel/ChannelSocket.hpp +1 -1
  10. package/worker/include/DepUsrSCTP.hpp +8 -7
  11. package/worker/include/RTC/ActiveSpeakerObserver.hpp +7 -7
  12. package/worker/include/RTC/AudioLevelObserver.hpp +7 -7
  13. package/worker/include/RTC/Consumer.hpp +3 -3
  14. package/worker/include/RTC/DataConsumer.hpp +3 -3
  15. package/worker/include/RTC/DataProducer.hpp +3 -3
  16. package/worker/include/RTC/DirectTransport.hpp +2 -2
  17. package/worker/include/RTC/DtlsTransport.hpp +8 -6
  18. package/worker/include/RTC/ICE/IceServer.hpp +8 -5
  19. package/worker/include/RTC/KeyFrameRequestManager.hpp +15 -12
  20. package/worker/include/RTC/NackGenerator.hpp +7 -6
  21. package/worker/include/RTC/PipeConsumer.hpp +1 -2
  22. package/worker/include/RTC/PipeTransport.hpp +2 -2
  23. package/worker/include/RTC/PlainTransport.hpp +2 -2
  24. package/worker/include/RTC/Producer.hpp +3 -3
  25. package/worker/include/RTC/RTP/RtpStream.hpp +7 -1
  26. package/worker/include/RTC/RTP/RtpStreamRecv.hpp +6 -5
  27. package/worker/include/RTC/RTP/RtpStreamSend.hpp +4 -1
  28. package/worker/include/RTC/Router.hpp +3 -3
  29. package/worker/include/RTC/RtpObserver.hpp +3 -3
  30. package/worker/include/RTC/SCTP/TODO_SCTP.md +18 -6
  31. package/worker/include/RTC/SCTP/association/Association.hpp +11 -8
  32. package/worker/include/RTC/SCTP/association/HeartbeatHandler.hpp +9 -6
  33. package/worker/include/RTC/SCTP/association/StreamResetHandler.hpp +37 -23
  34. package/worker/include/RTC/SCTP/association/TCBContext.hpp +3 -2
  35. package/worker/include/RTC/SCTP/association/TransmissionControlBlock.hpp +81 -8
  36. package/worker/include/RTC/SCTP/packet/UserData.hpp +36 -0
  37. package/worker/include/RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp +1 -1
  38. package/worker/include/RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp +1 -1
  39. package/worker/include/RTC/SCTP/public/SctpOptions.hpp +2 -1
  40. package/worker/include/RTC/SCTP/tx/OutstandingData.hpp +604 -0
  41. package/worker/include/RTC/SCTP/tx/RetransmissionQueue.hpp +336 -0
  42. package/worker/include/RTC/SCTP/tx/RetransmissionTimeout.hpp +5 -4
  43. package/worker/include/RTC/Serializable.hpp +8 -0
  44. package/worker/include/RTC/SimpleConsumer.hpp +1 -2
  45. package/worker/include/RTC/SimulcastConsumer.hpp +1 -2
  46. package/worker/include/RTC/SvcConsumer.hpp +1 -2
  47. package/worker/include/RTC/Transport.hpp +8 -8
  48. package/worker/include/RTC/TransportCongestionControlClient.hpp +8 -5
  49. package/worker/include/RTC/TransportCongestionControlServer.hpp +8 -5
  50. package/worker/include/RTC/WebRtcServer.hpp +3 -3
  51. package/worker/include/RTC/WebRtcTransport.hpp +3 -3
  52. package/worker/include/Shared.hpp +40 -0
  53. package/worker/include/SharedInterface.hpp +44 -0
  54. package/worker/include/Utils.hpp +6 -0
  55. package/worker/include/Worker.hpp +3 -3
  56. package/worker/include/common.hpp +1 -1
  57. package/worker/include/handles/BackoffTimerHandle.hpp +27 -65
  58. package/worker/include/handles/BackoffTimerHandleInterface.hpp +116 -0
  59. package/worker/include/handles/TimerHandle.hpp +36 -20
  60. package/worker/include/handles/TimerHandleInterface.hpp +43 -0
  61. package/worker/meson.build +21 -4
  62. package/worker/meson_options.txt +2 -1
  63. package/worker/mocks/include/Channel/MockChannelMessageRegistrator.hpp +45 -0
  64. package/worker/mocks/include/MockShared.hpp +43 -0
  65. package/worker/mocks/src/Channel/MockChannelMessageRegistrator.cpp +128 -0
  66. package/worker/mocks/src/MockShared.cpp +26 -0
  67. package/worker/scripts/clang-scripts.mjs +4 -1
  68. package/worker/src/Channel/ChannelMessageRegistrator.cpp +125 -0
  69. package/worker/src/Channel/ChannelSocket.cpp +1 -1
  70. package/worker/src/DepUsrSCTP.cpp +10 -4
  71. package/worker/src/RTC/ActiveSpeakerObserver.cpp +7 -7
  72. package/worker/src/RTC/AudioLevelObserver.cpp +12 -10
  73. package/worker/src/RTC/Consumer.cpp +23 -20
  74. package/worker/src/RTC/DataConsumer.cpp +11 -11
  75. package/worker/src/RTC/DataProducer.cpp +3 -3
  76. package/worker/src/RTC/DirectTransport.cpp +16 -16
  77. package/worker/src/RTC/DtlsTransport.cpp +4 -4
  78. package/worker/src/RTC/ICE/IceServer.cpp +4 -3
  79. package/worker/src/RTC/KeyFrameRequestManager.cpp +15 -15
  80. package/worker/src/RTC/NackGenerator.cpp +3 -3
  81. package/worker/src/RTC/PipeConsumer.cpp +5 -4
  82. package/worker/src/RTC/PipeTransport.cpp +3 -3
  83. package/worker/src/RTC/PlainTransport.cpp +10 -9
  84. package/worker/src/RTC/Producer.cpp +30 -28
  85. package/worker/src/RTC/RTCP/FeedbackPsRpsi.cpp +1 -2
  86. package/worker/src/RTC/RTP/RtpStream.cpp +9 -2
  87. package/worker/src/RTC/RTP/RtpStreamRecv.cpp +5 -4
  88. package/worker/src/RTC/RTP/RtpStreamSend.cpp +5 -2
  89. package/worker/src/RTC/Router.cpp +3 -3
  90. package/worker/src/RTC/RtpObserver.cpp +2 -1
  91. package/worker/src/RTC/SCTP/association/Association.cpp +94 -114
  92. package/worker/src/RTC/SCTP/association/HeartbeatHandler.cpp +27 -21
  93. package/worker/src/RTC/SCTP/association/StreamResetHandler.cpp +52 -55
  94. package/worker/src/RTC/SCTP/association/TransmissionControlBlock.cpp +144 -25
  95. package/worker/src/RTC/SCTP/packet/chunks/ForwardTsnChunk.cpp +2 -2
  96. package/worker/src/RTC/SCTP/packet/chunks/IForwardTsnChunk.cpp +2 -2
  97. package/worker/src/RTC/SCTP/tx/OutstandingData.cpp +905 -0
  98. package/worker/src/RTC/SCTP/tx/RetransmissionQueue.cpp +799 -0
  99. package/worker/src/RTC/SCTP/tx/RetransmissionTimeout.cpp +1 -1
  100. package/worker/src/RTC/SctpAssociation.cpp +1 -1
  101. package/worker/src/RTC/SimpleConsumer.cpp +8 -7
  102. package/worker/src/RTC/SimulcastConsumer.cpp +11 -10
  103. package/worker/src/RTC/SvcConsumer.cpp +11 -10
  104. package/worker/src/RTC/Transport.cpp +36 -26
  105. package/worker/src/RTC/TransportCongestionControlClient.cpp +4 -2
  106. package/worker/src/RTC/TransportCongestionControlServer.cpp +4 -3
  107. package/worker/src/RTC/WebRtcServer.cpp +5 -4
  108. package/worker/src/RTC/WebRtcTransport.cpp +39 -26
  109. package/worker/src/Shared.cpp +35 -0
  110. package/worker/src/Worker.cpp +10 -23
  111. package/worker/src/handles/BackoffTimerHandle.cpp +11 -16
  112. package/worker/src/handles/TimerHandle.cpp +5 -4
  113. package/worker/src/lib.cpp +14 -1
  114. package/worker/tasks.py +1 -1
  115. package/worker/test/include/RTC/ICE/iceCommon.hpp +1 -0
  116. package/worker/test/include/RTC/RTP/rtpCommon.hpp +1 -0
  117. package/worker/test/include/RTC/SCTP/sctpCommon.hpp +6 -0
  118. package/worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp +12 -5
  119. package/worker/test/src/RTC/RTP/TestRtpStreamSend.cpp +34 -23
  120. package/worker/test/src/RTC/SCTP/tx/TestOutstandingData.cpp +1196 -0
  121. package/worker/test/src/RTC/SCTP/tx/TestRetransmissionTimeout.cpp +33 -33
  122. package/worker/test/src/RTC/TestKeyFrameRequestManager.cpp +14 -6
  123. package/worker/test/src/RTC/TestNackGenerator.cpp +6 -2
  124. package/worker/test/src/RTC/TestSimpleConsumer.cpp +6 -10
  125. package/worker/test/src/RTC/TestTransportCongestionControlServer.cpp +9 -2
  126. package/worker/test/src/Utils/TestByte.cpp +98 -0
  127. package/worker/include/ChannelMessageRegistrator.hpp +0 -30
  128. package/worker/include/RTC/Shared.hpp +0 -23
  129. package/worker/src/ChannelMessageRegistrator.cpp +0 -119
  130. package/worker/src/RTC/Shared.cpp +0 -23
@@ -0,0 +1,799 @@
1
+ #define MS_CLASS "RTC::SCTP::RetransmissionQueue"
2
+ // TODO: SCTP: Comment.
3
+ #define MS_LOG_DEV_LEVEL 3
4
+
5
+ #include "RTC/SCTP/tx/RetransmissionQueue.hpp"
6
+ #include "Logger.hpp"
7
+ #include "Utils.hpp"
8
+ #include "RTC/SCTP/packet/chunks/DataChunk.hpp"
9
+ #include "RTC/SCTP/packet/chunks/IDataChunk.hpp"
10
+ #include <cmath> // std::min()
11
+ #include <numeric> // std::accumulate()
12
+ #include <string>
13
+
14
+ namespace RTC
15
+ {
16
+ namespace SCTP
17
+ {
18
+ /* Instance methods. */
19
+
20
+ RetransmissionQueue::RetransmissionQueue(
21
+ Listener* listener,
22
+ AssociationListener& associationListener,
23
+ uint32_t localInitialTsn,
24
+ uint32_t remoteAdvertisedReceiverWindowCredit,
25
+ // TODO: SCTP: Implement
26
+ // SendQueue& sendQueue,
27
+ BackoffTimerHandleInterface* t3RtxTimer,
28
+ const SctpOptions& sctpOptions,
29
+ // NOTE: I don't like default argument values in dcsctp (true and false),
30
+ // let's be explicit.
31
+ bool supportsPartialReliability,
32
+ bool useMessageInterleaving)
33
+ : listener(listener),
34
+ associationListener(associationListener),
35
+ sctpOptions(sctpOptions),
36
+ supportsPartialReliability(supportsPartialReliability),
37
+ dataChunkHeaderLength(
38
+ useMessageInterleaving ? IDataChunk::IDataChunkHeaderLength
39
+ : DataChunk::DataChunkHeaderLength),
40
+ t3RtxTimer(t3RtxTimer),
41
+ cwnd(sctpOptions.initialCwndMtus * sctpOptions.mtu),
42
+ rwnd(remoteAdvertisedReceiverWindowCredit),
43
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.1
44
+ //
45
+ // "The initial value of ssthresh MAY be arbitrarily high (for example,
46
+ // implementations MAY use the size of the receiver advertised window)."
47
+ ssthresh(this->rwnd),
48
+ // TODO: SCTP: Implement.
49
+ // sendQueue(sendQueue),
50
+ outstandingData(
51
+ this->dataChunkHeaderLength,
52
+ this->tsnUnwrapper.Unwrap(localInitialTsn - 1),
53
+ [/*this*/](uint16_t /*streamId*/, uint32_t /*outgoingMessageId*/)
54
+ {
55
+ // TODO: SCTP: Implement.
56
+ // return this->sendQueue.Discard(streamId, outgoingMessageId);
57
+
58
+ // TODO: SCTP: Remove when the above is uncommented.
59
+ return false;
60
+ })
61
+ {
62
+ MS_TRACE();
63
+ }
64
+
65
+ RetransmissionQueue::~RetransmissionQueue()
66
+ {
67
+ MS_TRACE();
68
+ }
69
+
70
+ bool RetransmissionQueue::HandleReceivedSackChunk(uint64_t nowMs, const SackChunk* receivedSackChunk)
71
+ {
72
+ MS_TRACE();
73
+
74
+ if (!IsSackChunkValid(receivedSackChunk))
75
+ {
76
+ return false;
77
+ }
78
+
79
+ const UnwrappedTsn oldLastCumulativeTsnAck = this->outstandingData.GetLastCumulativeTsnAck();
80
+ const size_t oldUnackedPacketBytes = this->outstandingData.GetUnackedPacketBytes();
81
+ #if MS_LOG_DEV_LEVEL == 3
82
+ const size_t oldRwnd = this->rwnd;
83
+ #endif
84
+ const UnwrappedTsn cumulativeTsnAck =
85
+ this->tsnUnwrapper.Unwrap(receivedSackChunk->GetCumulativeTsnAck());
86
+
87
+ if (receivedSackChunk->GetValidatedGapAckBlocks().empty())
88
+ {
89
+ UpdateRttMs(nowMs, cumulativeTsnAck);
90
+ }
91
+
92
+ // Exit fast recovery before continuing processing, in case it needs to go
93
+ // into fast recovery again due to new reported packet loss.
94
+ MayExitFastRecovery(cumulativeTsnAck);
95
+
96
+ const OutstandingData::AckInfo ackInfo = this->outstandingData.HandleSack(
97
+ cumulativeTsnAck, receivedSackChunk->GetValidatedGapAckBlocks(), IsInFastRecovery());
98
+
99
+ // Add lifecycle events for delivered messages.
100
+ for (const uint64_t lifecycleId : ackInfo.ackedLifecycleIds)
101
+ {
102
+ MS_DEBUG_TAG(
103
+ sctp,
104
+ "triggering OnAssociationLifecycleMessageDelivered() [lifecycleId:%" PRIu64 "]",
105
+ lifecycleId);
106
+
107
+ this->associationListener.OnAssociationLifecycleMessageDelivered(lifecycleId);
108
+ this->associationListener.OnAssociationLifecycleMessageEnd(lifecycleId);
109
+ }
110
+
111
+ for (const uint64_t lifecycleId : ackInfo.abandonedLifecycleIds)
112
+ {
113
+ MS_DEBUG_TAG(
114
+ sctp,
115
+ "triggering OnLifecycleMessageExpired() [lifecycleId:%" PRIu64 ", maybeDelivered:true]",
116
+ lifecycleId);
117
+
118
+ this->associationListener.OnAssociationLifecycleMessageExpired(
119
+ lifecycleId, /*maybeDelivered*/ true);
120
+ this->associationListener.OnAssociationLifecycleMessageEnd(lifecycleId);
121
+ }
122
+
123
+ // Update of this->outstandingData is now done. Congestion control remains.
124
+ UpdateReceiverWindow(receivedSackChunk->GetAdvertisedReceiverWindowCredit());
125
+
126
+ MS_DEBUG_DEV(
127
+ "Received SACK [cumulativeTsnAck:%" PRIu32 ", oldLastCumulativeTsnAck:%" PRIu32
128
+ ", unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu, rwnd:%zu, oldRwnd:%zu]",
129
+ cumulativeTsnAck.Wrap(),
130
+ oldLastCumulativeTsnAck.Wrap(),
131
+ this->outstandingData.GetUnackedPacketBytes(),
132
+ oldUnackedPacketBytes,
133
+ this->rwnd,
134
+ oldRwnd);
135
+
136
+ if (cumulativeTsnAck > oldLastCumulativeTsnAck)
137
+ {
138
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.2
139
+ //
140
+ // "Whenever a SACK is received that acknowledges the DATA chunk with
141
+ // the earliest outstanding TSN for that address, restart the T3-rtx
142
+ // timer for that address with its current RTO (if there is still
143
+ // outstanding data on that address)."
144
+ this->t3RtxTimer->Stop();
145
+
146
+ HandleIncreasedCumulativeTsnAck(oldUnackedPacketBytes, ackInfo.bytesAcked);
147
+ }
148
+
149
+ if (ackInfo.hasPacketLoss)
150
+ {
151
+ HandlePacketLoss(ackInfo.highestTsnAcked);
152
+ }
153
+
154
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-8.2
155
+ //
156
+ // "When an outstanding TSN is acknowledged [...] the endpoint shall clear
157
+ // the error counter ...".
158
+ if (ackInfo.bytesAcked > 0)
159
+ {
160
+ this->listener->OnRetransmissionQueueClearRetransmissionCounter();
161
+ }
162
+
163
+ StartT3RtxTimerIfOutstandingData();
164
+
165
+ return true;
166
+ }
167
+
168
+ void RetransmissionQueue::HandleT3RtxTimerExpiry()
169
+ {
170
+ MS_TRACE();
171
+
172
+ const size_t oldCwnd = this->cwnd;
173
+ const size_t oldUnackedPacketBytes = GetUnackedPacketBytes();
174
+
175
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3
176
+ //
177
+ // "For the destination address for which the timer expires, adjust
178
+ // its ssthresh with rules defined in Section 7.2.3 and set the cwnd
179
+ // <- MTU."
180
+ this->ssthresh = std::max(this->cwnd / 2, 4 * this->sctpOptions.mtu);
181
+ this->cwnd = 1 * this->sctpOptions.mtu;
182
+
183
+ // Errata: https://datatracker.ietf.org/doc/html/rfc8540#section-3.11
184
+ this->partialBytesAcked = 0;
185
+
186
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3
187
+ //
188
+ // "For the destination address for which the timer expires, set RTO
189
+ // <- RTO * 2 ("back off the timer"). The maximum value discussed in
190
+ // rule C7 above (RTO.max) may be used to provide an upper bound to this
191
+ // doubling operation."
192
+
193
+ // Already done by the BackoffTimerHandle implementation.
194
+
195
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3
196
+ //
197
+ // "Determine how many of the earliest (i.e., lowest TSN) outstanding
198
+ // DATA chunks for the address for which the T3-rtx has expired will fit
199
+ // into a single Packet"
200
+
201
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3
202
+ //
203
+ // "Note: Any DATA chunks that were sent to the address for which the
204
+ // T3-rtx timer expired but did not fit in one MTU (rule E3 above) should
205
+ // be marked for retransmission and sent as soon as cwnd allows (normally,
206
+ // when a SACK arrives)."
207
+ this->outstandingData.NackAll();
208
+
209
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3
210
+ //
211
+ // "Start the retransmission timer T3-rtx on the destination address to
212
+ // which the retransmission is sent, if rule R1 above indicates to do so."
213
+
214
+ // Already done by the BackoffTimerHandle implementation.
215
+
216
+ MS_DEBUG_TAG(
217
+ sctp,
218
+ "T3-rtx timer has expired [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu]",
219
+ this->cwnd,
220
+ oldCwnd,
221
+ this->ssthresh,
222
+ GetUnackedPacketBytes(),
223
+ oldUnackedPacketBytes);
224
+ }
225
+
226
+ std::vector<std::pair<uint32_t /*tsn*/, UserData>> RetransmissionQueue::GetChunksForFastRetransmit(
227
+ size_t maxLength)
228
+ {
229
+ MS_TRACE();
230
+
231
+ MS_ASSERT(
232
+ this->outstandingData.HasDataToBeFastRetransmitted(), "no data to be fast-retransmitted");
233
+ MS_ASSERT(
234
+ Utils::Byte::IsPaddedTo4Bytes(maxLength),
235
+ "given maxLength %zu is not divisible by 4",
236
+ maxLength);
237
+
238
+ std::vector<std::pair<uint32_t /*tsn*/, UserData>> result;
239
+
240
+ #if MS_LOG_DEV_LEVEL == 3
241
+ const size_t oldUnackedPacketBytes = GetUnackedPacketBytes();
242
+ #endif
243
+
244
+ result = this->outstandingData.GetChunksToBeFastRetransmitted(maxLength);
245
+
246
+ MS_ASSERT(!result.empty(), "result cannot be empty");
247
+
248
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4
249
+ //
250
+ // "4) Restart the T3-rtx timer only if ... the endpoint is retransmitting
251
+ // the first outstanding DATA chunk sent to that address."
252
+ if (result[0].first == this->outstandingData.GetLastCumulativeTsnAck().GetNextValue().Wrap())
253
+ {
254
+ MS_DEBUG_DEV("first outstanding data to be retransmitted, restarting T3-rtx timer");
255
+
256
+ this->t3RtxTimer->Stop();
257
+ }
258
+
259
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.2
260
+ //
261
+ // "Every time a DATA chunk is sent to any address (including a
262
+ // retransmission), if the T3-rtx timer of that address is not running,
263
+ // start it running so that it will expire after the RTO of that address."
264
+ if (!this->t3RtxTimer->IsRunning())
265
+ {
266
+ this->t3RtxTimer->Start();
267
+ }
268
+
269
+ const size_t bytesRetransmitted = std::accumulate(
270
+ result.begin(),
271
+ result.end(),
272
+ size_t{ 0 },
273
+ [&](size_t r, const std::pair<uint32_t /*tsn*/, UserData>& data)
274
+ {
275
+ return r + GetSerializedChunkLength(data.second);
276
+ });
277
+
278
+ ++this->rtxPacketsCount;
279
+ this->rtxBytesCount += bytesRetransmitted;
280
+
281
+ #if MS_LOG_DEV_LEVEL == 3
282
+ std::string tsnList;
283
+
284
+ for (const auto& [tsn, data] : result)
285
+ {
286
+ if (!tsnList.empty())
287
+ {
288
+ tsnList += ',';
289
+ }
290
+
291
+ tsnList += std::to_string(tsn);
292
+ }
293
+
294
+ MS_DEBUG_DEV(
295
+ "fast-retransmitting TSN %s - %zu bytes [unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu]",
296
+ tsnList.c_str(),
297
+ bytesRetransmitted,
298
+ GetUnackedPacketBytes(),
299
+ oldUnackedPacketBytes);
300
+ #endif
301
+
302
+ return result;
303
+ }
304
+
305
+ std::vector<std::pair<uint32_t /*tsn*/, UserData>> RetransmissionQueue::GetChunksToSend(
306
+ uint64_t /*nowMs*/, size_t maxLength)
307
+ {
308
+ MS_TRACE();
309
+
310
+ MS_ASSERT(
311
+ Utils::Byte::IsPaddedTo4Bytes(maxLength),
312
+ "given maxLength %zu is not divisible by 4",
313
+ maxLength);
314
+
315
+ std::vector<std::pair<uint32_t /*tsn*/, UserData>> result;
316
+
317
+ const size_t oldUnackedPacketBytes = GetUnackedPacketBytes();
318
+ #if MS_LOG_DEV_LEVEL == 3
319
+ const size_t oldRwnd = this->rwnd;
320
+ #endif
321
+
322
+ // Calculate the bandwidth budget (how many bytes that is allowed to be
323
+ // sent).
324
+ const size_t maxPacketBytesAllowedByCwnd =
325
+ oldUnackedPacketBytes >= this->cwnd ? 0 : this->cwnd - oldUnackedPacketBytes;
326
+
327
+ size_t maxPacketBytesAllowedByRwnd =
328
+ Utils::Byte::PadTo4Bytes(GetRwnd() + this->dataChunkHeaderLength);
329
+
330
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-6.1
331
+ //
332
+ // "However, regardless of the value of rwnd (including if it is 0), the
333
+ // data sender can always have one DATA chunk in flight to the receiver if
334
+ // allowed by cwnd (see rule B, below)."
335
+ if (this->outstandingData.GetUnackedItems() == 0)
336
+ {
337
+ maxPacketBytesAllowedByRwnd = this->sctpOptions.mtu;
338
+ }
339
+
340
+ size_t maxBytes = Utils::Byte::PadDownTo4Bytes(
341
+ std::min({ maxPacketBytesAllowedByCwnd, maxPacketBytesAllowedByRwnd, maxLength }));
342
+
343
+ result = this->outstandingData.GetChunksToBeRetransmitted(maxBytes);
344
+
345
+ const size_t bytesRetransmitted = std::accumulate(
346
+ result.begin(),
347
+ result.end(),
348
+ size_t{ 0 },
349
+ [&](size_t r, const std::pair<uint32_t /*tsn*/, UserData>& data)
350
+ {
351
+ return r + GetSerializedChunkLength(data.second);
352
+ });
353
+
354
+ maxBytes -= bytesRetransmitted;
355
+
356
+ if (!result.empty())
357
+ {
358
+ ++this->rtxPacketsCount;
359
+ this->rtxBytesCount += bytesRetransmitted;
360
+ }
361
+
362
+ while (maxBytes > this->dataChunkHeaderLength)
363
+ {
364
+ MS_ASSERT(
365
+ Utils::Byte::IsPaddedTo4Bytes(maxBytes),
366
+ "computed maxBytes %zu during the loop is not divisible by 4",
367
+ maxBytes);
368
+
369
+ // TODO: SCTP: Implement and uncomment.
370
+
371
+ // std::optional<SendQueue::DataToSend> chunkOpt =
372
+ // this->sendQueue.Produce(nowMs, maxBytes - this->dataChunkHeaderLength);
373
+
374
+ // if (!chunkOpt.has_value())
375
+ // {
376
+ // break;
377
+ // }
378
+
379
+ // const size_t chunkSize = GetSerializedChunkSize(chunkOpt->data);
380
+
381
+ // maxBytes -= chunkSize;
382
+
383
+ // this->rwnd -= chunkOpt->data.size();
384
+
385
+ // const std::optional<UnwrappedTsn> tsn = this->outstandingData.Insert(
386
+ // chunkOpt->messageId,
387
+ // chunkOpt->data,
388
+ // nowMs,
389
+ // this->supportsPartialReliability ? chunkOpt->maxRetransmissions
390
+ // : OutstandingData::MaxRetransmitsNoLimit,
391
+ // this->supportsPartialReliability ? chunkOpt->expiresAtMs
392
+ // : OutstandingData::ExpiresAtMsInfinite,
393
+ // chunkOpt->lifecycleId);
394
+
395
+ // if (tsn.has_value())
396
+ // {
397
+ // if (chunkOpt->lifecycleId != 0)
398
+ // {
399
+ // MS_ASSERT(chunkOpt->data.IsEnd(), "data.IsEnd() should return true");
400
+
401
+ // this->associationListener.OnAssociationLifecycleMessageFullySent(chunkOpt->lifecycleId);
402
+ // }
403
+
404
+ // result.emplace_back(tsn->Wrap(), std::move(chunkOpt->data));
405
+ // }
406
+ }
407
+
408
+ // https://tools.ietf.org/html/rfc9260#section-6.3.2
409
+ //
410
+ // "Every time a DATA chunk is sent to any address (including a
411
+ // retransmission), if the T3-rtx timer of that address is not running,
412
+ // start it running so that it will expire after the RTO of that address."
413
+ if (!result.empty())
414
+ {
415
+ if (!this->t3RtxTimer->IsRunning())
416
+ {
417
+ this->t3RtxTimer->Start();
418
+ }
419
+
420
+ #if MS_LOG_DEV_LEVEL == 3
421
+ std::string tsnList;
422
+
423
+ for (const auto& [tsn, data] : result)
424
+ {
425
+ if (!tsnList.empty())
426
+ {
427
+ tsnList += ',';
428
+ }
429
+
430
+ tsnList += std::to_string(tsn);
431
+ }
432
+
433
+ const size_t bytesRetransmitted = std::accumulate(
434
+ result.begin(),
435
+ result.end(),
436
+ size_t{ 0 },
437
+ [&](size_t r, const std::pair<uint32_t, UserData>& d)
438
+ {
439
+ return r + GetSerializedChunkLength(d.second);
440
+ });
441
+
442
+ MS_DEBUG_DEV(
443
+ "sending TSN %s - %zu bytes [unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu, cwnd:%zu, rwnd:%zu, oldRwnd:%zu]",
444
+ tsnList.c_str(),
445
+ bytesRetransmitted,
446
+ GetUnackedPacketBytes(),
447
+ oldUnackedPacketBytes,
448
+ cwnd,
449
+ rwnd,
450
+ oldRwnd);
451
+ #endif
452
+ }
453
+
454
+ return result;
455
+ }
456
+
457
+ bool RetransmissionQueue::ShouldSendForwardTsn(uint64_t nowMs)
458
+ {
459
+ MS_TRACE();
460
+
461
+ if (!this->supportsPartialReliability)
462
+ {
463
+ return false;
464
+ }
465
+
466
+ this->outstandingData.ExpireOutstandingChunks(nowMs);
467
+
468
+ return this->outstandingData.ShouldSendForwardTsn();
469
+ }
470
+
471
+ void RetransmissionQueue::PrepareResetStream(uint16_t /*streamId*/)
472
+ {
473
+ MS_TRACE();
474
+
475
+ // TODO: As per TODO comment in same method in dcsctp:
476
+ //
477
+ // "These calls are now only affecting the send queue. The packet buffer
478
+ // can also change behavior - for example draining the chunk producer and
479
+ // eagerly assign TSNs so that an "Outgoing SSN Reset Request" can be sent
480
+ // quickly, with a known `sender_last_assigned_tsn`.
481
+ // TODO: SCTP: Implement it.
482
+ // this->sendQueue.PrepareResetStream(streamId);
483
+ }
484
+
485
+ bool RetransmissionQueue::HasStreamsReadyToBeReset() const
486
+ {
487
+ MS_TRACE();
488
+
489
+ // TODO: SCTP: Implement it.
490
+ // return this->sendQueue.HasStreamsReadyToBeReset();
491
+
492
+ // TODO: SCTP: Remove.
493
+ return false;
494
+ }
495
+
496
+ std::vector<uint16_t /*streamId*/> RetransmissionQueue::BeginResetStreams()
497
+ {
498
+ MS_TRACE();
499
+
500
+ this->outstandingData.BeginResetStreams();
501
+
502
+ // TODO: SCTP: Implement it.
503
+ // this->sendQueue.GetStreamsReadyToBeReset();
504
+
505
+ // TODO: SCTP: Remove.
506
+ return {};
507
+ }
508
+
509
+ void RetransmissionQueue::CommitResetStreams()
510
+ {
511
+ MS_TRACE();
512
+
513
+ // TODO: SCTP: Implement it.
514
+ // this->sendQueue.CommitResetStreams();
515
+ }
516
+
517
+ void RetransmissionQueue::RollbackResetStreams()
518
+ {
519
+ MS_TRACE();
520
+
521
+ // TODO: SCTP: Implement it.
522
+ // this->sendQueue.RollbackResetStreams();
523
+ }
524
+
525
+ size_t RetransmissionQueue::GetSerializedChunkLength(const UserData& data) const
526
+ {
527
+ MS_TRACE();
528
+
529
+ return Utils::Byte::PadTo4Bytes(this->dataChunkHeaderLength + data.GetPayloadLength());
530
+ }
531
+
532
+ bool RetransmissionQueue::IsSackChunkValid(const SackChunk* sackChunk) const
533
+ {
534
+ MS_TRACE();
535
+
536
+ // https://tools.ietf.org/html/rfc9260#section-6.2.1
537
+ //
538
+ // "If Cumulative TSN Ack is less than the Cumulative TSN Ack Point, then
539
+ // drop the SACK. Since Cumulative TSN Ack is monotonically increasing, a
540
+ // SACK whose Cumulative TSN Ack is less than the Cumulative TSN Ack Point
541
+ // indicates an out-of- order SACK."
542
+ //
543
+ // @remarks
544
+ // - Important not to drop SACKs with identical TSN to that previously
545
+ // received, as the gap-ack-blocks or dup tsn fields may have changed.
546
+ const UnwrappedTsn cumulativeTsnAck =
547
+ this->tsnUnwrapper.PeekUnwrap(sackChunk->GetCumulativeTsnAck());
548
+
549
+ if (cumulativeTsnAck < this->outstandingData.GetLastCumulativeTsnAck())
550
+ {
551
+ // https://tools.ietf.org/html/rfc9260#section-6.2.1
552
+ //
553
+ // "If Cumulative TSN Ack is less than the Cumulative TSN Ack Point,
554
+ // then drop the SACK. Since Cumulative TSN Ack is monotonically
555
+ // increasing, a SACK whose Cumulative TSN Ack is less than the
556
+ // Cumulative TSN Ack Point indicates an out-of- order SACK."
557
+ return false;
558
+ }
559
+ else if (cumulativeTsnAck > this->outstandingData.GetHighestOutstandingTsn())
560
+ {
561
+ return false;
562
+ }
563
+
564
+ return std::ranges::all_of(
565
+ sackChunk->GetValidatedGapAckBlocks(),
566
+ [&](const auto& block)
567
+ {
568
+ return UnwrappedTsn::AddTo(cumulativeTsnAck, block.end) <=
569
+ this->outstandingData.GetHighestOutstandingTsn();
570
+ });
571
+ }
572
+
573
+ void RetransmissionQueue::UpdateRttMs(uint64_t nowMs, UnwrappedTsn cumulativeTsnAck)
574
+ {
575
+ MS_TRACE();
576
+
577
+ // RTT updating is flawed in SCTP, as explained in e.g. Pedersen J, Griwodz C,
578
+ // Halvorsen P (2006) Considerations of SCTP retransmission delays for thin
579
+ // streams.
580
+ // Due to delayed acknowledgement, the SACK may be sent much later which
581
+ // increases the calculated RTT.
582
+ //
583
+ // TODO: As per TODO comment in same method in dcsctp:
584
+ //
585
+ // "Consider occasionally sending DATA chunks with I-bit set and use only
586
+ // those packets for measurement.
587
+
588
+ const auto rttMs = this->outstandingData.MeasureRtt(nowMs, cumulativeTsnAck);
589
+
590
+ if (rttMs.has_value())
591
+ {
592
+ this->listener->OnRetransmissionQueueNewRttMs(rttMs.value());
593
+ }
594
+ }
595
+
596
+ void RetransmissionQueue::MayExitFastRecovery(UnwrappedTsn cumulativeTsnAck)
597
+ {
598
+ MS_TRACE();
599
+
600
+ // https://tools.ietf.org/html/rfc9260#section-7.2.4
601
+ //
602
+ // "When a SACK acknowledges all TSNs up to and including this [fast
603
+ // recovery] exit point, Fast Recovery is exited."
604
+ if (this->fastRecoveryExitTsn.has_value() && cumulativeTsnAck >= this->fastRecoveryExitTsn.value())
605
+ {
606
+ MS_DEBUG_DEV(
607
+ "exit point %" PRIu32 " reached, exiting fast recovery",
608
+ this->fastRecoveryExitTsn.value().Wrap());
609
+
610
+ this->fastRecoveryExitTsn = std::nullopt;
611
+ }
612
+ }
613
+
614
+ void RetransmissionQueue::StopT3RtxTimerOnIncreasedCumulativeTsnAck(UnwrappedTsn /*cumulativeTsnAck*/)
615
+ {
616
+ MS_TRACE();
617
+
618
+ // TODO: This method is NOT defined in dcsctp!
619
+ //
620
+ // @see https://issues.webrtc.org/issues/505751236
621
+ }
622
+
623
+ void RetransmissionQueue::HandleIncreasedCumulativeTsnAck(
624
+ size_t unackedPacketBytes, size_t totalBytesAcked)
625
+ {
626
+ MS_TRACE();
627
+
628
+ // Allow some margin for classifying as fully utilized, due to e.g. that
629
+ // too small packets (less than kMinimumFragmentedPayload) are not sent +
630
+ // overhead.
631
+ const bool isFullyUtilized = unackedPacketBytes + this->sctpOptions.mtu >= this->cwnd;
632
+ #if MS_LOG_DEV_LEVEL == 3
633
+ const size_t oldCwnd = this->cwnd;
634
+ #endif
635
+
636
+ if (GetCongestionAlgorithmPhase() == CongestionAlgorithmPhase::SLOW_START)
637
+ {
638
+ if (isFullyUtilized && !IsInFastRecovery())
639
+ {
640
+ // https://tools.ietf.org/html/rfc9260#section-7.2.1
641
+ //
642
+ // "Only when these three conditions are met can the cwnd be
643
+ // increased; otherwise, the cwnd MUST not be increased. If these
644
+ // conditions are met, then cwnd MUST be increased by, at most, the
645
+ // lesser of 1) the total size of the previously outstanding DATA
646
+ // chunk(s) acknowledged, and 2) the destination's path MTU."
647
+ this->cwnd += std::min(totalBytesAcked, this->sctpOptions.mtu);
648
+
649
+ MS_DEBUG_DEV("SS increase [cwnd:%zu, oldCwnd:%zu]", this->cwnd, oldCwnd);
650
+ }
651
+ }
652
+ else if (GetCongestionAlgorithmPhase() == CongestionAlgorithmPhase::CONGESTION_AVOIDANCE)
653
+ {
654
+ // https://tools.ietf.org/html/rfc9260#section-7.2.2
655
+ //
656
+ // "Whenever cwnd is greater than ssthresh, upon each SACK arrival
657
+ // that advances the Cumulative TSN Ack Point, increase
658
+ // partial_bytes_acked by the total number of bytes of all new chunks
659
+ // acknowledged in that SACK including chunks acknowledged by the new
660
+ // Cumulative TSN Ack and by Gap Ack Blocks."
661
+ #if MS_LOG_DEV_LEVEL == 3
662
+ const size_t oldPba = this->partialBytesAcked;
663
+ #endif
664
+
665
+ this->partialBytesAcked += totalBytesAcked;
666
+
667
+ if (this->partialBytesAcked >= this->cwnd && isFullyUtilized)
668
+ {
669
+ // https://tools.ietf.org/html/rfc9260#section-7.2.2
670
+ //
671
+ // "When partial_bytes_acked is equal to or greater than cwnd and
672
+ // before the arrival of the SACK the sender had cwnd or more bytes of
673
+ // data outstanding (i.e., before arrival of the SACK, flightsize was
674
+ // greater than or equal to cwnd), increase cwnd by MTU, and reset
675
+ // partial_bytes_acked to (partial_bytes_acked - cwnd)."
676
+
677
+ // Errata: https://datatracker.ietf.org/doc/html/rfc8540#section-3.12
678
+ this->partialBytesAcked -= this->cwnd;
679
+ this->cwnd += this->sctpOptions.mtu;
680
+
681
+ MS_DEBUG_DEV(
682
+ "CA increase [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]",
683
+ this->cwnd,
684
+ oldCwnd,
685
+ this->ssthresh,
686
+ this->partialBytesAcked,
687
+ oldPba);
688
+ }
689
+ else
690
+ {
691
+ MS_DEBUG_DEV(
692
+ "CA unchanged [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]",
693
+ this->cwnd,
694
+ oldCwnd,
695
+ this->ssthresh,
696
+ this->partialBytesAcked,
697
+ oldPba);
698
+ }
699
+ }
700
+ }
701
+
702
+ void RetransmissionQueue::HandlePacketLoss(UnwrappedTsn /*highestTsnAcked*/)
703
+ {
704
+ MS_TRACE();
705
+
706
+ // https://tools.ietf.org/html/rfc9260#section-7.2.4
707
+ //
708
+ // "If not in Fast Recovery, adjust the ssthresh and cwnd of the
709
+ // destination address(es) to which the missing DATA chunks were last
710
+ // sent, according to the formula described in Section 7.2.3."
711
+ if (!IsInFastRecovery())
712
+ {
713
+ #if MS_LOG_DEV_LEVEL == 3
714
+ const size_t oldCwnd = this->cwnd;
715
+ const size_t oldPba = this->partialBytesAcked;
716
+ #endif
717
+
718
+ this->ssthresh =
719
+ std::max(this->cwnd / 2, this->sctpOptions.minCwndMtus * this->sctpOptions.mtu);
720
+ this->cwnd = this->ssthresh;
721
+ this->partialBytesAcked = 0;
722
+
723
+ MS_DEBUG_DEV(
724
+ "packet loss detected (not fast recovery) [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]",
725
+ this->cwnd,
726
+ oldCwnd,
727
+ this->ssthresh,
728
+ this->partialBytesAcked,
729
+ oldPba);
730
+
731
+ // https://tools.ietf.org/html/rfc9260#section-7.2.4
732
+ //
733
+ // "If not in Fast Recovery, enter Fast Recovery and mark the highest
734
+ // outstanding TSN as the Fast Recovery exit point."
735
+ this->fastRecoveryExitTsn = this->outstandingData.GetHighestOutstandingTsn();
736
+
737
+ MS_DEBUG_DEV(
738
+ "fast recovery initiated with exit point %" PRIu32,
739
+ this->fastRecoveryExitTsn.value().Wrap());
740
+ }
741
+ // https://tools.ietf.org/html/rfc9260#section-7.2.4
742
+ //
743
+ // "While in Fast Recovery, the ssthresh and cwnd SHOULD NOT change for
744
+ // any destinations due to a subsequent Fast Recovery event (i.e., one
745
+ // SHOULD NOT reduce the cwnd further due to a subsequent Fast
746
+ // Retransmit)."
747
+ else
748
+ {
749
+ MS_DEBUG_DEV("packet loss detected (fast recovery), no changes");
750
+ }
751
+ }
752
+
753
+ void RetransmissionQueue::UpdateReceiverWindow(uint32_t aRwnd)
754
+ {
755
+ MS_TRACE();
756
+
757
+ this->rwnd = this->outstandingData.GetUnackedPayloadBytes() >= aRwnd
758
+ ? 0
759
+ : aRwnd - this->outstandingData.GetUnackedPayloadBytes();
760
+ }
761
+
762
+ void RetransmissionQueue::StartT3RtxTimerIfOutstandingData()
763
+ {
764
+ MS_TRACE();
765
+
766
+ // Note: Can't use `GetUnackedPacketBytes()` as that one doesn't count
767
+ // chunks to be retransmitted.
768
+
769
+ // https://tools.ietf.org/html/rfc9260#section-6.3.2
770
+ // "Whenever all outstanding data sent to an address have been
771
+ // acknowledged, turn off the T3-rtx timer of that address.
772
+ if (this->outstandingData.IsEmpty())
773
+ {
774
+ // Note: Already stopped in `StopT3RtxTimerOnIncreasedCumulativeTsnAck()`."
775
+ //
776
+ // TODO: As said above, `StopT3RtxTimerOnIncreasedCumulativeTsnAck()`
777
+ // is NOT defined in dcsctp and of course it's never called from
778
+ // anywhere.
779
+ }
780
+ else
781
+ {
782
+ // https://tools.ietf.org/html/rfc9260#section-6.3.2
783
+ //
784
+ // "Whenever a SACK is received that acknowledges the DATA chunk with
785
+ // the earliest outstanding TSN for that address, restart the T3-rtx
786
+ // timer for that address with its current RTO (if there is still
787
+ // outstanding data on that address)."
788
+ // "Whenever a SACK is received missing a TSN that was previously
789
+ // acknowledged via a Gap Ack Block, start the T3-rtx for the
790
+ // destination address to which the DATA chunk was originally
791
+ // transmitted if it is not already running."
792
+ if (!this->t3RtxTimer->IsRunning())
793
+ {
794
+ this->t3RtxTimer->Start();
795
+ }
796
+ }
797
+ }
798
+ } // namespace SCTP
799
+ } // namespace RTC