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
@@ -0,0 +1,1755 @@
1
+ #include "common.hpp"
2
+ #include "RTC/SCTP/association/Association.hpp"
3
+ #include "RTC/SCTP/packet/ErrorCause.hpp"
4
+ #include "RTC/SCTP/packet/Packet.hpp"
5
+ #include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp"
6
+ #include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp"
7
+ #include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp"
8
+ #include "RTC/SCTP/packet/chunks/DataChunk.hpp"
9
+ #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp"
10
+ #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp"
11
+ #include "RTC/SCTP/packet/chunks/IDataChunk.hpp"
12
+ #include "RTC/SCTP/packet/chunks/InitAckChunk.hpp"
13
+ #include "RTC/SCTP/packet/chunks/InitChunk.hpp"
14
+ #include "RTC/SCTP/packet/chunks/OperationErrorChunk.hpp"
15
+ #include "RTC/SCTP/packet/chunks/SackChunk.hpp"
16
+ #include "RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp"
17
+ #include "RTC/SCTP/packet/chunks/ShutdownChunk.hpp"
18
+ #include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp"
19
+ #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp"
20
+ #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp"
21
+ #include "RTC/SCTP/public/Message.hpp"
22
+ #include "RTC/SCTP/public/SctpOptions.hpp"
23
+ #include "RTC/SCTP/public/SctpTypes.hpp"
24
+ #include "mocks/include/MockShared.hpp"
25
+ #include "mocks/include/RTC/SCTP/association/MockAssociationListener.hpp"
26
+ #include <array>
27
+ #include <catch2/catch_test_macros.hpp>
28
+ #include <span>
29
+ #include <string_view>
30
+ #include <vector>
31
+
32
+ namespace
33
+ {
34
+ // Initial value of the simulated clock (ms).
35
+ constexpr uint64_t InitialNowMs{ 1000000 };
36
+
37
+ // All backoff timer labels an SCTP association may create. Used by `runTimers()`
38
+ // to fire whichever timers have expired after advancing time.
39
+ constexpr std::array<std::string_view, 8> SctpTimerLabels{
40
+ "sctp-t1-init", "sctp-t1-cookie", "sctp-t2-shutdown", "sctp-t3-rtx",
41
+ "sctp-delayed-ack", "sctp-heartbeat-interval", "sctp-heartbeat-timeout", "sctp-re-config",
42
+ };
43
+
44
+ /**
45
+ * Builds the default SctpOptions used in tests. Tweaked to make timers
46
+ * predictable.
47
+ */
48
+ RTC::SCTP::SctpOptions makeSctpOptions()
49
+ {
50
+ RTC::SCTP::SctpOptions sctpOptions;
51
+
52
+ // To make the heartbeat interval more predictable in tests.
53
+ sctpOptions.heartbeatIntervalIncludeRtt = false;
54
+ sctpOptions.maxBurst = 4;
55
+
56
+ return sctpOptions;
57
+ }
58
+
59
+ /**
60
+ * An SCTP Association under test, together with its simulated clock and
61
+ * listener.
62
+ */
63
+ class AssociationUnderTest
64
+ {
65
+ public:
66
+ /**
67
+ * `mayConnectOnReceivedSctpData` defaults to false so that, just like
68
+ * dcsctp, the connection is only initiated by explicitly calling `Connect()`.
69
+ * In production mediasoup associations auto-initiate the connection upon
70
+ * receiving data (both peers are active), but for tests we want to mimic
71
+ * dcsctp's asymmetric handshake (one active peer, one passive peer) so that
72
+ * packet sequences and counts match.
73
+ */
74
+ explicit AssociationUnderTest(
75
+ RTC::SCTP::SctpOptions sctpOptions = makeSctpOptions(),
76
+ bool mayConnectOnReceivedSctpData = false)
77
+ // NOTE: The order in which these members are initialized is **critical**.
78
+ : sctpOptions(sctpOptions),
79
+ shared(/*getTimeMs*/
80
+ [this]()
81
+ {
82
+ return this->nowMs;
83
+ }),
84
+ association(
85
+ this->sctpOptions,
86
+ std::addressof(this->listener),
87
+ std::addressof(this->shared),
88
+ /*isDataChannel*/ true,
89
+ mayConnectOnReceivedSctpData)
90
+ {
91
+ }
92
+
93
+ /**
94
+ * Advances the simulated clock of this association by `incrementMs`.
95
+ */
96
+ void AdvanceTimeMs(uint64_t incrementMs)
97
+ {
98
+ this->nowMs += incrementMs;
99
+ }
100
+
101
+ public:
102
+ uint64_t nowMs{ InitialNowMs };
103
+ RTC::SCTP::SctpOptions sctpOptions;
104
+ mocks::RTC::SCTP::MockAssociationListener listener;
105
+ mocks::MockShared shared;
106
+ RTC::SCTP::Association association;
107
+ };
108
+
109
+ /**
110
+ * Parses a previously consumed sent packet buffer into a `SCTP::Packet`.
111
+ */
112
+ std::unique_ptr<RTC::SCTP::Packet> parsePacket(const std::vector<uint8_t>& buffer)
113
+ {
114
+ return std::unique_ptr<RTC::SCTP::Packet>(RTC::SCTP::Packet::Parse(buffer.data(), buffer.size()));
115
+ }
116
+
117
+ /**
118
+ * Returns true if the packet contains a single chunk of the given type.
119
+ */
120
+ template<typename ChunkType>
121
+ bool packetHasSingleChunkOfType(const std::vector<uint8_t>& buffer)
122
+ {
123
+ const auto packet = parsePacket(buffer);
124
+
125
+ return packet && packet->GetChunksCount() == 1 &&
126
+ packet->GetFirstChunkOfType<ChunkType>() != nullptr;
127
+ }
128
+
129
+ /**
130
+ * Returns true if the packet contains a DATA or I-DATA chunk.
131
+ */
132
+ bool packetHasDataChunk(const std::vector<uint8_t>& buffer)
133
+ {
134
+ const auto packet = parsePacket(buffer);
135
+
136
+ return packet && (packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() != nullptr ||
137
+ packet->GetFirstChunkOfType<RTC::SCTP::IDataChunk>() != nullptr);
138
+ }
139
+
140
+ /**
141
+ * Delivers a single sent packet from `from` to `to`. The packet must exist.
142
+ */
143
+ void deliverFirstSentPacket(AssociationUnderTest& from, AssociationUnderTest& to)
144
+ {
145
+ const auto buffer = from.listener.ConsumeFirstSentPacket();
146
+
147
+ REQUIRE(!buffer.empty());
148
+
149
+ to.association.ReceiveSctpData(buffer.data(), buffer.size());
150
+ }
151
+
152
+ /**
153
+ * Delivers all currently queued packets between `a` and `z` back and forth
154
+ * until neither has anything more to send.
155
+ */
156
+ void exchangeMessages(AssociationUnderTest& a, AssociationUnderTest& z)
157
+ {
158
+ bool deliveredPacket{ false };
159
+
160
+ do
161
+ {
162
+ deliveredPacket = false;
163
+
164
+ const auto bufferFromA = a.listener.ConsumeFirstSentPacket();
165
+
166
+ if (!bufferFromA.empty())
167
+ {
168
+ deliveredPacket = true;
169
+ z.association.ReceiveSctpData(bufferFromA.data(), bufferFromA.size());
170
+ }
171
+
172
+ const auto bufferFromZ = z.listener.ConsumeFirstSentPacket();
173
+
174
+ if (!bufferFromZ.empty())
175
+ {
176
+ deliveredPacket = true;
177
+ a.association.ReceiveSctpData(bufferFromZ.data(), bufferFromZ.size());
178
+ }
179
+ } while (deliveredPacket);
180
+ }
181
+
182
+ /**
183
+ * Fires every Association backoff timer that has expired, looping until none
184
+ * remain expired.
185
+ */
186
+ void runTimers(AssociationUnderTest& s)
187
+ {
188
+ bool fired{ false };
189
+
190
+ do
191
+ {
192
+ fired = false;
193
+
194
+ for (const auto label : SctpTimerLabels)
195
+ {
196
+ auto* timer = s.shared.GetBackoffTimer(label);
197
+
198
+ // NOTE: We must check `IsRunning()` because MockBackoffTimerHandle's
199
+ // `EvaluateHasExpired()` only compares times. A stopped timer whose
200
+ // expiry time is in the past would otherwise be fired again and again.
201
+ if (timer && timer->IsRunning() && timer->EvaluateHasExpired())
202
+ {
203
+ fired = true;
204
+ }
205
+ }
206
+ } while (fired);
207
+ }
208
+
209
+ /**
210
+ * Advances the simulated clock of both associations by `durationMs` and fires
211
+ * any timer that has expired.
212
+ */
213
+ void advanceTimeMs(AssociationUnderTest& a, AssociationUnderTest& z, uint64_t durationMs)
214
+ {
215
+ a.AdvanceTimeMs(durationMs);
216
+ z.AdvanceTimeMs(durationMs);
217
+
218
+ runTimers(a);
219
+ runTimers(z);
220
+ }
221
+
222
+ /**
223
+ * Calls `Connect()` on `a` (the active peer) and drives the handshake to
224
+ * completion against `z` (the passive peer).
225
+ */
226
+ void connectAssociations(AssociationUnderTest& a, AssociationUnderTest& z)
227
+ {
228
+ a.association.Connect();
229
+ exchangeMessages(a, z);
230
+
231
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
232
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
233
+ }
234
+
235
+ /**
236
+ * Connects `a` (active) against `z` (passive) and returns `a`'s local
237
+ * verification tag, i.e. the tag that the peer must put in packets sent to
238
+ * `a`. It's captured from the COOKIE-ACK that `z` sends to `a`, and is needed
239
+ * to craft raw packets to be injected into `a`.
240
+ */
241
+ uint32_t connectAndGetVerificationTag(AssociationUnderTest& a, AssociationUnderTest& z)
242
+ {
243
+ a.association.Connect();
244
+ // Z reads INIT, produces INIT-ACK.
245
+ deliverFirstSentPacket(a, z);
246
+ // A reads INIT-ACK, produces COOKIE-ECHO.
247
+ deliverFirstSentPacket(z, a);
248
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
249
+ deliverFirstSentPacket(a, z);
250
+
251
+ // Z now has a pending COOKIE-ACK destined to A; its verification tag is A's
252
+ // local verification tag.
253
+ const auto buffer = z.listener.ConsumeFirstSentPacket();
254
+
255
+ REQUIRE(!buffer.empty());
256
+
257
+ const uint32_t verificationTag = parsePacket(buffer)->GetVerificationTag();
258
+
259
+ // Deliver the COOKIE-ACK so the connection is fully established on A.
260
+ a.association.ReceiveSctpData(buffer.data(), buffer.size());
261
+
262
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
263
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
264
+
265
+ return verificationTag;
266
+ }
267
+
268
+ /**
269
+ * Creates an empty SCTP packet addressed to a peer, carrying the given
270
+ * verification tag. `buffer` must outlive the returned packet, as the packet
271
+ * is built in place on it.
272
+ */
273
+ std::unique_ptr<RTC::SCTP::Packet> buildPacket(std::vector<uint8_t>& buffer, uint32_t verificationTag)
274
+ {
275
+ std::unique_ptr<RTC::SCTP::Packet> packet(
276
+ RTC::SCTP::Packet::Factory(buffer.data(), buffer.size()));
277
+
278
+ packet->SetSourcePort(5000);
279
+ packet->SetDestinationPort(5000);
280
+ packet->SetVerificationTag(verificationTag);
281
+
282
+ return packet;
283
+ }
284
+
285
+ /**
286
+ * Writes the packet checksum and injects it into the given association.
287
+ */
288
+ void injectPacket(AssociationUnderTest& target, RTC::SCTP::Packet* packet)
289
+ {
290
+ packet->WriteCRC32cChecksum();
291
+
292
+ target.association.ReceiveSctpData(packet->GetBuffer(), packet->GetLength());
293
+ }
294
+
295
+ /**
296
+ * Enqueues a message to be sent on the given association.
297
+ */
298
+ RTC::SCTP::Types::SendMessageStatus sendMessage(
299
+ AssociationUnderTest& s,
300
+ uint16_t streamId,
301
+ uint32_t ppid,
302
+ std::vector<uint8_t> payload,
303
+ const RTC::SCTP::SendMessageOptions& sendMessageOptions = {})
304
+ {
305
+ return s.association.SendMessage(
306
+ RTC::SCTP::Message(streamId, ppid, std::move(payload)), sendMessageOptions);
307
+ }
308
+
309
+ /**
310
+ * Consumes every received message on the given association and returns their
311
+ * payload protocol identifiers, in the order they were received.
312
+ */
313
+ std::vector<uint32_t> getReceivedMessagePpids(AssociationUnderTest& s)
314
+ {
315
+ std::vector<uint32_t> ppids;
316
+
317
+ for (;;)
318
+ {
319
+ const auto message = s.listener.ConsumeFirstReceivedMessage();
320
+
321
+ if (!message.has_value())
322
+ {
323
+ break;
324
+ }
325
+
326
+ ppids.push_back(message->GetPayloadProtocolId());
327
+ }
328
+
329
+ return ppids;
330
+ }
331
+ } // namespace
332
+
333
+ SCENARIO("SCTP Association", "[sctp][association]")
334
+ {
335
+ SECTION("establishes connection")
336
+ {
337
+ AssociationUnderTest a;
338
+ AssociationUnderTest z;
339
+
340
+ a.association.Connect();
341
+ // Z reads INIT, produces INIT-ACK.
342
+ deliverFirstSentPacket(a, z);
343
+ // A reads INIT-ACK, produces COOKIE-ECHO.
344
+ deliverFirstSentPacket(z, a);
345
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
346
+ deliverFirstSentPacket(a, z);
347
+ // A reads COOKIE-ACK.
348
+ deliverFirstSentPacket(z, a);
349
+
350
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
351
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
352
+ REQUIRE(a.listener.IsConnected() == true);
353
+ REQUIRE(z.listener.IsConnected() == true);
354
+ REQUIRE(a.listener.HasRestarted() == false);
355
+ REQUIRE(z.listener.HasRestarted() == false);
356
+ }
357
+
358
+ SECTION("establishes connection with setup collision")
359
+ {
360
+ AssociationUnderTest a;
361
+ AssociationUnderTest z;
362
+
363
+ a.association.Connect();
364
+ z.association.Connect();
365
+
366
+ exchangeMessages(a, z);
367
+
368
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
369
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
370
+ REQUIRE(a.listener.HasRestarted() == false);
371
+ REQUIRE(z.listener.HasRestarted() == false);
372
+ }
373
+
374
+ SECTION("establishes simultaneous connection")
375
+ {
376
+ AssociationUnderTest a;
377
+ AssociationUnderTest z;
378
+
379
+ a.association.Connect();
380
+
381
+ // INIT isn't received by Z, as it wasn't ready yet.
382
+ a.listener.ConsumeFirstSentPacket();
383
+
384
+ z.association.Connect();
385
+
386
+ // A reads INIT, produces INIT-ACK.
387
+ deliverFirstSentPacket(z, a);
388
+ // Z reads INIT-ACK, sends COOKIE-ECHO.
389
+ deliverFirstSentPacket(a, z);
390
+ // A reads COOKIE-ECHO, establishes connection.
391
+ deliverFirstSentPacket(z, a);
392
+
393
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
394
+
395
+ // Proceed with the remaining packets.
396
+ exchangeMessages(a, z);
397
+
398
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
399
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
400
+ REQUIRE(a.listener.HasRestarted() == false);
401
+ REQUIRE(z.listener.HasRestarted() == false);
402
+ }
403
+
404
+ SECTION("establishes connection with lost COOKIE-ACK")
405
+ {
406
+ AssociationUnderTest a;
407
+ AssociationUnderTest z;
408
+
409
+ a.association.Connect();
410
+ // Z reads INIT, produces INIT-ACK.
411
+ deliverFirstSentPacket(a, z);
412
+ // A reads INIT-ACK, produces COOKIE-ECHO.
413
+ deliverFirstSentPacket(z, a);
414
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
415
+ deliverFirstSentPacket(a, z);
416
+ // COOKIE-ACK is lost.
417
+ z.listener.ConsumeFirstSentPacket();
418
+
419
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTING);
420
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
421
+
422
+ // This will make A re-send the COOKIE-ECHO.
423
+ advanceTimeMs(a, z, a.sctpOptions.t1CookieTimeoutMs);
424
+
425
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
426
+ deliverFirstSentPacket(a, z);
427
+ // A reads COOKIE-ACK.
428
+ deliverFirstSentPacket(z, a);
429
+
430
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
431
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
432
+ }
433
+
434
+ SECTION("resends INIT and establishes connection")
435
+ {
436
+ AssociationUnderTest a;
437
+ AssociationUnderTest z;
438
+
439
+ a.association.Connect();
440
+
441
+ // INIT is never received by Z.
442
+ REQUIRE(
443
+ packetHasSingleChunkOfType<RTC::SCTP::InitChunk>(a.listener.ConsumeFirstSentPacket()) == true);
444
+
445
+ advanceTimeMs(a, z, a.sctpOptions.t1InitTimeoutMs);
446
+
447
+ // Z reads INIT, produces INIT-ACK.
448
+ deliverFirstSentPacket(a, z);
449
+ // A reads INIT-ACK, produces COOKIE-ECHO.
450
+ deliverFirstSentPacket(z, a);
451
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
452
+ deliverFirstSentPacket(a, z);
453
+ // A reads COOKIE-ACK.
454
+ deliverFirstSentPacket(z, a);
455
+
456
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
457
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
458
+ }
459
+
460
+ SECTION("resending INIT too many times aborts")
461
+ {
462
+ AssociationUnderTest a;
463
+ AssociationUnderTest z;
464
+
465
+ a.association.Connect();
466
+
467
+ // INIT is never received by Z.
468
+ REQUIRE(
469
+ packetHasSingleChunkOfType<RTC::SCTP::InitChunk>(a.listener.ConsumeFirstSentPacket()) == true);
470
+
471
+ const auto maxInitRetransmissions = a.sctpOptions.maxInitRetransmissions.value();
472
+
473
+ for (size_t i = 0; i < maxInitRetransmissions; ++i)
474
+ {
475
+ advanceTimeMs(a, z, a.sctpOptions.t1InitTimeoutMs * (1u << i));
476
+
477
+ // INIT is resent.
478
+ REQUIRE(
479
+ packetHasSingleChunkOfType<RTC::SCTP::InitChunk>(a.listener.ConsumeFirstSentPacket()) == true);
480
+ }
481
+
482
+ // Another timeout, after the max init retransmits.
483
+ advanceTimeMs(a, z, a.sctpOptions.t1InitTimeoutMs * (1u << maxInitRetransmissions));
484
+
485
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
486
+ REQUIRE(a.listener.HasFailed() == true);
487
+ REQUIRE(a.listener.GetFailedErrorKind() == RTC::SCTP::Types::ErrorKind::TOO_MANY_RETRIES);
488
+ }
489
+
490
+ SECTION("resends COOKIE-ECHO and establishes connection")
491
+ {
492
+ AssociationUnderTest a;
493
+ AssociationUnderTest z;
494
+
495
+ a.association.Connect();
496
+
497
+ // Z reads INIT, produces INIT-ACK.
498
+ deliverFirstSentPacket(a, z);
499
+ // A reads INIT-ACK, produces COOKIE-ECHO.
500
+ deliverFirstSentPacket(z, a);
501
+
502
+ // COOKIE-ECHO is never received by Z.
503
+ REQUIRE(
504
+ packetHasSingleChunkOfType<RTC::SCTP::CookieEchoChunk>(a.listener.ConsumeFirstSentPacket()) ==
505
+ true);
506
+
507
+ advanceTimeMs(a, z, a.sctpOptions.t1CookieTimeoutMs);
508
+
509
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
510
+ deliverFirstSentPacket(a, z);
511
+ // A reads COOKIE-ACK.
512
+ deliverFirstSentPacket(z, a);
513
+
514
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
515
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
516
+ }
517
+
518
+ SECTION("resending COOKIE-ECHO too many times aborts")
519
+ {
520
+ AssociationUnderTest a;
521
+ AssociationUnderTest z;
522
+
523
+ a.association.Connect();
524
+
525
+ // Z reads INIT, produces INIT-ACK.
526
+ deliverFirstSentPacket(a, z);
527
+ // A reads INIT-ACK, produces COOKIE-ECHO.
528
+ deliverFirstSentPacket(z, a);
529
+
530
+ // COOKIE-ECHO is never received by Z.
531
+ REQUIRE(
532
+ packetHasSingleChunkOfType<RTC::SCTP::CookieEchoChunk>(a.listener.ConsumeFirstSentPacket()) ==
533
+ true);
534
+
535
+ const auto maxInitRetransmissions = a.sctpOptions.maxInitRetransmissions.value();
536
+
537
+ for (size_t i = 0; i < maxInitRetransmissions; ++i)
538
+ {
539
+ advanceTimeMs(a, z, a.sctpOptions.t1CookieTimeoutMs * (1u << i));
540
+
541
+ // COOKIE-ECHO is resent.
542
+ REQUIRE(
543
+ packetHasSingleChunkOfType<RTC::SCTP::CookieEchoChunk>(a.listener.ConsumeFirstSentPacket()) ==
544
+ true);
545
+ }
546
+
547
+ // Another timeout, after the max init retransmits.
548
+ advanceTimeMs(a, z, a.sctpOptions.t1CookieTimeoutMs * (1u << maxInitRetransmissions));
549
+
550
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
551
+ REQUIRE(a.listener.HasFailed() == true);
552
+ REQUIRE(a.listener.GetFailedErrorKind() == RTC::SCTP::Types::ErrorKind::TOO_MANY_RETRIES);
553
+ }
554
+
555
+ SECTION("shutting down while establishing connection")
556
+ {
557
+ AssociationUnderTest a;
558
+ AssociationUnderTest z;
559
+
560
+ a.association.Connect();
561
+
562
+ // Z reads INIT, produces INIT-ACK.
563
+ deliverFirstSentPacket(a, z);
564
+ // A reads INIT-ACK, produces COOKIE-ECHO.
565
+ deliverFirstSentPacket(z, a);
566
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
567
+ deliverFirstSentPacket(a, z);
568
+ // Drop COOKIE-ACK, just to more easily verify shutdown protocol.
569
+ z.listener.ConsumeFirstSentPacket();
570
+
571
+ // As Association A has received INIT-ACK, it has a TCB and is connected,
572
+ // while Association Z needs to receive COOKIE-ECHO to get there. A still
573
+ // has timers running at this point.
574
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTING);
575
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
576
+
577
+ // Association A is now shut down, which should make it stop those timers.
578
+ a.association.Shutdown();
579
+
580
+ // Z reads SHUTDOWN, produces SHUTDOWN-ACK.
581
+ deliverFirstSentPacket(a, z);
582
+ // A reads SHUTDOWN-ACK, produces SHUTDOWN-COMPLETE.
583
+ deliverFirstSentPacket(z, a);
584
+ // Z reads SHUTDOWN-COMPLETE.
585
+ deliverFirstSentPacket(a, z);
586
+
587
+ REQUIRE(a.listener.ConsumeFirstSentPacket().empty() == true);
588
+ REQUIRE(z.listener.ConsumeFirstSentPacket().empty() == true);
589
+
590
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
591
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
592
+ }
593
+
594
+ SECTION("shuts down connection")
595
+ {
596
+ AssociationUnderTest a;
597
+ AssociationUnderTest z;
598
+
599
+ connectAssociations(a, z);
600
+
601
+ a.association.Shutdown();
602
+ // Z reads SHUTDOWN, produces SHUTDOWN-ACK.
603
+ deliverFirstSentPacket(a, z);
604
+ // A reads SHUTDOWN-ACK, produces SHUTDOWN-COMPLETE.
605
+ deliverFirstSentPacket(z, a);
606
+ // Z reads SHUTDOWN-COMPLETE.
607
+ deliverFirstSentPacket(a, z);
608
+
609
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
610
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
611
+ REQUIRE(a.listener.IsClosed() == true);
612
+ REQUIRE(z.listener.IsClosed() == true);
613
+ }
614
+
615
+ SECTION("shutdown timer expiring too many times closes connection")
616
+ {
617
+ AssociationUnderTest a;
618
+ AssociationUnderTest z;
619
+
620
+ connectAssociations(a, z);
621
+
622
+ a.association.Shutdown();
623
+ // Drop the first SHUTDOWN packet.
624
+ a.listener.ConsumeFirstSentPacket();
625
+
626
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::SHUTTING_DOWN);
627
+
628
+ const auto maxRetransmissions = a.sctpOptions.maxRetransmissions.value();
629
+
630
+ for (size_t i = 0; i < maxRetransmissions; ++i)
631
+ {
632
+ advanceTimeMs(a, z, a.sctpOptions.initialRtoMs * (1u << i));
633
+
634
+ // SHUTDOWN is resent (and dropped).
635
+ REQUIRE(
636
+ packetHasSingleChunkOfType<RTC::SCTP::ShutdownChunk>(a.listener.ConsumeFirstSentPacket()) ==
637
+ true);
638
+ }
639
+
640
+ // The last expiry makes it abort the connection.
641
+ advanceTimeMs(a, z, a.sctpOptions.initialRtoMs * (1u << maxRetransmissions));
642
+
643
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
644
+ REQUIRE(a.listener.IsClosed() == true);
645
+ REQUIRE(a.listener.GetClosedErrorKind() == RTC::SCTP::Types::ErrorKind::TOO_MANY_RETRIES);
646
+ // An ABORT chunk is sent.
647
+ REQUIRE(
648
+ packetHasSingleChunkOfType<RTC::SCTP::AbortAssociationChunk>(
649
+ a.listener.ConsumeFirstSentPacket()) == true);
650
+ }
651
+
652
+ SECTION("T2-shutdown timer expiry resends SHUTDOWN-ACK")
653
+ {
654
+ AssociationUnderTest a;
655
+ AssociationUnderTest z;
656
+
657
+ connectAssociations(a, z);
658
+
659
+ z.association.Shutdown();
660
+
661
+ // A receives SHUTDOWN, sends SHUTDOWN-ACK. A is now in SHUTDOWN-ACK-SENT
662
+ // state.
663
+ deliverFirstSentPacket(z, a);
664
+
665
+ // Consume the SHUTDOWN-ACK sent by A (drop it).
666
+ REQUIRE(
667
+ packetHasSingleChunkOfType<RTC::SCTP::ShutdownAckChunk>(a.listener.ConsumeFirstSentPacket()) ==
668
+ true);
669
+
670
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::SHUTTING_DOWN);
671
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::SHUTTING_DOWN);
672
+
673
+ // Advance time to trigger timer expiry, which makes A resend SHUTDOWN-ACK.
674
+ advanceTimeMs(a, z, a.sctpOptions.initialRtoMs);
675
+
676
+ REQUIRE(
677
+ packetHasSingleChunkOfType<RTC::SCTP::ShutdownAckChunk>(a.listener.ConsumeFirstSentPacket()) ==
678
+ true);
679
+ }
680
+
681
+ SECTION("shutdown is ignored in SHUTDOWN-RECEIVED state")
682
+ {
683
+ AssociationUnderTest a;
684
+ AssociationUnderTest z;
685
+
686
+ connectAssociations(a, z);
687
+
688
+ z.association.Shutdown();
689
+
690
+ // A reads SHUTDOWN, produces SHUTDOWN-ACK.
691
+ deliverFirstSentPacket(z, a);
692
+
693
+ REQUIRE(
694
+ packetHasSingleChunkOfType<RTC::SCTP::ShutdownAckChunk>(a.listener.ConsumeFirstSentPacket()) ==
695
+ true);
696
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::SHUTTING_DOWN);
697
+
698
+ // Second shutdown while already shutting down, must be ignored.
699
+ a.association.Shutdown();
700
+ REQUIRE(a.listener.ConsumeFirstSentPacket().empty() == true);
701
+ }
702
+
703
+ SECTION("shutdown is ignored in SHUTDOWN-PENDING state")
704
+ {
705
+ AssociationUnderTest a;
706
+ AssociationUnderTest z;
707
+
708
+ connectAssociations(a, z);
709
+
710
+ // Send a message that will remain outstanding (unacknowledged) which
711
+ // transitions the association to SHUTDOWN-PENDING instead of SHUTDOWN-SENT.
712
+ sendMessage(a, 1, 53, { 1, 2 });
713
+ a.association.Shutdown();
714
+
715
+ REQUIRE(packetHasDataChunk(a.listener.ConsumeFirstSentPacket()) == true);
716
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::SHUTTING_DOWN);
717
+
718
+ // Second shutdown while already shutting down, must be ignored.
719
+ a.association.Shutdown();
720
+ REQUIRE(a.listener.ConsumeFirstSentPacket().empty() == true);
721
+ }
722
+
723
+ SECTION("establishes connection while sending data")
724
+ {
725
+ AssociationUnderTest a;
726
+ AssociationUnderTest z;
727
+
728
+ a.association.Connect();
729
+
730
+ sendMessage(a, 1, 53, { 1, 2 });
731
+
732
+ // Z reads INIT, produces INIT-ACK.
733
+ deliverFirstSentPacket(a, z);
734
+ // A reads INIT-ACK, produces COOKIE-ECHO.
735
+ deliverFirstSentPacket(z, a);
736
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
737
+ deliverFirstSentPacket(a, z);
738
+ // A reads COOKIE-ACK.
739
+ deliverFirstSentPacket(z, a);
740
+
741
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
742
+ REQUIRE(z.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
743
+
744
+ // Deliver the remaining packets so that the message reaches Z.
745
+ exchangeMessages(a, z);
746
+
747
+ const auto message = z.listener.ConsumeFirstReceivedMessage();
748
+
749
+ REQUIRE(message.has_value() == true);
750
+ REQUIRE(message->GetStreamId() == 1);
751
+ }
752
+
753
+ SECTION("sends a message after established")
754
+ {
755
+ AssociationUnderTest a;
756
+ AssociationUnderTest z;
757
+
758
+ connectAssociations(a, z);
759
+
760
+ sendMessage(a, 1, 53, { 1, 2 });
761
+ // Z reads the DATA chunk.
762
+ deliverFirstSentPacket(a, z);
763
+
764
+ const auto message = z.listener.ConsumeFirstReceivedMessage();
765
+
766
+ REQUIRE(message.has_value() == true);
767
+ REQUIRE(message->GetStreamId() == 1);
768
+ REQUIRE(message->GetPayloadProtocolId() == 53);
769
+ }
770
+
771
+ SECTION("resends a packet when the T3-RTX timer expires")
772
+ {
773
+ AssociationUnderTest a;
774
+ AssociationUnderTest z;
775
+
776
+ connectAssociations(a, z);
777
+
778
+ sendMessage(a, 1, 53, { 1, 2 });
779
+ // Drop the first DATA packet.
780
+ a.listener.ConsumeFirstSentPacket();
781
+
782
+ // Advance time so that the T3-RTX timer expires and the DATA is resent.
783
+ advanceTimeMs(a, z, a.sctpOptions.initialRtoMs);
784
+
785
+ // Z reads the retransmitted DATA chunk.
786
+ deliverFirstSentPacket(a, z);
787
+
788
+ const auto message = z.listener.ConsumeFirstReceivedMessage();
789
+
790
+ REQUIRE(message.has_value() == true);
791
+ REQUIRE(message->GetStreamId() == 1);
792
+ }
793
+
794
+ SECTION("sends a lot of bytes with a missed packet")
795
+ {
796
+ AssociationUnderTest a;
797
+ AssociationUnderTest z;
798
+
799
+ connectAssociations(a, z);
800
+
801
+ const std::vector<uint8_t> payload(a.sctpOptions.mtu * 20);
802
+
803
+ sendMessage(a, 1, 53, payload);
804
+
805
+ // Z reads the first DATA.
806
+ deliverFirstSentPacket(a, z);
807
+ // Second DATA is lost.
808
+ a.listener.ConsumeFirstSentPacket();
809
+
810
+ // Retransmit and handle the rest.
811
+ exchangeMessages(a, z);
812
+
813
+ const auto message = z.listener.ConsumeFirstReceivedMessage();
814
+
815
+ REQUIRE(message.has_value() == true);
816
+ REQUIRE(message->GetStreamId() == 1);
817
+ REQUIRE(message->GetPayloadLength() == payload.size());
818
+ }
819
+
820
+ SECTION("sends a heartbeat on idle connection")
821
+ {
822
+ AssociationUnderTest a;
823
+ AssociationUnderTest z;
824
+
825
+ connectAssociations(a, z);
826
+
827
+ REQUIRE(a.listener.ConsumeFirstSentPacket().empty() == true);
828
+
829
+ // Let the heartbeat interval timer expire.
830
+ advanceTimeMs(a, z, a.sctpOptions.heartbeatIntervalMs);
831
+
832
+ const auto buffer = a.listener.ConsumeFirstSentPacket();
833
+
834
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::HeartbeatRequestChunk>(buffer) == true);
835
+
836
+ // Feed it to Z and expect a HEARTBEAT-ACK that is propagated back to A.
837
+ z.association.ReceiveSctpData(buffer.data(), buffer.size());
838
+ deliverFirstSentPacket(z, a);
839
+ }
840
+
841
+ SECTION("sends many messages")
842
+ {
843
+ AssociationUnderTest a;
844
+ AssociationUnderTest z;
845
+
846
+ connectAssociations(a, z);
847
+
848
+ constexpr int Iterations{ 100 };
849
+
850
+ std::vector<RTC::SCTP::Message> messages;
851
+
852
+ messages.reserve(Iterations);
853
+
854
+ for (int i = 0; i < Iterations; ++i)
855
+ {
856
+ messages.emplace_back(/*streamId*/ 1, /*ppid*/ 53, std::vector<uint8_t>{ 1, 2 });
857
+ }
858
+
859
+ const auto statuses = a.association.SendManyMessages(messages, /*sendMessageOptions*/ {});
860
+
861
+ REQUIRE(statuses.size() == Iterations);
862
+
863
+ for (const auto status : statuses)
864
+ {
865
+ REQUIRE(status == RTC::SCTP::Types::SendMessageStatus::SUCCESS);
866
+ }
867
+
868
+ exchangeMessages(a, z);
869
+
870
+ for (int i = 0; i < Iterations; ++i)
871
+ {
872
+ REQUIRE(z.listener.ConsumeFirstReceivedMessage().has_value() == true);
873
+ }
874
+
875
+ REQUIRE(z.listener.ConsumeFirstReceivedMessage().has_value() == false);
876
+ }
877
+
878
+ SECTION("initial metrics are unset")
879
+ {
880
+ const AssociationUnderTest a;
881
+
882
+ REQUIRE(a.association.MakeMetrics().has_value() == false);
883
+ }
884
+
885
+ SECTION("message interleaving metrics are set")
886
+ {
887
+ for (const bool aEnable : { false, true })
888
+ {
889
+ for (const bool zEnable : { false, true })
890
+ {
891
+ auto aSctpOptions = makeSctpOptions();
892
+
893
+ aSctpOptions.enableMessageInterleaving = aEnable;
894
+
895
+ auto zSctpOptions = makeSctpOptions();
896
+
897
+ zSctpOptions.enableMessageInterleaving = zEnable;
898
+
899
+ AssociationUnderTest a(aSctpOptions);
900
+ AssociationUnderTest z(zSctpOptions);
901
+
902
+ connectAssociations(a, z);
903
+
904
+ REQUIRE(a.association.MakeMetrics()->usesMessageInterleaving == (aEnable && zEnable));
905
+ }
906
+ }
907
+ }
908
+
909
+ SECTION("rx and tx packet metrics increase")
910
+ {
911
+ AssociationUnderTest a;
912
+ AssociationUnderTest z;
913
+
914
+ connectAssociations(a, z);
915
+
916
+ // A sent INIT and COOKIE-ECHO, received INIT-ACK and COOKIE-ACK.
917
+ REQUIRE(a.association.MakeMetrics()->txPacketsCount == 2);
918
+ REQUIRE(a.association.MakeMetrics()->rxPacketsCount == 2);
919
+ REQUIRE(a.association.MakeMetrics()->txMessagesCount == 0);
920
+ REQUIRE(
921
+ a.association.MakeMetrics()->cwndBytes == a.sctpOptions.initialCwndMtus * a.sctpOptions.mtu);
922
+ REQUIRE(a.association.MakeMetrics()->unackDataCount == 0);
923
+
924
+ REQUIRE(z.association.MakeMetrics()->rxPacketsCount == 2);
925
+ REQUIRE(z.association.MakeMetrics()->rxMessagesCount == 0);
926
+
927
+ sendMessage(a, 1, 53, { 1, 2 });
928
+
929
+ REQUIRE(a.association.MakeMetrics()->unackDataCount == 1);
930
+
931
+ // Z reads DATA.
932
+ deliverFirstSentPacket(a, z);
933
+ // A reads SACK.
934
+ deliverFirstSentPacket(z, a);
935
+
936
+ REQUIRE(a.association.MakeMetrics()->unackDataCount == 0);
937
+ REQUIRE(z.listener.ConsumeFirstReceivedMessage().has_value() == true);
938
+
939
+ REQUIRE(a.association.MakeMetrics()->txPacketsCount == 3);
940
+ REQUIRE(a.association.MakeMetrics()->rxPacketsCount == 3);
941
+ REQUIRE(a.association.MakeMetrics()->txMessagesCount == 1);
942
+
943
+ REQUIRE(z.association.MakeMetrics()->rxPacketsCount == 3);
944
+ REQUIRE(z.association.MakeMetrics()->rxMessagesCount == 1);
945
+ }
946
+
947
+ SECTION("streams have initial priority")
948
+ {
949
+ auto sctpOptions = makeSctpOptions();
950
+
951
+ sctpOptions.defaultStreamPriority = 42;
952
+
953
+ AssociationUnderTest a(sctpOptions);
954
+
955
+ REQUIRE(a.association.GetStreamPriority(1) == 42);
956
+
957
+ sendMessage(a, 2, 53, { 1, 2 });
958
+
959
+ REQUIRE(a.association.GetStreamPriority(2) == 42);
960
+ }
961
+
962
+ SECTION("can change stream priority")
963
+ {
964
+ auto sctpOptions = makeSctpOptions();
965
+
966
+ sctpOptions.defaultStreamPriority = 42;
967
+
968
+ AssociationUnderTest a(sctpOptions);
969
+
970
+ a.association.SetStreamPriority(1, 43);
971
+
972
+ REQUIRE(a.association.GetStreamPriority(1) == 43);
973
+
974
+ sendMessage(a, 2, 53, { 1, 2 });
975
+
976
+ a.association.SetStreamPriority(2, 43);
977
+
978
+ REQUIRE(a.association.GetStreamPriority(2) == 43);
979
+ }
980
+
981
+ SECTION("respects the per-stream queue limit")
982
+ {
983
+ auto sctpOptions = makeSctpOptions();
984
+
985
+ sctpOptions.maxSendBufferSize = 4000;
986
+ sctpOptions.perStreamSendQueueLimit = 1000;
987
+
988
+ AssociationUnderTest a(sctpOptions);
989
+
990
+ REQUIRE(
991
+ sendMessage(a, 1, 53, std::vector<uint8_t>(600)) ==
992
+ RTC::SCTP::Types::SendMessageStatus::SUCCESS);
993
+ REQUIRE(
994
+ sendMessage(a, 1, 53, std::vector<uint8_t>(600)) ==
995
+ RTC::SCTP::Types::SendMessageStatus::SUCCESS);
996
+ REQUIRE(
997
+ sendMessage(a, 1, 53, std::vector<uint8_t>(600)) ==
998
+ RTC::SCTP::Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION);
999
+ // The per-stream limit for stream 1 is reached, but not for stream 2.
1000
+ REQUIRE(
1001
+ sendMessage(a, 2, 53, std::vector<uint8_t>(600)) ==
1002
+ RTC::SCTP::Types::SendMessageStatus::SUCCESS);
1003
+ REQUIRE(
1004
+ sendMessage(a, 2, 53, std::vector<uint8_t>(600)) ==
1005
+ RTC::SCTP::Types::SendMessageStatus::SUCCESS);
1006
+ REQUIRE(
1007
+ sendMessage(a, 2, 53, std::vector<uint8_t>(600)) ==
1008
+ RTC::SCTP::Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION);
1009
+ }
1010
+
1011
+ SECTION("has reasonable buffered amount values")
1012
+ {
1013
+ AssociationUnderTest a;
1014
+ AssociationUnderTest z;
1015
+
1016
+ connectAssociations(a, z);
1017
+
1018
+ REQUIRE(a.association.GetStreamBufferedAmount(1) == 0);
1019
+
1020
+ // Sending a small message will directly send it as a single packet, so
1021
+ // nothing is left in the queue.
1022
+ sendMessage(a, 1, 53, std::vector<uint8_t>(10));
1023
+
1024
+ REQUIRE(a.association.GetStreamBufferedAmount(1) == 0);
1025
+
1026
+ // Sending a large message will directly start sending a few packets, so the
1027
+ // buffered amount is not the full message size.
1028
+ const size_t largeSize = a.sctpOptions.mtu * 20;
1029
+
1030
+ sendMessage(a, 1, 53, std::vector<uint8_t>(largeSize));
1031
+
1032
+ REQUIRE(a.association.GetStreamBufferedAmount(1) > 0);
1033
+ REQUIRE(a.association.GetStreamBufferedAmount(1) < largeSize);
1034
+ }
1035
+
1036
+ SECTION("has a default buffered amount low threshold of zero")
1037
+ {
1038
+ const AssociationUnderTest a;
1039
+
1040
+ REQUIRE(a.association.GetStreamBufferedAmountLowThreshold(1) == 0);
1041
+ }
1042
+
1043
+ SECTION("triggers OnAssociationStreamBufferedAmountLow with default threshold zero")
1044
+ {
1045
+ AssociationUnderTest a;
1046
+ AssociationUnderTest z;
1047
+
1048
+ REQUIRE(a.listener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1) == false);
1049
+
1050
+ connectAssociations(a, z);
1051
+
1052
+ sendMessage(a, 1, 53, std::vector<uint8_t>(10));
1053
+ exchangeMessages(a, z);
1054
+
1055
+ REQUIRE(a.listener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1) == true);
1056
+ }
1057
+
1058
+ SECTION("detects the peer implementation")
1059
+ {
1060
+ AssociationUnderTest a;
1061
+ AssociationUnderTest z;
1062
+
1063
+ connectAssociations(a, z);
1064
+
1065
+ // Both peers are mediasoup.
1066
+ REQUIRE(
1067
+ a.association.MakeMetrics()->peerImplementation ==
1068
+ RTC::SCTP::Types::SctpImplementation::MEDIASOUP);
1069
+ // As A initiated the connection establishment, the passive peer Z will not
1070
+ // receive enough information to know about A's implementation.
1071
+ REQUIRE(
1072
+ z.association.MakeMetrics()->peerImplementation ==
1073
+ RTC::SCTP::Types::SctpImplementation::UNKNOWN);
1074
+ }
1075
+
1076
+ SECTION("both peers detect the peer implementation")
1077
+ {
1078
+ AssociationUnderTest a;
1079
+ AssociationUnderTest z;
1080
+
1081
+ a.association.Connect();
1082
+ z.association.Connect();
1083
+
1084
+ exchangeMessages(a, z);
1085
+
1086
+ REQUIRE(
1087
+ a.association.MakeMetrics()->peerImplementation ==
1088
+ RTC::SCTP::Types::SctpImplementation::MEDIASOUP);
1089
+ REQUIRE(
1090
+ z.association.MakeMetrics()->peerImplementation ==
1091
+ RTC::SCTP::Types::SctpImplementation::MEDIASOUP);
1092
+ }
1093
+
1094
+ SECTION("exposes the number of negotiated streams")
1095
+ {
1096
+ auto aSctpOptions = makeSctpOptions();
1097
+
1098
+ aSctpOptions.announcedMaxInboundStreams = 12;
1099
+ aSctpOptions.announcedMaxOutboundStreams = 45;
1100
+
1101
+ auto zSctpOptions = makeSctpOptions();
1102
+
1103
+ zSctpOptions.announcedMaxInboundStreams = 23;
1104
+ zSctpOptions.announcedMaxOutboundStreams = 34;
1105
+
1106
+ AssociationUnderTest a(aSctpOptions);
1107
+ AssociationUnderTest z(zSctpOptions);
1108
+
1109
+ connectAssociations(a, z);
1110
+
1111
+ const auto metricsA = a.association.MakeMetrics();
1112
+
1113
+ REQUIRE(metricsA->negotiatedMaxInboundStreams == 12);
1114
+ REQUIRE(metricsA->negotiatedMaxOutboundStreams == 23);
1115
+
1116
+ const auto metricsZ = z.association.MakeMetrics();
1117
+
1118
+ REQUIRE(metricsZ->negotiatedMaxInboundStreams == 23);
1119
+ REQUIRE(metricsZ->negotiatedMaxOutboundStreams == 12);
1120
+ }
1121
+
1122
+ SECTION("always sends INIT with non-zero checksum")
1123
+ {
1124
+ auto sctpOptions = makeSctpOptions();
1125
+
1126
+ sctpOptions.zeroChecksumAlternateErrorDetectionMethod =
1127
+ RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;
1128
+
1129
+ AssociationUnderTest a(sctpOptions);
1130
+
1131
+ a.association.Connect();
1132
+
1133
+ const auto buffer = a.listener.ConsumeFirstSentPacket();
1134
+
1135
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::InitChunk>(buffer) == true);
1136
+ REQUIRE(parsePacket(buffer)->GetChecksum() != 0);
1137
+ }
1138
+
1139
+ SECTION("may send INIT-ACK with zero checksum")
1140
+ {
1141
+ auto sctpOptions = makeSctpOptions();
1142
+
1143
+ sctpOptions.zeroChecksumAlternateErrorDetectionMethod =
1144
+ RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;
1145
+
1146
+ AssociationUnderTest a(sctpOptions);
1147
+ AssociationUnderTest z(sctpOptions);
1148
+
1149
+ a.association.Connect();
1150
+ // Z reads INIT, produces INIT-ACK.
1151
+ deliverFirstSentPacket(a, z);
1152
+
1153
+ const auto buffer = z.listener.ConsumeFirstSentPacket();
1154
+
1155
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::InitAckChunk>(buffer) == true);
1156
+ REQUIRE(parsePacket(buffer)->GetChecksum() == 0);
1157
+ }
1158
+
1159
+ SECTION("always sends COOKIE-ECHO with non-zero checksum")
1160
+ {
1161
+ auto sctpOptions = makeSctpOptions();
1162
+
1163
+ sctpOptions.zeroChecksumAlternateErrorDetectionMethod =
1164
+ RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;
1165
+
1166
+ AssociationUnderTest a(sctpOptions);
1167
+ AssociationUnderTest z(sctpOptions);
1168
+
1169
+ a.association.Connect();
1170
+ // Z reads INIT, produces INIT-ACK.
1171
+ deliverFirstSentPacket(a, z);
1172
+ // A reads INIT-ACK, produces COOKIE-ECHO.
1173
+ deliverFirstSentPacket(z, a);
1174
+
1175
+ const auto buffer = a.listener.ConsumeFirstSentPacket();
1176
+
1177
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::CookieEchoChunk>(buffer) == true);
1178
+ REQUIRE(parsePacket(buffer)->GetChecksum() != 0);
1179
+ }
1180
+
1181
+ SECTION("sends COOKIE-ACK with zero checksum")
1182
+ {
1183
+ auto sctpOptions = makeSctpOptions();
1184
+
1185
+ sctpOptions.zeroChecksumAlternateErrorDetectionMethod =
1186
+ RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;
1187
+
1188
+ AssociationUnderTest a(sctpOptions);
1189
+ AssociationUnderTest z(sctpOptions);
1190
+
1191
+ a.association.Connect();
1192
+ // Z reads INIT, produces INIT-ACK.
1193
+ deliverFirstSentPacket(a, z);
1194
+ // A reads INIT-ACK, produces COOKIE-ECHO.
1195
+ deliverFirstSentPacket(z, a);
1196
+ // Z reads COOKIE-ECHO, produces COOKIE-ACK.
1197
+ deliverFirstSentPacket(a, z);
1198
+
1199
+ const auto buffer = z.listener.ConsumeFirstSentPacket();
1200
+
1201
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::CookieAckChunk>(buffer) == true);
1202
+ REQUIRE(parsePacket(buffer)->GetChecksum() == 0);
1203
+ }
1204
+
1205
+ SECTION("sends DATA with zero checksum")
1206
+ {
1207
+ auto sctpOptions = makeSctpOptions();
1208
+
1209
+ sctpOptions.zeroChecksumAlternateErrorDetectionMethod =
1210
+ RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;
1211
+
1212
+ AssociationUnderTest a(sctpOptions);
1213
+ AssociationUnderTest z(sctpOptions);
1214
+
1215
+ connectAssociations(a, z);
1216
+
1217
+ sendMessage(a, 1, 53, std::vector<uint8_t>(a.sctpOptions.mtu - 100));
1218
+
1219
+ const auto buffer = a.listener.ConsumeFirstSentPacket();
1220
+
1221
+ REQUIRE(packetHasDataChunk(buffer) == true);
1222
+ REQUIRE(parsePacket(buffer)->GetChecksum() == 0);
1223
+ }
1224
+
1225
+ SECTION("both sides send heartbeats")
1226
+ {
1227
+ // Make them have slightly different heartbeat intervals, to validate that
1228
+ // sending an ack by Z doesn't restart its heartbeat timer.
1229
+ auto aSctpOptions = makeSctpOptions();
1230
+
1231
+ aSctpOptions.heartbeatIntervalMs = 1000;
1232
+
1233
+ auto zSctpOptions = makeSctpOptions();
1234
+
1235
+ zSctpOptions.heartbeatIntervalMs = 1100;
1236
+
1237
+ AssociationUnderTest a(aSctpOptions);
1238
+ AssociationUnderTest z(zSctpOptions);
1239
+
1240
+ connectAssociations(a, z);
1241
+
1242
+ advanceTimeMs(a, z, 1000);
1243
+
1244
+ const auto bufferA = a.listener.ConsumeFirstSentPacket();
1245
+
1246
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::HeartbeatRequestChunk>(bufferA) == true);
1247
+
1248
+ // Z receives the heartbeat and sends an ACK that is propagated back to A.
1249
+ z.association.ReceiveSctpData(bufferA.data(), bufferA.size());
1250
+ deliverFirstSentPacket(z, a);
1251
+
1252
+ // A little while later, Z should send heartbeats to A.
1253
+ advanceTimeMs(a, z, 100);
1254
+
1255
+ const auto bufferZ = z.listener.ConsumeFirstSentPacket();
1256
+
1257
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::HeartbeatRequestChunk>(bufferZ) == true);
1258
+
1259
+ // A receives the heartbeat and sends an ACK that is propagated back to Z.
1260
+ a.association.ReceiveSctpData(bufferZ.data(), bufferZ.size());
1261
+ deliverFirstSentPacket(a, z);
1262
+ }
1263
+
1264
+ SECTION("closes connection after too many lost heartbeats")
1265
+ {
1266
+ AssociationUnderTest a;
1267
+ // Disable Z's heartbeats so that it doesn't interfere.
1268
+ auto zSctpOptions = makeSctpOptions();
1269
+
1270
+ zSctpOptions.heartbeatIntervalMs = 0;
1271
+
1272
+ AssociationUnderTest z(zSctpOptions);
1273
+
1274
+ connectAssociations(a, z);
1275
+
1276
+ REQUIRE(a.listener.ConsumeFirstSentPacket().empty() == true);
1277
+
1278
+ const auto maxRetransmissions = a.sctpOptions.maxRetransmissions.value();
1279
+
1280
+ uint64_t timeToNextHeartbeatMs = a.sctpOptions.heartbeatIntervalMs;
1281
+
1282
+ for (size_t i = 0; i < maxRetransmissions; ++i)
1283
+ {
1284
+ // Let the heartbeat interval timer expire, sending a heartbeat.
1285
+ advanceTimeMs(a, z, timeToNextHeartbeatMs);
1286
+
1287
+ // Drop every heartbeat.
1288
+ REQUIRE(
1289
+ packetHasSingleChunkOfType<RTC::SCTP::HeartbeatRequestChunk>(
1290
+ a.listener.ConsumeFirstSentPacket()) == true);
1291
+
1292
+ // Let the heartbeat timeout expire.
1293
+ advanceTimeMs(a, z, 1000);
1294
+
1295
+ timeToNextHeartbeatMs = a.sctpOptions.heartbeatIntervalMs - 1000;
1296
+ }
1297
+
1298
+ // The last heartbeat.
1299
+ advanceTimeMs(a, z, timeToNextHeartbeatMs);
1300
+
1301
+ REQUIRE(a.listener.HasSentPackets() == true);
1302
+
1303
+ a.listener.ConsumeFirstSentPacket();
1304
+
1305
+ // Should suffice as exceeding RTO, which aborts the connection.
1306
+ advanceTimeMs(a, z, 1000);
1307
+
1308
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CLOSED);
1309
+ REQUIRE(a.listener.IsClosed() == true);
1310
+ REQUIRE(a.listener.GetClosedErrorKind() == RTC::SCTP::Types::ErrorKind::TOO_MANY_RETRIES);
1311
+ }
1312
+
1313
+ SECTION("recovers after a successful heartbeat ack")
1314
+ {
1315
+ AssociationUnderTest a;
1316
+ // Disable Z's heartbeats so that it doesn't interfere.
1317
+ auto zSctpOptions = makeSctpOptions();
1318
+
1319
+ zSctpOptions.heartbeatIntervalMs = 0;
1320
+
1321
+ AssociationUnderTest z(zSctpOptions);
1322
+
1323
+ connectAssociations(a, z);
1324
+
1325
+ REQUIRE(a.listener.ConsumeFirstSentPacket().empty() == true);
1326
+
1327
+ const auto maxRetransmissions = a.sctpOptions.maxRetransmissions.value();
1328
+
1329
+ uint64_t timeToNextHeartbeatMs = a.sctpOptions.heartbeatIntervalMs;
1330
+
1331
+ for (size_t i = 0; i < maxRetransmissions; ++i)
1332
+ {
1333
+ advanceTimeMs(a, z, timeToNextHeartbeatMs);
1334
+
1335
+ // Drop every heartbeat.
1336
+ a.listener.ConsumeFirstSentPacket();
1337
+
1338
+ // Let the heartbeat timeout expire.
1339
+ advanceTimeMs(a, z, 1000);
1340
+
1341
+ timeToNextHeartbeatMs = a.sctpOptions.heartbeatIntervalMs - 1000;
1342
+ }
1343
+
1344
+ // Get the last heartbeat and ack it (round trip through Z).
1345
+ advanceTimeMs(a, z, timeToNextHeartbeatMs);
1346
+
1347
+ const auto buffer = a.listener.ConsumeFirstSentPacket();
1348
+
1349
+ REQUIRE(packetHasSingleChunkOfType<RTC::SCTP::HeartbeatRequestChunk>(buffer) == true);
1350
+
1351
+ z.association.ReceiveSctpData(buffer.data(), buffer.size());
1352
+ // A reads the HEARTBEAT-ACK, which clears the error counter.
1353
+ deliverFirstSentPacket(z, a);
1354
+
1355
+ // Should suffice as exceeding RTO, but the timer will not fire as it was
1356
+ // stopped by the ack.
1357
+ advanceTimeMs(a, z, 1000);
1358
+
1359
+ REQUIRE(a.association.GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED);
1360
+ REQUIRE(a.listener.IsClosed() == false);
1361
+
1362
+ // Verify that we get new heartbeats again.
1363
+ advanceTimeMs(a, z, timeToNextHeartbeatMs);
1364
+
1365
+ REQUIRE(
1366
+ packetHasSingleChunkOfType<RTC::SCTP::HeartbeatRequestChunk>(
1367
+ a.listener.ConsumeFirstSentPacket()) == true);
1368
+ }
1369
+
1370
+ SECTION("resets a stream")
1371
+ {
1372
+ AssociationUnderTest a;
1373
+ AssociationUnderTest z;
1374
+
1375
+ connectAssociations(a, z);
1376
+
1377
+ sendMessage(a, 1, 53, { 1, 2 });
1378
+ // Z reads the DATA chunk.
1379
+ deliverFirstSentPacket(a, z);
1380
+
1381
+ REQUIRE(z.listener.ConsumeFirstReceivedMessage().has_value() == true);
1382
+
1383
+ // A reads the SACK.
1384
+ deliverFirstSentPacket(z, a);
1385
+
1386
+ // Reset the outgoing stream. This will directly send a RE-CONFIG.
1387
+ const std::vector<uint16_t> streamIds{ 1 };
1388
+
1389
+ REQUIRE(a.association.ResetStreams(streamIds) == RTC::SCTP::Types::ResetStreamsStatus::PERFORMED);
1390
+
1391
+ // Z reads the RE-CONFIG, which triggers OnAssociationInboundStreamsReset and
1392
+ // sends a RE-CONFIG response.
1393
+ deliverFirstSentPacket(a, z);
1394
+
1395
+ REQUIRE(z.listener.HasInboundStreamsResetForStreamId(1) == true);
1396
+
1397
+ // A reads the response, which triggers OnAssociationStreamsResetPerformed.
1398
+ deliverFirstSentPacket(z, a);
1399
+
1400
+ REQUIRE(a.listener.HasStreamsResetPerformedForStreamId(1) == true);
1401
+ }
1402
+
1403
+ SECTION("small messages with priority arrive in a specific order")
1404
+ {
1405
+ AssociationUnderTest a;
1406
+ AssociationUnderTest z;
1407
+
1408
+ a.association.SetStreamPriority(1, 700);
1409
+ a.association.SetStreamPriority(2, 200);
1410
+ a.association.SetStreamPriority(3, 100);
1411
+
1412
+ // Enqueue messages before connecting, to ensure they aren't sent as soon as
1413
+ // sendMessage() is called.
1414
+ sendMessage(a, 3, 301, std::vector<uint8_t>(10));
1415
+ sendMessage(a, 1, 101, std::vector<uint8_t>(10));
1416
+ sendMessage(a, 2, 201, std::vector<uint8_t>(10));
1417
+ sendMessage(a, 1, 102, std::vector<uint8_t>(10));
1418
+ sendMessage(a, 1, 103, std::vector<uint8_t>(10));
1419
+
1420
+ connectAssociations(a, z);
1421
+ exchangeMessages(a, z);
1422
+
1423
+ REQUIRE(getReceivedMessagePpids(z) == std::vector<uint32_t>{ 101, 102, 103, 201, 301 });
1424
+ }
1425
+
1426
+ SECTION("large messages with priority arrive in a specific order")
1427
+ {
1428
+ AssociationUnderTest a;
1429
+ AssociationUnderTest z;
1430
+
1431
+ const size_t largeSize = a.sctpOptions.mtu * 20;
1432
+
1433
+ a.association.SetStreamPriority(1, 700);
1434
+ a.association.SetStreamPriority(2, 200);
1435
+ a.association.SetStreamPriority(3, 100);
1436
+
1437
+ // Enqueue messages before connecting, to ensure they aren't sent as soon as
1438
+ // sendMessage() is called.
1439
+ sendMessage(a, 3, 301, std::vector<uint8_t>(largeSize));
1440
+ sendMessage(a, 1, 101, std::vector<uint8_t>(largeSize));
1441
+ sendMessage(a, 2, 201, std::vector<uint8_t>(largeSize));
1442
+ sendMessage(a, 1, 102, std::vector<uint8_t>(largeSize));
1443
+
1444
+ connectAssociations(a, z);
1445
+ exchangeMessages(a, z);
1446
+
1447
+ REQUIRE(getReceivedMessagePpids(z) == std::vector<uint32_t>{ 101, 102, 201, 301 });
1448
+ }
1449
+
1450
+ SECTION("a higher priority message interrupts a lower priority message")
1451
+ {
1452
+ AssociationUnderTest a;
1453
+ AssociationUnderTest z;
1454
+
1455
+ connectAssociations(a, z);
1456
+
1457
+ const size_t largeSize = a.sctpOptions.mtu * 20;
1458
+
1459
+ a.association.SetStreamPriority(2, 128);
1460
+ sendMessage(a, 2, 201, std::vector<uint8_t>(largeSize));
1461
+
1462
+ // Due to a non-zero initial congestion window, the message will already
1463
+ // start to send, but will not be sent completely. Now enqueue two messages
1464
+ // on a higher priority stream: one small and one large.
1465
+ a.association.SetStreamPriority(1, 512);
1466
+ sendMessage(a, 1, 101, std::vector<uint8_t>(10));
1467
+ sendMessage(a, 1, 102, std::vector<uint8_t>(largeSize));
1468
+
1469
+ exchangeMessages(a, z);
1470
+
1471
+ REQUIRE(getReceivedMessagePpids(z) == std::vector<uint32_t>{ 101, 102, 201 });
1472
+ }
1473
+
1474
+ SECTION("lifecycle events are generated for acked messages")
1475
+ {
1476
+ AssociationUnderTest a;
1477
+ AssociationUnderTest z;
1478
+
1479
+ connectAssociations(a, z);
1480
+
1481
+ const size_t largeSize = a.sctpOptions.mtu * 20;
1482
+
1483
+ sendMessage(a, 2, 101, std::vector<uint8_t>(largeSize), { .lifecycleId = 41 });
1484
+ sendMessage(a, 2, 102, std::vector<uint8_t>(largeSize));
1485
+ sendMessage(a, 2, 103, std::vector<uint8_t>(largeSize), { .lifecycleId = 42 });
1486
+
1487
+ exchangeMessages(a, z);
1488
+ // In case of delayed ack.
1489
+ advanceTimeMs(a, z, a.sctpOptions.delayedAckMaxTimeoutMs);
1490
+ exchangeMessages(a, z);
1491
+
1492
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageDeliveredBeenCalledWithLifecycleId(41) == true);
1493
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(41) == true);
1494
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageDeliveredBeenCalledWithLifecycleId(42) == true);
1495
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(42) == true);
1496
+
1497
+ REQUIRE(getReceivedMessagePpids(z) == std::vector<uint32_t>{ 101, 102, 103 });
1498
+ }
1499
+
1500
+ SECTION("lifecycle events for an expired message with a lifetime limit")
1501
+ {
1502
+ AssociationUnderTest a;
1503
+ AssociationUnderTest z;
1504
+
1505
+ // Send it before the association is connected, to prevent it from being
1506
+ // sent too quickly. The idea is that it should be expired before even
1507
+ // attempting to send it in full.
1508
+ sendMessage(a, 1, 51, std::vector<uint8_t>(10), { .lifetimeMs = 100, .lifecycleId = 1 });
1509
+
1510
+ advanceTimeMs(a, z, 200);
1511
+
1512
+ connectAssociations(a, z);
1513
+ exchangeMessages(a, z);
1514
+
1515
+ REQUIRE(
1516
+ a.listener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(
1517
+ 1, /*maybeDelivered*/ false) == true);
1518
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1) == true);
1519
+
1520
+ REQUIRE(getReceivedMessagePpids(z).empty() == true);
1521
+ }
1522
+
1523
+ SECTION("responds with an error to an unknown chunk")
1524
+ {
1525
+ AssociationUnderTest a;
1526
+ AssociationUnderTest z;
1527
+
1528
+ const uint32_t verificationTag = connectAndGetVerificationTag(a, z);
1529
+
1530
+ std::vector<uint8_t> buffer(a.sctpOptions.mtu);
1531
+
1532
+ const auto packet = buildPacket(buffer, verificationTag);
1533
+
1534
+ // Build a 4-byte chunk and patch its type to an unknown value (0x49), whose
1535
+ // top two bits (01) request the receiver to skip it and report an error.
1536
+ auto* cookieAckChunk = packet->BuildChunkInPlace<RTC::SCTP::CookieAckChunk>();
1537
+
1538
+ cookieAckChunk->Consolidate();
1539
+
1540
+ const_cast<uint8_t*>(packet->GetBuffer())[12] = 0x49;
1541
+
1542
+ injectPacket(a, packet.get());
1543
+
1544
+ // A replies with an OPERATION-ERROR chunk carrying an Unrecognized Chunk
1545
+ // Type error cause.
1546
+ const auto buffer2 = a.listener.ConsumeFirstSentPacket();
1547
+ const auto replyPacket = parsePacket(buffer2);
1548
+
1549
+ REQUIRE(replyPacket);
1550
+
1551
+ const auto* operationErrorChunk =
1552
+ replyPacket->GetFirstChunkOfType<RTC::SCTP::OperationErrorChunk>();
1553
+
1554
+ REQUIRE(operationErrorChunk);
1555
+
1556
+ const auto* errorCause = operationErrorChunk->GetErrorCauseAt(0);
1557
+
1558
+ REQUIRE(errorCause);
1559
+ REQUIRE(errorCause->GetCode() == RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE);
1560
+ }
1561
+
1562
+ SECTION("reports a received error chunk as a callback")
1563
+ {
1564
+ AssociationUnderTest a;
1565
+ AssociationUnderTest z;
1566
+
1567
+ const uint32_t verificationTag = connectAndGetVerificationTag(a, z);
1568
+
1569
+ std::vector<uint8_t> buffer(a.sctpOptions.mtu);
1570
+
1571
+ const auto packet = buildPacket(buffer, verificationTag);
1572
+
1573
+ auto* operationErrorChunk = packet->BuildChunkInPlace<RTC::SCTP::OperationErrorChunk>();
1574
+ auto* unrecognizedChunkTypeErrorCause =
1575
+ operationErrorChunk->BuildErrorCauseInPlace<RTC::SCTP::UnrecognizedChunkTypeErrorCause>();
1576
+
1577
+ const uint8_t unknownChunk[]{ 0x49, 0x00, 0x00, 0x04 };
1578
+
1579
+ unrecognizedChunkTypeErrorCause->SetUnrecognizedChunk(unknownChunk, sizeof(unknownChunk));
1580
+ unrecognizedChunkTypeErrorCause->Consolidate();
1581
+ operationErrorChunk->Consolidate();
1582
+
1583
+ injectPacket(a, packet.get());
1584
+
1585
+ REQUIRE(a.listener.HasErrored() == true);
1586
+ REQUIRE(a.listener.GetErroredErrorKind() == RTC::SCTP::Types::ErrorKind::PEER_REPORTED);
1587
+ }
1588
+
1589
+ SECTION("answers a heartbeat request with an ack")
1590
+ {
1591
+ AssociationUnderTest a;
1592
+ AssociationUnderTest z;
1593
+
1594
+ const uint32_t verificationTag = connectAndGetVerificationTag(a, z);
1595
+
1596
+ std::vector<uint8_t> buffer(a.sctpOptions.mtu);
1597
+
1598
+ const auto packet = buildPacket(buffer, verificationTag);
1599
+
1600
+ auto* heartbeatRequestChunk = packet->BuildChunkInPlace<RTC::SCTP::HeartbeatRequestChunk>();
1601
+ auto* heartbeatInfoParameter =
1602
+ heartbeatRequestChunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();
1603
+
1604
+ const uint8_t info[]{ 1, 2, 3, 4 };
1605
+
1606
+ heartbeatInfoParameter->SetInfo(info, sizeof(info));
1607
+ heartbeatInfoParameter->Consolidate();
1608
+ heartbeatRequestChunk->Consolidate();
1609
+
1610
+ injectPacket(a, packet.get());
1611
+
1612
+ // A replies with a HEARTBEAT-ACK chunk echoing the Heartbeat Info.
1613
+ const auto buffer2 = a.listener.ConsumeFirstSentPacket();
1614
+ const auto replyPacket = parsePacket(buffer2);
1615
+
1616
+ REQUIRE(replyPacket);
1617
+
1618
+ const auto* heartbeatAckChunk = replyPacket->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>();
1619
+
1620
+ REQUIRE(heartbeatAckChunk);
1621
+
1622
+ const auto* replyInfoParameter =
1623
+ heartbeatAckChunk->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>();
1624
+
1625
+ REQUIRE(replyInfoParameter);
1626
+ REQUIRE(replyInfoParameter->HasInfo() == true);
1627
+ }
1628
+
1629
+ SECTION("sends a message with limited retransmissions")
1630
+ {
1631
+ // Disable message interleaving so that ordered DATA chunks (with SSN) are
1632
+ // used.
1633
+ auto sctpOptions = makeSctpOptions();
1634
+
1635
+ sctpOptions.enableMessageInterleaving = false;
1636
+
1637
+ AssociationUnderTest a(sctpOptions);
1638
+ AssociationUnderTest z(sctpOptions);
1639
+
1640
+ connectAssociations(a, z);
1641
+
1642
+ RTC::SCTP::SendMessageOptions sendMessageOptions;
1643
+
1644
+ sendMessageOptions.maxRetransmissions = 0;
1645
+
1646
+ const std::vector<uint8_t> payload(a.sctpOptions.mtu - 100);
1647
+
1648
+ sendMessage(a, 1, 51, payload, sendMessageOptions);
1649
+ sendMessage(a, 1, 52, payload, sendMessageOptions);
1650
+ sendMessage(a, 1, 53, payload, sendMessageOptions);
1651
+
1652
+ // Z reads the first DATA.
1653
+ deliverFirstSentPacket(a, z);
1654
+ // Second DATA is lost.
1655
+ a.listener.ConsumeFirstSentPacket();
1656
+ // Z reads the third DATA.
1657
+ deliverFirstSentPacket(a, z);
1658
+
1659
+ // Let everything settle: the missing chunk will be nacked, and only after
1660
+ // the t3-rtx timer expires will it be marked for retransmission and then,
1661
+ // since it has no retransmissions left, abandoned (triggering a FORWARD-TSN).
1662
+ // We exchange packets and advance timers (delayed ack and RTO) until there's
1663
+ // nothing more to do, instead of asserting the exact intermediate sequence.
1664
+ exchangeMessages(a, z);
1665
+ advanceTimeMs(a, z, a.sctpOptions.delayedAckMaxTimeoutMs);
1666
+ exchangeMessages(a, z);
1667
+ advanceTimeMs(a, z, a.sctpOptions.initialRtoMs);
1668
+ exchangeMessages(a, z);
1669
+ advanceTimeMs(a, z, a.sctpOptions.delayedAckMaxTimeoutMs);
1670
+ exchangeMessages(a, z);
1671
+
1672
+ // The first and third messages are delivered; the second one is abandoned.
1673
+ REQUIRE(getReceivedMessagePpids(z) == std::vector<uint32_t>{ 51, 53 });
1674
+ }
1675
+
1676
+ SECTION("lifecycle events for fail on max retransmissions")
1677
+ {
1678
+ auto sctpOptions = makeSctpOptions();
1679
+
1680
+ sctpOptions.enableMessageInterleaving = false;
1681
+
1682
+ AssociationUnderTest a(sctpOptions);
1683
+ AssociationUnderTest z(sctpOptions);
1684
+
1685
+ connectAssociations(a, z);
1686
+
1687
+ const std::vector<uint8_t> payload(a.sctpOptions.mtu - 100);
1688
+
1689
+ sendMessage(a, 1, 51, payload, { .maxRetransmissions = 0, .lifecycleId = 1 });
1690
+ sendMessage(a, 1, 52, payload, { .maxRetransmissions = 0, .lifecycleId = 2 });
1691
+ sendMessage(a, 1, 53, payload, { .maxRetransmissions = 0, .lifecycleId = 3 });
1692
+
1693
+ // Z reads the first DATA.
1694
+ deliverFirstSentPacket(a, z);
1695
+ // Second DATA is lost.
1696
+ a.listener.ConsumeFirstSentPacket();
1697
+
1698
+ exchangeMessages(a, z);
1699
+
1700
+ // Handle the delayed SACK.
1701
+ advanceTimeMs(a, z, a.sctpOptions.delayedAckMaxTimeoutMs);
1702
+ exchangeMessages(a, z);
1703
+
1704
+ // The chunk is now nacked. Let the RTO expire to discard the message.
1705
+ advanceTimeMs(a, z, a.sctpOptions.initialRtoMs);
1706
+ exchangeMessages(a, z);
1707
+
1708
+ // Handle the delayed SACK.
1709
+ advanceTimeMs(a, z, a.sctpOptions.delayedAckMaxTimeoutMs);
1710
+ exchangeMessages(a, z);
1711
+
1712
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageDeliveredBeenCalledWithLifecycleId(1) == true);
1713
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1) == true);
1714
+ REQUIRE(
1715
+ a.listener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(
1716
+ 2, /*maybeDelivered*/ true) == true);
1717
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(2) == true);
1718
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageDeliveredBeenCalledWithLifecycleId(3) == true);
1719
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(3) == true);
1720
+
1721
+ REQUIRE(getReceivedMessagePpids(z) == std::vector<uint32_t>{ 51, 53 });
1722
+ }
1723
+
1724
+ SECTION("lifecycle events for an expired message with a retransmit limit")
1725
+ {
1726
+ auto sctpOptions = makeSctpOptions();
1727
+
1728
+ sctpOptions.enableMessageInterleaving = false;
1729
+
1730
+ AssociationUnderTest a(sctpOptions);
1731
+ AssociationUnderTest z(sctpOptions);
1732
+
1733
+ connectAssociations(a, z);
1734
+
1735
+ // Will not be able to send it in full within the congestion window, so it
1736
+ // will need SACKs to be received for more fragments to be sent.
1737
+ const std::vector<uint8_t> payload(a.sctpOptions.mtu * 20);
1738
+
1739
+ sendMessage(a, 1, 51, payload, { .maxRetransmissions = 0, .lifecycleId = 1 });
1740
+
1741
+ // Z reads the first DATA.
1742
+ deliverFirstSentPacket(a, z);
1743
+ // Second DATA is lost.
1744
+ a.listener.ConsumeFirstSentPacket();
1745
+
1746
+ exchangeMessages(a, z);
1747
+
1748
+ REQUIRE(
1749
+ a.listener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(
1750
+ 1, /*maybeDelivered*/ false) == true);
1751
+ REQUIRE(a.listener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1) == true);
1752
+
1753
+ REQUIRE(getReceivedMessagePpids(z).empty() == true);
1754
+ }
1755
+ }