mediasoup 3.20.4 → 3.20.6

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 (49) hide show
  1. package/package.json +4 -4
  2. package/worker/include/RTC/Consumer.hpp +80 -45
  3. package/worker/include/RTC/PipeProducerStreamManager.hpp +78 -0
  4. package/worker/include/RTC/ProducerStreamManager.hpp +181 -0
  5. package/worker/include/RTC/SCTP/association/Association.hpp +24 -0
  6. package/worker/include/RTC/SCTP/association/StateCookie.hpp +104 -11
  7. package/worker/include/RTC/SCTP/packet/parameters/StateCookieParameter.hpp +4 -1
  8. package/worker/include/RTC/SCTP/public/SctpOptions.hpp +13 -0
  9. package/worker/include/RTC/SeqManager.hpp +2 -2
  10. package/worker/include/RTC/SimpleProducerStreamManager.hpp +72 -0
  11. package/worker/include/RTC/SimulcastProducerStreamManager.hpp +93 -0
  12. package/worker/include/RTC/SvcProducerStreamManager.hpp +72 -0
  13. package/worker/include/RTC/Transport.hpp +7 -1
  14. package/worker/meson.build +9 -5
  15. package/worker/src/RTC/Consumer.cpp +1404 -30
  16. package/worker/src/RTC/DirectTransport.cpp +4 -1
  17. package/worker/src/RTC/PipeProducerStreamManager.cpp +266 -0
  18. package/worker/src/RTC/PipeTransport.cpp +4 -1
  19. package/worker/src/RTC/PlainTransport.cpp +4 -1
  20. package/worker/src/RTC/SCTP/association/Association.cpp +142 -3
  21. package/worker/src/RTC/SCTP/association/StateCookie.cpp +96 -31
  22. package/worker/src/RTC/SCTP/association/StreamResetHandler.cpp +4 -0
  23. package/worker/src/RTC/SCTP/packet/Packet.cpp +1 -1
  24. package/worker/src/RTC/SCTP/packet/parameters/StateCookieParameter.cpp +12 -3
  25. package/worker/src/RTC/SCTP/public/SctpOptions.cpp +4 -0
  26. package/worker/src/RTC/SCTP/rx/DataTracker.cpp +4 -1
  27. package/worker/src/RTC/SeqManager.cpp +42 -29
  28. package/worker/src/RTC/SimpleProducerStreamManager.cpp +343 -0
  29. package/worker/src/RTC/SimulcastProducerStreamManager.cpp +1068 -0
  30. package/worker/src/RTC/SvcProducerStreamManager.cpp +664 -0
  31. package/worker/src/RTC/Transport.cpp +7 -44
  32. package/worker/src/RTC/WebRtcTransport.cpp +8 -2
  33. package/worker/test/include/RTC/SCTP/sctpCommon.hpp +1 -1
  34. package/worker/test/src/RTC/SCTP/association/TestAssociation.cpp +115 -0
  35. package/worker/test/src/RTC/SCTP/association/TestStateCookie.cpp +123 -0
  36. package/worker/test/src/RTC/SCTP/packet/TestPacket.cpp +4 -4
  37. package/worker/test/src/RTC/{TestSimpleConsumer.cpp → TestConsumer.cpp} +6 -7
  38. package/worker/test/src/RTC/TestPipeProducerStreamManager.cpp +471 -0
  39. package/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +531 -0
  40. package/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +1040 -0
  41. package/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +1278 -0
  42. package/worker/include/RTC/PipeConsumer.hpp +0 -95
  43. package/worker/include/RTC/SimpleConsumer.hpp +0 -102
  44. package/worker/include/RTC/SimulcastConsumer.hpp +0 -141
  45. package/worker/include/RTC/SvcConsumer.hpp +0 -118
  46. package/worker/src/RTC/PipeConsumer.cpp +0 -874
  47. package/worker/src/RTC/SimpleConsumer.cpp +0 -882
  48. package/worker/src/RTC/SimulcastConsumer.cpp +0 -1887
  49. package/worker/src/RTC/SvcConsumer.cpp +0 -1384
@@ -15,7 +15,10 @@ namespace RTC
15
15
  const std::string& id,
16
16
  RTC::Transport::Listener* listener,
17
17
  const FBS::DirectTransport::DirectTransportOptions* options)
18
- : RTC::Transport::Transport(shared, id, listener, options->base())
18
+ // DirectTransport doesn't support SCTP, so State Cookie authentication is
19
+ // irrelevant here.
20
+ : RTC::Transport::Transport(
21
+ shared, id, listener, options->base(), /*requireSctpStateCookieAuthentication*/ false)
19
22
  {
20
23
  MS_TRACE();
21
24
 
@@ -0,0 +1,266 @@
1
+ #define MS_CLASS "RTC::PipeProducerStreamManager"
2
+ // #define MS_LOG_DEV_LEVEL 3
3
+
4
+ #include "RTC/PipeProducerStreamManager.hpp"
5
+ #include "Logger.hpp"
6
+
7
+ namespace RTC
8
+ {
9
+ /* Instance methods. */
10
+
11
+ PipeProducerStreamManager::PipeProducerStreamManager(
12
+ const std::vector<RTC::RtpEncodingParameters>& consumableRtpEncodings,
13
+ const RTC::ConsumerTypes::VideoLayers& preferredLayers,
14
+ std::unique_ptr<RTC::RTP::Codecs::EncodingContext> encodingContext,
15
+ RTC::Media::Kind kind,
16
+ bool keyFrameSupported,
17
+ Listener* listener,
18
+ SharedInterface* shared)
19
+ : ProducerStreamManager(
20
+ consumableRtpEncodings,
21
+ preferredLayers,
22
+ std::move(encodingContext),
23
+ kind,
24
+ keyFrameSupported,
25
+ listener,
26
+ shared)
27
+ {
28
+ MS_TRACE();
29
+
30
+ // Initialize per-stream sync state for each consumable encoding.
31
+ for (const auto& encoding : this->consumableRtpEncodings)
32
+ {
33
+ this->mapMappedSsrcSyncRequired[encoding.ssrc] = false;
34
+ }
35
+ }
36
+
37
+ bool PipeProducerStreamManager::IsActive() const
38
+ {
39
+ MS_TRACE();
40
+
41
+ return this->listener->IsActive();
42
+ }
43
+
44
+ void PipeProducerStreamManager::ProducerRtpStream(
45
+ RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/)
46
+ {
47
+ MS_TRACE();
48
+
49
+ // Do nothing.
50
+ }
51
+
52
+ void PipeProducerStreamManager::ProducerNewRtpStream(
53
+ RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/)
54
+ {
55
+ MS_TRACE();
56
+
57
+ // Do nothing.
58
+ }
59
+
60
+ void PipeProducerStreamManager::ProducerRtpStreamScore(
61
+ RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)
62
+ {
63
+ MS_TRACE();
64
+
65
+ // Do nothing.
66
+ }
67
+
68
+ void PipeProducerStreamManager::ProducerRtcpSenderReport(
69
+ RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/)
70
+ {
71
+ MS_TRACE();
72
+
73
+ // Do nothing.
74
+ }
75
+
76
+ uint32_t PipeProducerStreamManager::IncreaseLayer(
77
+ uint32_t /*bitrate*/, bool /*considerLoss*/, float /*lossPercentage*/, uint64_t /*nowMs*/)
78
+ {
79
+ MS_TRACE();
80
+
81
+ // Pipe does not play the BWE game.
82
+ return 0u;
83
+ }
84
+
85
+ void PipeProducerStreamManager::ApplyLayers(uint64_t /*rtpStreamActiveMs*/)
86
+ {
87
+ MS_TRACE();
88
+
89
+ // Pipe does not play the BWE game.
90
+ }
91
+
92
+ uint32_t PipeProducerStreamManager::GetDesiredBitrate(uint64_t /*nowMs*/) const
93
+ {
94
+ MS_TRACE();
95
+
96
+ // Pipe does not play the BWE game.
97
+ return 0u;
98
+ }
99
+
100
+ ProducerStreamManager::RtpPacketProcessResult PipeProducerStreamManager::ProcessRtpPacket(
101
+ RTC::RTP::Packet* packet,
102
+ bool /*lastSentPacketHasMarker*/,
103
+ uint32_t /*clockRate*/,
104
+ uint32_t /*maxPacketTs*/)
105
+ {
106
+ MS_TRACE();
107
+
108
+ RtpPacketProcessResult result;
109
+
110
+ auto it = this->mapMappedSsrcSyncRequired.find(packet->GetSsrc());
111
+
112
+ MS_ASSERT(it != this->mapMappedSsrcSyncRequired.end(), "unknown mapped SSRC");
113
+
114
+ bool& syncRequired = it->second;
115
+
116
+ // If sync required and key frames supported, buffer non-key-frame packets.
117
+ if (syncRequired && this->keyFrameSupported && !packet->IsKeyFrame())
118
+ {
119
+ result.type = RtpPacketProcessResult::Type::BUFFER;
120
+
121
+ #ifdef MS_RTC_LOGGER_RTP
122
+ packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME);
123
+ #endif
124
+
125
+ return result;
126
+ }
127
+
128
+ // Packets with only padding are not forwarded.
129
+ if (packet->GetPayloadLength() == 0)
130
+ {
131
+ result.type = RtpPacketProcessResult::Type::DROP;
132
+
133
+ #ifdef MS_RTC_LOGGER_RTP
134
+ packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD);
135
+ #endif
136
+
137
+ return result;
138
+ }
139
+
140
+ // Whether this is the first packet after re-sync.
141
+ const bool isSyncPacket = syncRequired;
142
+
143
+ if (isSyncPacket)
144
+ {
145
+ if (packet->IsKeyFrame())
146
+ {
147
+ MS_DEBUG_TAG(
148
+ rtp,
149
+ "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]",
150
+ packet->GetSsrc(),
151
+ packet->GetSequenceNumber(),
152
+ packet->GetTimestamp());
153
+
154
+ result.sendBufferedPackets = true;
155
+ }
156
+
157
+ result.isSyncPacket = true;
158
+ result.syncSeqValue = packet->GetSequenceNumber() - 1;
159
+
160
+ syncRequired = false;
161
+ }
162
+
163
+ // Pipe passes timestamp and marker through unchanged.
164
+ result.type = RtpPacketProcessResult::Type::FORWARD;
165
+ result.tsOffset = 0u;
166
+ result.marker = packet->HasMarker();
167
+
168
+ return result;
169
+ }
170
+
171
+ void PipeProducerStreamManager::RequestKeyFrame()
172
+ {
173
+ MS_TRACE();
174
+
175
+ if (this->kind != RTC::Media::Kind::VIDEO)
176
+ {
177
+ return;
178
+ }
179
+
180
+ for (const auto& encoding : this->consumableRtpEncodings)
181
+ {
182
+ this->listener->OnProducerStreamManagerKeyFrameRequested(encoding.ssrc);
183
+ }
184
+ }
185
+
186
+ void PipeProducerStreamManager::RequestKeyFrameForTargetSpatialLayer()
187
+ {
188
+ MS_TRACE();
189
+
190
+ RequestKeyFrame();
191
+ }
192
+
193
+ void PipeProducerStreamManager::RequestKeyFrameForCurrentSpatialLayer()
194
+ {
195
+ MS_TRACE();
196
+
197
+ RequestKeyFrame();
198
+ }
199
+
200
+ void PipeProducerStreamManager::UpdateTargetLayers(int16_t /*spatial*/, int16_t /*temporal*/)
201
+ {
202
+ MS_TRACE();
203
+
204
+ // No layer management for pipe.
205
+ }
206
+
207
+ bool PipeProducerStreamManager::RecalculateTargetLayers(
208
+ RTC::ConsumerTypes::VideoLayers& /*newTargetLayers*/) const
209
+ {
210
+ MS_TRACE();
211
+
212
+ // No layer changes for pipe.
213
+ return false;
214
+ }
215
+
216
+ void PipeProducerStreamManager::OnTransportConnected()
217
+ {
218
+ MS_TRACE();
219
+
220
+ for (auto& kv : this->mapMappedSsrcSyncRequired)
221
+ {
222
+ kv.second = true;
223
+ }
224
+
225
+ if (IsActive())
226
+ {
227
+ RequestKeyFrame();
228
+ }
229
+ }
230
+
231
+ void PipeProducerStreamManager::OnTransportDisconnected()
232
+ {
233
+ MS_TRACE();
234
+
235
+ for (auto& kv : this->mapMappedSsrcSyncRequired)
236
+ {
237
+ kv.second = false;
238
+ }
239
+ }
240
+
241
+ void PipeProducerStreamManager::OnPaused()
242
+ {
243
+ MS_TRACE();
244
+
245
+ for (auto& kv : this->mapMappedSsrcSyncRequired)
246
+ {
247
+ kv.second = false;
248
+ }
249
+ }
250
+
251
+ void PipeProducerStreamManager::OnResumed()
252
+ {
253
+ MS_TRACE();
254
+
255
+ for (auto& kv : this->mapMappedSsrcSyncRequired)
256
+ {
257
+ kv.second = true;
258
+ }
259
+
260
+ if (IsActive())
261
+ {
262
+ RequestKeyFrame();
263
+ }
264
+ }
265
+
266
+ } // namespace RTC
@@ -29,7 +29,10 @@ namespace RTC
29
29
  const std::string& id,
30
30
  RTC::Transport::Listener* listener,
31
31
  const FBS::PipeTransport::PipeTransportOptions* options)
32
- : RTC::Transport::Transport(shared, id, listener, options->base())
32
+ // SCTP over PipeTransport is not protected by DTLS, so received State
33
+ // Cookies must be authenticated to prevent forgery (RFC 9260 section 5.1.3).
34
+ : RTC::Transport::Transport(
35
+ shared, id, listener, options->base(), /*requireSctpStateCookieAuthentication*/ true)
33
36
  {
34
37
  MS_TRACE();
35
38
 
@@ -37,7 +37,10 @@ namespace RTC
37
37
  const std::string& id,
38
38
  RTC::Transport::Listener* listener,
39
39
  const FBS::PlainTransport::PlainTransportOptions* options)
40
- : RTC::Transport::Transport(shared, id, listener, options->base())
40
+ // SCTP over PlainTransport is not protected by DTLS, so received State
41
+ // Cookies must be authenticated to prevent forgery (RFC 9260 section 5.1.3).
42
+ : RTC::Transport::Transport(
43
+ shared, id, listener, options->base(), /*requireSctpStateCookieAuthentication*/ true)
41
44
  {
42
45
  MS_TRACE();
43
46
 
@@ -7,6 +7,7 @@
7
7
  #include "RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp"
8
8
  #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp"
9
9
  #include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp"
10
+ #include "RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp"
10
11
  #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp"
11
12
  #include "RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp"
12
13
  #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp"
@@ -83,6 +84,15 @@ namespace RTC
83
84
  mayConnectOnReceivedSctpData(mayConnectOnReceivedSctpData)
84
85
  {
85
86
  MS_TRACE();
87
+
88
+ // Generate the per-association secret used to authenticate the State
89
+ // Cookies we generate.
90
+ //
91
+ // @see RFC 9260 section 5.1.3.
92
+ if (this->sctpOptions.requireAuthenticatedCookie)
93
+ {
94
+ Utils::Crypto::WriteRandomBytes(this->stateCookieSecret, Association::StateCookieSecretLength);
95
+ }
86
96
  }
87
97
 
88
98
  Association::~Association()
@@ -1057,6 +1067,32 @@ namespace RTC
1057
1067
  {
1058
1068
  MS_TRACE();
1059
1069
 
1070
+ // https://datatracker.ietf.org/doc/html/rfc9653#section-5.3
1071
+ //
1072
+ // "If an endpoint has sent the Zero Checksum Acceptable Chunk Parameter
1073
+ // indicating the support of an alternate error detection method in an INIT
1074
+ // or INIT ACK chunk, in addition to SCTP packets containing the correct
1075
+ // CRC32c checksum value it MUST accept SCTP packets that have an incorrect
1076
+ // checksum value of zero [...]"
1077
+ const bool acceptZeroChecksum =
1078
+ this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod !=
1079
+ ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE;
1080
+
1081
+ // Validate the CRC32c checksum unless this is a zero checksum packet that we
1082
+ // announced we are willing to accept.
1083
+ if (!acceptZeroChecksum || receivedPacket->GetChecksum() != 0)
1084
+ {
1085
+ if (!receivedPacket->ValidateCRC32cChecksum())
1086
+ {
1087
+ MS_WARN_TAG(sctp, "invalid CRC32c checksum, packet discarded");
1088
+
1089
+ this->associationListenerDeferrer.OnAssociationError(
1090
+ Types::ErrorKind::PARSE_FAILED, "invalid CRC32c checksum");
1091
+
1092
+ return false;
1093
+ }
1094
+ }
1095
+
1060
1096
  const uint32_t localVerificationTag = this->tcb ? this->tcb->GetLocalVerificationTag() : 0;
1061
1097
 
1062
1098
  // https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1
@@ -1462,7 +1498,9 @@ namespace RTC
1462
1498
 
1463
1499
  case State::CLOSED:
1464
1500
  {
1465
- MS_WARN_TAG(sctp, "ignoring INIT chunk received in CLOSED state)");
1501
+ MS_WARN_TAG(sctp, "ignoring INIT chunk received in CLOSED state");
1502
+
1503
+ return;
1466
1504
  }
1467
1505
 
1468
1506
  // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.1
@@ -1535,6 +1573,12 @@ namespace RTC
1535
1573
  const auto negotiatedCapabilities =
1536
1574
  NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitChunk);
1537
1575
 
1576
+ // When authentication is enabled, generate an authenticated cookie with a
1577
+ // creation timestamp and a MAC keyed with our per-association secret.
1578
+ //
1579
+ // @see RFC 9260 section 5.1.3.
1580
+ const bool authenticateCookie = this->sctpOptions.requireAuthenticatedCookie;
1581
+
1538
1582
  // Write the StateCookie in place in the parameter.
1539
1583
  stateCookieParameter->WriteStateCookieInPlace(
1540
1584
  localVerificationTag,
@@ -1543,7 +1587,10 @@ namespace RTC
1543
1587
  receivedInitChunk->GetInitialTsn(),
1544
1588
  receivedInitChunk->GetAdvertisedReceiverWindowCredit(),
1545
1589
  tieTag,
1546
- negotiatedCapabilities);
1590
+ negotiatedCapabilities,
1591
+ /*creationTimestampMs*/ authenticateCookie ? this->shared->GetTimeMs() : 0,
1592
+ /*macKey*/ authenticateCookie ? this->stateCookieSecret : nullptr,
1593
+ /*macKeyLength*/ authenticateCookie ? Association::StateCookieSecretLength : 0);
1547
1594
 
1548
1595
  stateCookieParameter->Consolidate();
1549
1596
 
@@ -1676,6 +1723,18 @@ namespace RTC
1676
1723
  return;
1677
1724
  }
1678
1725
 
1726
+ // When authentication is enabled, verify that the cookie was generated by
1727
+ // us (valid MAC) and is not stale before trusting any of its contents.
1728
+ //
1729
+ // @see RFC 9260 section 5.1.4.
1730
+ if (this->sctpOptions.requireAuthenticatedCookie)
1731
+ {
1732
+ if (!VerifyReceivedStateCookie(cookie.get()))
1733
+ {
1734
+ return;
1735
+ }
1736
+ }
1737
+
1679
1738
  if (this->tcb)
1680
1739
  {
1681
1740
  if (!HandleReceivedCookieEchoChunkWithTcb(receivedPacket, cookie.get()))
@@ -1812,7 +1871,7 @@ namespace RTC
1812
1871
  else if (
1813
1872
  receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() &&
1814
1873
  cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag() &&
1815
- cookie->GetTieTag() == this->tcb->GetTieTag())
1874
+ cookie->GetTieTag() == 0)
1816
1875
  {
1817
1876
  MS_DEBUG_DEV("received COOKIE-ECHO indicating a late COOKIE-ECHO, discarding");
1818
1877
 
@@ -2430,6 +2489,86 @@ namespace RTC
2430
2489
  return !skipProcessing;
2431
2490
  }
2432
2491
 
2492
+ bool Association::VerifyReceivedStateCookie(const StateCookie* cookie)
2493
+ {
2494
+ MS_TRACE();
2495
+
2496
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-5.1.4
2497
+ //
2498
+ // "Authenticate the State Cookie as one that it previously generated by
2499
+ // comparing the computed MAC against the one carried in the State Cookie.
2500
+ // If this comparison fails, the SCTP packet, including the COOKIE ECHO and
2501
+ // any DATA chunks, should be silently discarded."
2502
+ //
2503
+ // NOTE: The Verification Tag check (RFC 9260 section 5.1.4) is not done
2504
+ // here but in the COOKIE-ECHO handling logic (with and without TCB), which
2505
+ // is performed regardless of whether authentication is enabled.
2506
+ if (!StateCookie::VerifyMac(
2507
+ cookie->GetBuffer(),
2508
+ cookie->GetLength(),
2509
+ this->stateCookieSecret,
2510
+ Association::StateCookieSecretLength))
2511
+ {
2512
+ MS_WARN_TAG(
2513
+ sctp,
2514
+ "received COOKIE-ECHO chunk with State Cookie that failed MAC authentication, discarding");
2515
+
2516
+ this->associationListenerDeferrer.OnAssociationError(
2517
+ Types::ErrorKind::PARSE_FAILED,
2518
+ "received COOKIE-ECHO chunk with unauthenticated State Cookie");
2519
+
2520
+ return false;
2521
+ }
2522
+
2523
+ // https://datatracker.ietf.org/doc/html/rfc9260#section-5.1.4
2524
+ //
2525
+ // "Compare the creation timestamp in the State Cookie to the current local
2526
+ // time. If the elapsed time is longer than the lifespan carried in the
2527
+ // State Cookie, then the packet, including the COOKIE ECHO and any attached
2528
+ // DATA chunks, SHOULD be discarded, and the endpoint MUST transmit an ERROR
2529
+ // chunk with a 'Stale Cookie' error cause to the peer endpoint."
2530
+ const uint64_t nowMs = this->shared->GetTimeMs();
2531
+ const uint64_t creationMs = cookie->GetCreationTimestampMs();
2532
+
2533
+ if (nowMs > creationMs && nowMs - creationMs > StateCookie::ValidCookieLifeMs)
2534
+ {
2535
+ const uint64_t stalenessMs = nowMs - creationMs - StateCookie::ValidCookieLifeMs;
2536
+
2537
+ MS_WARN_TAG(
2538
+ sctp,
2539
+ "received COOKIE-ECHO chunk with stale State Cookie, sending Stale Cookie error [staleness:%" PRIu64
2540
+ "ms]",
2541
+ stalenessMs);
2542
+
2543
+ // Respond with an ERROR chunk carrying a Stale Cookie error cause. The
2544
+ // packet must use the verification tag the peer expects (the remote
2545
+ // verification tag stored in the cookie we generated).
2546
+ auto packet = CreatePacketWithVerificationTag(cookie->GetRemoteVerificationTag());
2547
+ auto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();
2548
+ auto* staleCookieErrorCause =
2549
+ operationErrorChunk->BuildErrorCauseInPlace<StaleCookieErrorCause>();
2550
+
2551
+ // The Measure of Staleness is expressed in microseconds.
2552
+ const uint64_t stalenessUs = stalenessMs * 1000;
2553
+
2554
+ staleCookieErrorCause->SetMeasureOfStaleness(
2555
+ stalenessUs > std::numeric_limits<uint32_t>::max() ? std::numeric_limits<uint32_t>::max()
2556
+ : static_cast<uint32_t>(stalenessUs));
2557
+
2558
+ staleCookieErrorCause->Consolidate();
2559
+ operationErrorChunk->Consolidate();
2560
+
2561
+ this->packetSender.SendPacket(packet.get());
2562
+
2563
+ this->associationListenerDeferrer.OnAssociationError(
2564
+ Types::ErrorKind::WRONG_SEQUENCE, "received COOKIE-ECHO chunk with stale State Cookie");
2565
+
2566
+ return false;
2567
+ }
2568
+
2569
+ return true;
2570
+ }
2571
+
2433
2572
  void Association::OnT1InitTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)
2434
2573
  {
2435
2574
  MS_TRACE();
@@ -4,6 +4,8 @@
4
4
  #include "RTC/SCTP/association/StateCookie.hpp"
5
5
  #include "Logger.hpp"
6
6
  #include "MediaSoupErrors.hpp"
7
+ #include <cstring> // std::memcpy(), std::memcmp()
8
+ #include <string_view>
7
9
 
8
10
  namespace RTC
9
11
  {
@@ -11,31 +13,6 @@ namespace RTC
11
13
  {
12
14
  /* Class methods. */
13
15
 
14
- bool StateCookie::IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength)
15
- {
16
- MS_TRACE();
17
-
18
- if (bufferLength != StateCookie::StateCookieLength)
19
- {
20
- return false;
21
- }
22
-
23
- if (Utils::Byte::Get8Bytes(buffer, 0) != StateCookie::Magic1)
24
- {
25
- return false;
26
- }
27
-
28
- auto* negotiatedCapabilitiesField = reinterpret_cast<NegotiatedCapabilitiesField*>(
29
- const_cast<uint8_t*>(buffer) + StateCookie::NegotiatedCapabilitiesOffset);
30
-
31
- if (ntohs(negotiatedCapabilitiesField->magic2) != StateCookie::Magic2)
32
- {
33
- return false;
34
- }
35
-
36
- return true;
37
- }
38
-
39
16
  StateCookie* StateCookie::Parse(const uint8_t* buffer, size_t bufferLength)
40
17
  {
41
18
  MS_TRACE();
@@ -61,7 +38,10 @@ namespace RTC
61
38
  uint32_t remoteInitialTsn,
62
39
  uint32_t remoteAdvertisedReceiverWindowCredit,
63
40
  uint64_t tieTag,
64
- const NegotiatedCapabilities& negotiatedCapabilities)
41
+ const NegotiatedCapabilities& negotiatedCapabilities,
42
+ uint64_t creationTimestampMs,
43
+ const uint8_t* macKey,
44
+ size_t macKeyLength)
65
45
  {
66
46
  MS_TRACE();
67
47
 
@@ -75,9 +55,15 @@ namespace RTC
75
55
  remoteInitialTsn,
76
56
  remoteAdvertisedReceiverWindowCredit,
77
57
  tieTag,
78
- negotiatedCapabilities);
58
+ negotiatedCapabilities,
59
+ creationTimestampMs,
60
+ macKey,
61
+ macKeyLength);
62
+
63
+ const size_t cookieLength = macKey != nullptr ? StateCookie::AuthenticatedStateCookieLength
64
+ : StateCookie::StateCookieLength;
79
65
 
80
- return new StateCookie(buffer, StateCookie::StateCookieLength);
66
+ return new StateCookie(buffer, cookieLength);
81
67
  }
82
68
 
83
69
  void StateCookie::Write(
@@ -89,11 +75,18 @@ namespace RTC
89
75
  uint32_t remoteInitialTsn,
90
76
  uint32_t remoteAdvertisedReceiverWindowCredit,
91
77
  uint64_t tieTag,
92
- const NegotiatedCapabilities& negotiatedCapabilities)
78
+ const NegotiatedCapabilities& negotiatedCapabilities,
79
+ uint64_t creationTimestampMs,
80
+ const uint8_t* macKey,
81
+ size_t macKeyLength)
93
82
  {
94
83
  MS_TRACE();
95
84
 
96
- if (bufferLength < StateCookie::StateCookieLength)
85
+ const bool authenticate = macKey != nullptr;
86
+ const size_t cookieLength =
87
+ authenticate ? StateCookie::AuthenticatedStateCookieLength : StateCookie::StateCookieLength;
88
+
89
+ if (bufferLength < cookieLength)
97
90
  {
98
91
  MS_THROW_TYPE_ERROR("buffer too small");
99
92
  }
@@ -119,6 +112,65 @@ namespace RTC
119
112
  htons(negotiatedCapabilities.negotiatedMaxOutboundStreams);
120
113
  negotiatedCapabilitiesField->negotiatedMaxInboundStreams =
121
114
  htons(negotiatedCapabilities.negotiatedMaxInboundStreams);
115
+
116
+ if (!authenticate)
117
+ {
118
+ return;
119
+ }
120
+
121
+ // Append the creation timestamp and the MAC computed over all preceding
122
+ // bytes (including the timestamp).
123
+ //
124
+ // @see RFC 9260 section 5.1.3.
125
+ Utils::Byte::Set8Bytes(buffer, StateCookie::TimestampOffset, creationTimestampMs);
126
+
127
+ const uint8_t* mac = Utils::Crypto::GetHmacSha1(
128
+ reinterpret_cast<const char*>(macKey), macKeyLength, buffer, StateCookie::MacOffset);
129
+
130
+ std::memcpy(buffer + StateCookie::MacOffset, mac, StateCookie::MacLength);
131
+ }
132
+
133
+ bool StateCookie::IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength)
134
+ {
135
+ MS_TRACE();
136
+
137
+ if (bufferLength != StateCookie::StateCookieLength && bufferLength != StateCookie::AuthenticatedStateCookieLength)
138
+ {
139
+ return false;
140
+ }
141
+
142
+ if (Utils::Byte::Get8Bytes(buffer, 0) != StateCookie::Magic1)
143
+ {
144
+ return false;
145
+ }
146
+
147
+ auto* negotiatedCapabilitiesField = reinterpret_cast<NegotiatedCapabilitiesField*>(
148
+ const_cast<uint8_t*>(buffer) + StateCookie::NegotiatedCapabilitiesOffset);
149
+
150
+ if (ntohs(negotiatedCapabilitiesField->magic2) != StateCookie::Magic2)
151
+ {
152
+ return false;
153
+ }
154
+
155
+ return true;
156
+ }
157
+
158
+ bool StateCookie::VerifyMac(
159
+ const uint8_t* buffer, size_t bufferLength, const uint8_t* macKey, size_t macKeyLength)
160
+ {
161
+ MS_TRACE();
162
+
163
+ // An authenticated cookie has a fixed length.
164
+ if (bufferLength != StateCookie::AuthenticatedStateCookieLength)
165
+ {
166
+ return false;
167
+ }
168
+
169
+ // Recompute the MAC over all bytes preceding the MAC field.
170
+ const uint8_t* expectedMac = Utils::Crypto::GetHmacSha1(
171
+ reinterpret_cast<const char*>(macKey), macKeyLength, buffer, StateCookie::MacOffset);
172
+
173
+ return std::memcmp(buffer + StateCookie::MacOffset, expectedMac, StateCookie::MacLength) == 0;
122
174
  }
123
175
 
124
176
  Types::SctpImplementation StateCookie::DetermineSctpImplementation(
@@ -158,7 +210,13 @@ namespace RTC
158
210
  {
159
211
  MS_TRACE();
160
212
 
161
- SetLength(StateCookie::StateCookieLength);
213
+ // The cookie length is determined by whether it carries a MAC. When the
214
+ // cookie is cloned into a larger buffer, `CloneInto()` will set the
215
+ // proper length afterwards.
216
+ SetLength(
217
+ bufferLength == StateCookie::AuthenticatedStateCookieLength
218
+ ? StateCookie::AuthenticatedStateCookieLength
219
+ : StateCookie::StateCookieLength);
162
220
  }
163
221
 
164
222
  StateCookie::~StateCookie()
@@ -183,6 +241,13 @@ namespace RTC
183
241
  " remote advertised receiver window credit: %" PRIu32,
184
242
  GetRemoteAdvertisedReceiverWindowCredit());
185
243
  MS_DUMP_CLEAN(indentation, " tie-tag: %" PRIu64, GetTieTag());
244
+ MS_DUMP_CLEAN(indentation, " authenticated: %s", IsAuthenticated() ? "yes" : "no");
245
+
246
+ if (IsAuthenticated())
247
+ {
248
+ MS_DUMP_CLEAN(indentation, " creation timestamp (ms): %" PRIu64, GetCreationTimestampMs());
249
+ }
250
+
186
251
  negotiatedCapabilities.Dump(indentation + 1);
187
252
  MS_DUMP_CLEAN(indentation, "</SCTP::StateCookie>");
188
253
  }
@@ -376,6 +376,10 @@ namespace RTC
376
376
  ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO);
377
377
 
378
378
  reconfigurationResponseParameter->Consolidate();
379
+
380
+ this->lastProcessedReqSeqNbr = requestSn;
381
+ this->lastProcessedReqResult =
382
+ ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO;
379
383
  }
380
384
  else
381
385
  {