1 /*
2 * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10 #include "net/dcsctp/tx/outstanding_data.h"
11
12 #include <algorithm>
13 #include <set>
14 #include <utility>
15 #include <vector>
16
17 #include "net/dcsctp/common/math.h"
18 #include "net/dcsctp/common/sequence_numbers.h"
19 #include "net/dcsctp/public/types.h"
20 #include "rtc_base/logging.h"
21
22 namespace dcsctp {
23
24 // The number of times a packet must be NACKed before it's retransmitted.
25 // See https://tools.ietf.org/html/rfc4960#section-7.2.4
26 constexpr uint8_t kNumberOfNacksForRetransmission = 3;
27
28 // Returns how large a chunk will be, serialized, carrying the data
GetSerializedChunkSize(const Data & data) const29 size_t OutstandingData::GetSerializedChunkSize(const Data& data) const {
30 return RoundUpTo4(data_chunk_header_size_ + data.size());
31 }
32
Ack()33 void OutstandingData::Item::Ack() {
34 if (lifecycle_ != Lifecycle::kAbandoned) {
35 lifecycle_ = Lifecycle::kActive;
36 }
37 ack_state_ = AckState::kAcked;
38 }
39
Nack(bool retransmit_now)40 OutstandingData::Item::NackAction OutstandingData::Item::Nack(
41 bool retransmit_now) {
42 ack_state_ = AckState::kNacked;
43 ++nack_count_;
44 if (!should_be_retransmitted() && !is_abandoned() &&
45 (retransmit_now || nack_count_ >= kNumberOfNacksForRetransmission)) {
46 // Nacked enough times - it's considered lost.
47 if (num_retransmissions_ < *max_retransmissions_) {
48 lifecycle_ = Lifecycle::kToBeRetransmitted;
49 return NackAction::kRetransmit;
50 }
51 Abandon();
52 return NackAction::kAbandon;
53 }
54 return NackAction::kNothing;
55 }
56
MarkAsRetransmitted()57 void OutstandingData::Item::MarkAsRetransmitted() {
58 lifecycle_ = Lifecycle::kActive;
59 ack_state_ = AckState::kUnacked;
60
61 nack_count_ = 0;
62 ++num_retransmissions_;
63 }
64
Abandon()65 void OutstandingData::Item::Abandon() {
66 lifecycle_ = Lifecycle::kAbandoned;
67 }
68
has_expired(TimeMs now) const69 bool OutstandingData::Item::has_expired(TimeMs now) const {
70 return expires_at_ <= now;
71 }
72
IsConsistent() const73 bool OutstandingData::IsConsistent() const {
74 size_t actual_outstanding_bytes = 0;
75 size_t actual_outstanding_items = 0;
76
77 std::set<UnwrappedTSN> combined_to_be_retransmitted;
78 combined_to_be_retransmitted.insert(to_be_retransmitted_.begin(),
79 to_be_retransmitted_.end());
80 combined_to_be_retransmitted.insert(to_be_fast_retransmitted_.begin(),
81 to_be_fast_retransmitted_.end());
82
83 std::set<UnwrappedTSN> actual_combined_to_be_retransmitted;
84 for (const auto& [tsn, item] : outstanding_data_) {
85 if (item.is_outstanding()) {
86 actual_outstanding_bytes += GetSerializedChunkSize(item.data());
87 ++actual_outstanding_items;
88 }
89
90 if (item.should_be_retransmitted()) {
91 actual_combined_to_be_retransmitted.insert(tsn);
92 }
93 }
94
95 if (outstanding_data_.empty() &&
96 next_tsn_ != last_cumulative_tsn_ack_.next_value()) {
97 return false;
98 }
99
100 return actual_outstanding_bytes == outstanding_bytes_ &&
101 actual_outstanding_items == outstanding_items_ &&
102 actual_combined_to_be_retransmitted == combined_to_be_retransmitted;
103 }
104
AckChunk(AckInfo & ack_info,std::map<UnwrappedTSN,Item>::iterator iter)105 void OutstandingData::AckChunk(AckInfo& ack_info,
106 std::map<UnwrappedTSN, Item>::iterator iter) {
107 if (!iter->second.is_acked()) {
108 size_t serialized_size = GetSerializedChunkSize(iter->second.data());
109 ack_info.bytes_acked += serialized_size;
110 if (iter->second.is_outstanding()) {
111 outstanding_bytes_ -= serialized_size;
112 --outstanding_items_;
113 }
114 if (iter->second.should_be_retransmitted()) {
115 RTC_DCHECK(to_be_fast_retransmitted_.find(iter->first) ==
116 to_be_fast_retransmitted_.end());
117 to_be_retransmitted_.erase(iter->first);
118 }
119 iter->second.Ack();
120 ack_info.highest_tsn_acked =
121 std::max(ack_info.highest_tsn_acked, iter->first);
122 }
123 }
124
HandleSack(UnwrappedTSN cumulative_tsn_ack,rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,bool is_in_fast_recovery)125 OutstandingData::AckInfo OutstandingData::HandleSack(
126 UnwrappedTSN cumulative_tsn_ack,
127 rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,
128 bool is_in_fast_recovery) {
129 OutstandingData::AckInfo ack_info(cumulative_tsn_ack);
130 // Erase all items up to cumulative_tsn_ack.
131 RemoveAcked(cumulative_tsn_ack, ack_info);
132
133 // ACK packets reported in the gap ack blocks
134 AckGapBlocks(cumulative_tsn_ack, gap_ack_blocks, ack_info);
135
136 // NACK and possibly mark for retransmit chunks that weren't acked.
137 NackBetweenAckBlocks(cumulative_tsn_ack, gap_ack_blocks, is_in_fast_recovery,
138 ack_info);
139
140 RTC_DCHECK(IsConsistent());
141 return ack_info;
142 }
143
RemoveAcked(UnwrappedTSN cumulative_tsn_ack,AckInfo & ack_info)144 void OutstandingData::RemoveAcked(UnwrappedTSN cumulative_tsn_ack,
145 AckInfo& ack_info) {
146 auto first_unacked = outstanding_data_.upper_bound(cumulative_tsn_ack);
147
148 for (auto iter = outstanding_data_.begin(); iter != first_unacked; ++iter) {
149 AckChunk(ack_info, iter);
150 if (iter->second.lifecycle_id().IsSet()) {
151 RTC_DCHECK(iter->second.data().is_end);
152 if (iter->second.is_abandoned()) {
153 ack_info.abandoned_lifecycle_ids.push_back(iter->second.lifecycle_id());
154 } else {
155 ack_info.acked_lifecycle_ids.push_back(iter->second.lifecycle_id());
156 }
157 }
158 }
159
160 outstanding_data_.erase(outstanding_data_.begin(), first_unacked);
161 last_cumulative_tsn_ack_ = cumulative_tsn_ack;
162 }
163
AckGapBlocks(UnwrappedTSN cumulative_tsn_ack,rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,AckInfo & ack_info)164 void OutstandingData::AckGapBlocks(
165 UnwrappedTSN cumulative_tsn_ack,
166 rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,
167 AckInfo& ack_info) {
168 // Mark all non-gaps as ACKED (but they can't be removed) as (from RFC)
169 // "SCTP considers the information carried in the Gap Ack Blocks in the
170 // SACK chunk as advisory.". Note that when NR-SACK is supported, this can be
171 // handled differently.
172
173 for (auto& block : gap_ack_blocks) {
174 auto start = outstanding_data_.lower_bound(
175 UnwrappedTSN::AddTo(cumulative_tsn_ack, block.start));
176 auto end = outstanding_data_.upper_bound(
177 UnwrappedTSN::AddTo(cumulative_tsn_ack, block.end));
178 for (auto iter = start; iter != end; ++iter) {
179 AckChunk(ack_info, iter);
180 }
181 }
182 }
183
NackBetweenAckBlocks(UnwrappedTSN cumulative_tsn_ack,rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,bool is_in_fast_recovery,OutstandingData::AckInfo & ack_info)184 void OutstandingData::NackBetweenAckBlocks(
185 UnwrappedTSN cumulative_tsn_ack,
186 rtc::ArrayView<const SackChunk::GapAckBlock> gap_ack_blocks,
187 bool is_in_fast_recovery,
188 OutstandingData::AckInfo& ack_info) {
189 // Mark everything between the blocks as NACKED/TO_BE_RETRANSMITTED.
190 // https://tools.ietf.org/html/rfc4960#section-7.2.4
191 // "Mark the DATA chunk(s) with three miss indications for retransmission."
192 // "For each incoming SACK, miss indications are incremented only for
193 // missing TSNs prior to the highest TSN newly acknowledged in the SACK."
194 //
195 // What this means is that only when there is a increasing stream of data
196 // received and there are new packets seen (since last time), packets that are
197 // in-flight and between gaps should be nacked. This means that SCTP relies on
198 // the T3-RTX-timer to re-send packets otherwise.
199 UnwrappedTSN max_tsn_to_nack = ack_info.highest_tsn_acked;
200 if (is_in_fast_recovery && cumulative_tsn_ack > last_cumulative_tsn_ack_) {
201 // https://tools.ietf.org/html/rfc4960#section-7.2.4
202 // "If an endpoint is in Fast Recovery and a SACK arrives that advances
203 // the Cumulative TSN Ack Point, the miss indications are incremented for
204 // all TSNs reported missing in the SACK."
205 max_tsn_to_nack = UnwrappedTSN::AddTo(
206 cumulative_tsn_ack,
207 gap_ack_blocks.empty() ? 0 : gap_ack_blocks.rbegin()->end);
208 }
209
210 UnwrappedTSN prev_block_last_acked = cumulative_tsn_ack;
211 for (auto& block : gap_ack_blocks) {
212 UnwrappedTSN cur_block_first_acked =
213 UnwrappedTSN::AddTo(cumulative_tsn_ack, block.start);
214 for (auto iter = outstanding_data_.upper_bound(prev_block_last_acked);
215 iter != outstanding_data_.lower_bound(cur_block_first_acked); ++iter) {
216 if (iter->first <= max_tsn_to_nack) {
217 ack_info.has_packet_loss |=
218 NackItem(iter->first, iter->second, /*retransmit_now=*/false,
219 /*do_fast_retransmit=*/!is_in_fast_recovery);
220 }
221 }
222 prev_block_last_acked = UnwrappedTSN::AddTo(cumulative_tsn_ack, block.end);
223 }
224
225 // Note that packets are not NACKED which are above the highest gap-ack-block
226 // (or above the cumulative ack TSN if no gap-ack-blocks) as only packets
227 // up until the highest_tsn_acked (see above) should be considered when
228 // NACKing.
229 }
230
NackItem(UnwrappedTSN tsn,Item & item,bool retransmit_now,bool do_fast_retransmit)231 bool OutstandingData::NackItem(UnwrappedTSN tsn,
232 Item& item,
233 bool retransmit_now,
234 bool do_fast_retransmit) {
235 if (item.is_outstanding()) {
236 outstanding_bytes_ -= GetSerializedChunkSize(item.data());
237 --outstanding_items_;
238 }
239
240 switch (item.Nack(retransmit_now)) {
241 case Item::NackAction::kNothing:
242 return false;
243 case Item::NackAction::kRetransmit:
244 if (do_fast_retransmit) {
245 to_be_fast_retransmitted_.insert(tsn);
246 } else {
247 to_be_retransmitted_.insert(tsn);
248 }
249 RTC_DLOG(LS_VERBOSE) << *tsn.Wrap() << " marked for retransmission";
250 break;
251 case Item::NackAction::kAbandon:
252 AbandonAllFor(item);
253 break;
254 }
255 return true;
256 }
257
AbandonAllFor(const Item & item)258 void OutstandingData::AbandonAllFor(const Item& item) {
259 // Erase all remaining chunks from the producer, if any.
260 if (discard_from_send_queue_(item.data().is_unordered, item.data().stream_id,
261 item.data().message_id)) {
262 // There were remaining chunks to be produced for this message. Since the
263 // receiver may have already received all chunks (up till now) for this
264 // message, we can't just FORWARD-TSN to the last fragment in this
265 // (abandoned) message and start sending a new message, as the receiver will
266 // then see a new message before the end of the previous one was seen (or
267 // skipped over). So create a new fragment, representing the end, that the
268 // received will never see as it is abandoned immediately and used as cum
269 // TSN in the sent FORWARD-TSN.
270 UnwrappedTSN tsn = next_tsn_;
271 next_tsn_.Increment();
272 Data message_end(item.data().stream_id, item.data().ssn,
273 item.data().message_id, item.data().fsn, item.data().ppid,
274 std::vector<uint8_t>(), Data::IsBeginning(false),
275 Data::IsEnd(true), item.data().is_unordered);
276 Item& added_item =
277 outstanding_data_
278 .emplace(std::piecewise_construct, std::forward_as_tuple(tsn),
279 std::forward_as_tuple(std::move(message_end), TimeMs(0),
280 MaxRetransmits::NoLimit(),
281 TimeMs::InfiniteFuture(),
282 LifecycleId::NotSet()))
283 .first->second;
284 // The added chunk shouldn't be included in `outstanding_bytes`, so set it
285 // as acked.
286 added_item.Ack();
287 RTC_DLOG(LS_VERBOSE) << "Adding unsent end placeholder for message at tsn="
288 << *tsn.Wrap();
289 }
290
291 for (auto& [tsn, other] : outstanding_data_) {
292 if (!other.is_abandoned() &&
293 other.data().stream_id == item.data().stream_id &&
294 other.data().is_unordered == item.data().is_unordered &&
295 other.data().message_id == item.data().message_id) {
296 RTC_DLOG(LS_VERBOSE) << "Marking chunk " << *tsn.Wrap()
297 << " as abandoned";
298 if (other.should_be_retransmitted()) {
299 to_be_fast_retransmitted_.erase(tsn);
300 to_be_retransmitted_.erase(tsn);
301 }
302 other.Abandon();
303 }
304 }
305 }
306
ExtractChunksThatCanFit(std::set<UnwrappedTSN> & chunks,size_t max_size)307 std::vector<std::pair<TSN, Data>> OutstandingData::ExtractChunksThatCanFit(
308 std::set<UnwrappedTSN>& chunks,
309 size_t max_size) {
310 std::vector<std::pair<TSN, Data>> result;
311
312 for (auto it = chunks.begin(); it != chunks.end();) {
313 UnwrappedTSN tsn = *it;
314 auto elem = outstanding_data_.find(tsn);
315 RTC_DCHECK(elem != outstanding_data_.end());
316 Item& item = elem->second;
317 RTC_DCHECK(item.should_be_retransmitted());
318 RTC_DCHECK(!item.is_outstanding());
319 RTC_DCHECK(!item.is_abandoned());
320 RTC_DCHECK(!item.is_acked());
321
322 size_t serialized_size = GetSerializedChunkSize(item.data());
323 if (serialized_size <= max_size) {
324 item.MarkAsRetransmitted();
325 result.emplace_back(tsn.Wrap(), item.data().Clone());
326 max_size -= serialized_size;
327 outstanding_bytes_ += serialized_size;
328 ++outstanding_items_;
329 it = chunks.erase(it);
330 } else {
331 ++it;
332 }
333 // No point in continuing if the packet is full.
334 if (max_size <= data_chunk_header_size_) {
335 break;
336 }
337 }
338 return result;
339 }
340
341 std::vector<std::pair<TSN, Data>>
GetChunksToBeFastRetransmitted(size_t max_size)342 OutstandingData::GetChunksToBeFastRetransmitted(size_t max_size) {
343 std::vector<std::pair<TSN, Data>> result =
344 ExtractChunksThatCanFit(to_be_fast_retransmitted_, max_size);
345
346 // https://datatracker.ietf.org/doc/html/rfc4960#section-7.2.4
347 // "Those TSNs marked for retransmission due to the Fast-Retransmit algorithm
348 // that did not fit in the sent datagram carrying K other TSNs are also marked
349 // as ineligible for a subsequent Fast Retransmit. However, as they are
350 // marked for retransmission they will be retransmitted later on as soon as
351 // cwnd allows."
352 if (!to_be_fast_retransmitted_.empty()) {
353 to_be_retransmitted_.insert(to_be_fast_retransmitted_.begin(),
354 to_be_fast_retransmitted_.end());
355 to_be_fast_retransmitted_.clear();
356 }
357
358 RTC_DCHECK(IsConsistent());
359 return result;
360 }
361
GetChunksToBeRetransmitted(size_t max_size)362 std::vector<std::pair<TSN, Data>> OutstandingData::GetChunksToBeRetransmitted(
363 size_t max_size) {
364 // Chunks scheduled for fast retransmission must be sent first.
365 RTC_DCHECK(to_be_fast_retransmitted_.empty());
366 return ExtractChunksThatCanFit(to_be_retransmitted_, max_size);
367 }
368
ExpireOutstandingChunks(TimeMs now)369 void OutstandingData::ExpireOutstandingChunks(TimeMs now) {
370 for (const auto& [tsn, item] : outstanding_data_) {
371 // Chunks that are nacked can be expired. Care should be taken not to expire
372 // unacked (in-flight) chunks as they might have been received, but the SACK
373 // is either delayed or in-flight and may be received later.
374 if (item.is_abandoned()) {
375 // Already abandoned.
376 } else if (item.is_nacked() && item.has_expired(now)) {
377 RTC_DLOG(LS_VERBOSE) << "Marking nacked chunk " << *tsn.Wrap()
378 << " and message " << *item.data().message_id
379 << " as expired";
380 AbandonAllFor(item);
381 } else {
382 // A non-expired chunk. No need to iterate any further.
383 break;
384 }
385 }
386 RTC_DCHECK(IsConsistent());
387 }
388
highest_outstanding_tsn() const389 UnwrappedTSN OutstandingData::highest_outstanding_tsn() const {
390 return outstanding_data_.empty() ? last_cumulative_tsn_ack_
391 : outstanding_data_.rbegin()->first;
392 }
393
Insert(const Data & data,TimeMs time_sent,MaxRetransmits max_retransmissions,TimeMs expires_at,LifecycleId lifecycle_id)394 absl::optional<UnwrappedTSN> OutstandingData::Insert(
395 const Data& data,
396 TimeMs time_sent,
397 MaxRetransmits max_retransmissions,
398 TimeMs expires_at,
399 LifecycleId lifecycle_id) {
400 UnwrappedTSN tsn = next_tsn_;
401 next_tsn_.Increment();
402
403 // All chunks are always padded to be even divisible by 4.
404 size_t chunk_size = GetSerializedChunkSize(data);
405 outstanding_bytes_ += chunk_size;
406 ++outstanding_items_;
407 auto it = outstanding_data_
408 .emplace(std::piecewise_construct, std::forward_as_tuple(tsn),
409 std::forward_as_tuple(data.Clone(), time_sent,
410 max_retransmissions, expires_at,
411 lifecycle_id))
412 .first;
413
414 if (it->second.has_expired(time_sent)) {
415 // No need to send it - it was expired when it was in the send
416 // queue.
417 RTC_DLOG(LS_VERBOSE) << "Marking freshly produced chunk "
418 << *it->first.Wrap() << " and message "
419 << *it->second.data().message_id << " as expired";
420 AbandonAllFor(it->second);
421 RTC_DCHECK(IsConsistent());
422 return absl::nullopt;
423 }
424
425 RTC_DCHECK(IsConsistent());
426 return tsn;
427 }
428
NackAll()429 void OutstandingData::NackAll() {
430 for (auto& [tsn, item] : outstanding_data_) {
431 if (!item.is_acked()) {
432 NackItem(tsn, item, /*retransmit_now=*/true,
433 /*do_fast_retransmit=*/false);
434 }
435 }
436 RTC_DCHECK(IsConsistent());
437 }
438
MeasureRTT(TimeMs now,UnwrappedTSN tsn) const439 absl::optional<DurationMs> OutstandingData::MeasureRTT(TimeMs now,
440 UnwrappedTSN tsn) const {
441 auto it = outstanding_data_.find(tsn);
442 if (it != outstanding_data_.end() && !it->second.has_been_retransmitted()) {
443 // https://tools.ietf.org/html/rfc4960#section-6.3.1
444 // "Karn's algorithm: RTT measurements MUST NOT be made using
445 // packets that were retransmitted (and thus for which it is ambiguous
446 // whether the reply was for the first instance of the chunk or for a
447 // later instance)"
448 return now - it->second.time_sent();
449 }
450 return absl::nullopt;
451 }
452
453 std::vector<std::pair<TSN, OutstandingData::State>>
GetChunkStatesForTesting() const454 OutstandingData::GetChunkStatesForTesting() const {
455 std::vector<std::pair<TSN, State>> states;
456 states.emplace_back(last_cumulative_tsn_ack_.Wrap(), State::kAcked);
457 for (const auto& [tsn, item] : outstanding_data_) {
458 State state;
459 if (item.is_abandoned()) {
460 state = State::kAbandoned;
461 } else if (item.should_be_retransmitted()) {
462 state = State::kToBeRetransmitted;
463 } else if (item.is_acked()) {
464 state = State::kAcked;
465 } else if (item.is_outstanding()) {
466 state = State::kInFlight;
467 } else {
468 state = State::kNacked;
469 }
470
471 states.emplace_back(tsn.Wrap(), state);
472 }
473 return states;
474 }
475
ShouldSendForwardTsn() const476 bool OutstandingData::ShouldSendForwardTsn() const {
477 if (!outstanding_data_.empty()) {
478 auto it = outstanding_data_.begin();
479 return it->first == last_cumulative_tsn_ack_.next_value() &&
480 it->second.is_abandoned();
481 }
482 return false;
483 }
484
CreateForwardTsn() const485 ForwardTsnChunk OutstandingData::CreateForwardTsn() const {
486 std::map<StreamID, SSN> skipped_per_ordered_stream;
487 UnwrappedTSN new_cumulative_ack = last_cumulative_tsn_ack_;
488
489 for (const auto& [tsn, item] : outstanding_data_) {
490 if ((tsn != new_cumulative_ack.next_value()) || !item.is_abandoned()) {
491 break;
492 }
493 new_cumulative_ack = tsn;
494 if (!item.data().is_unordered &&
495 item.data().ssn > skipped_per_ordered_stream[item.data().stream_id]) {
496 skipped_per_ordered_stream[item.data().stream_id] = item.data().ssn;
497 }
498 }
499
500 std::vector<ForwardTsnChunk::SkippedStream> skipped_streams;
501 skipped_streams.reserve(skipped_per_ordered_stream.size());
502 for (const auto& [stream_id, ssn] : skipped_per_ordered_stream) {
503 skipped_streams.emplace_back(stream_id, ssn);
504 }
505 return ForwardTsnChunk(new_cumulative_ack.Wrap(), std::move(skipped_streams));
506 }
507
CreateIForwardTsn() const508 IForwardTsnChunk OutstandingData::CreateIForwardTsn() const {
509 std::map<std::pair<IsUnordered, StreamID>, MID> skipped_per_stream;
510 UnwrappedTSN new_cumulative_ack = last_cumulative_tsn_ack_;
511
512 for (const auto& [tsn, item] : outstanding_data_) {
513 if ((tsn != new_cumulative_ack.next_value()) || !item.is_abandoned()) {
514 break;
515 }
516 new_cumulative_ack = tsn;
517 std::pair<IsUnordered, StreamID> stream_id =
518 std::make_pair(item.data().is_unordered, item.data().stream_id);
519
520 if (item.data().message_id > skipped_per_stream[stream_id]) {
521 skipped_per_stream[stream_id] = item.data().message_id;
522 }
523 }
524
525 std::vector<IForwardTsnChunk::SkippedStream> skipped_streams;
526 skipped_streams.reserve(skipped_per_stream.size());
527 for (const auto& [stream, message_id] : skipped_per_stream) {
528 skipped_streams.emplace_back(stream.first, stream.second, message_id);
529 }
530
531 return IForwardTsnChunk(new_cumulative_ack.Wrap(),
532 std::move(skipped_streams));
533 }
534
ResetSequenceNumbers(UnwrappedTSN next_tsn,UnwrappedTSN last_cumulative_tsn)535 void OutstandingData::ResetSequenceNumbers(UnwrappedTSN next_tsn,
536 UnwrappedTSN last_cumulative_tsn) {
537 RTC_DCHECK(outstanding_data_.empty());
538 RTC_DCHECK(next_tsn_ == last_cumulative_tsn_ack_.next_value());
539 RTC_DCHECK(next_tsn == last_cumulative_tsn.next_value());
540 next_tsn_ = next_tsn;
541 last_cumulative_tsn_ack_ = last_cumulative_tsn;
542 }
543 } // namespace dcsctp
544