| /* | |
| * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "net/dcsctp/rx/data_tracker.h" | |
| #include <algorithm> | |
| #include <cstdint> | |
| #include <iterator> | |
| #include <set> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/algorithm/container.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/optional.h" | |
| #include "net/dcsctp/common/sequence_numbers.h" | |
| #include "net/dcsctp/packet/chunk/sack_chunk.h" | |
| #include "net/dcsctp/timer/timer.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/strings/string_builder.h" | |
| namespace dcsctp { | |
| constexpr size_t DataTracker::kMaxDuplicateTsnReported; | |
| constexpr size_t DataTracker::kMaxGapAckBlocksReported; | |
| bool DataTracker::AdditionalTsnBlocks::Add(UnwrappedTSN tsn) { | |
| // Find any block to expand. It will look for any block that includes (also | |
| // when expanded) the provided `tsn`. It will return the block that is greater | |
| // than, or equal to `tsn`. | |
| auto it = absl::c_lower_bound( | |
| blocks_, tsn, [&](const TsnRange& elem, const UnwrappedTSN& t) { | |
| return elem.last.next_value() < t; | |
| }); | |
| if (it == blocks_.end()) { | |
| // No matching block found. There is no greater than, or equal block - which | |
| // means that this TSN is greater than any block. It can then be inserted at | |
| // the end. | |
| blocks_.emplace_back(tsn, tsn); | |
| return true; | |
| } | |
| if (tsn >= it->first && tsn <= it->last) { | |
| // It's already in this block. | |
| return false; | |
| } | |
| if (it->last.next_value() == tsn) { | |
| // This block can be expanded to the right, or merged with the next. | |
| auto next_it = it + 1; | |
| if (next_it != blocks_.end() && tsn.next_value() == next_it->first) { | |
| // Expanding it would make it adjacent to next block - merge those. | |
| it->last = next_it->last; | |
| blocks_.erase(next_it); | |
| return true; | |
| } | |
| // Expand to the right | |
| it->last = tsn; | |
| return true; | |
| } | |
| if (it->first == tsn.next_value()) { | |
| // This block can be expanded to the left. Merging to the left would've been | |
| // covered by the above "merge to the right". Both blocks (expand a | |
| // right-most block to the left and expand a left-most block to the right) | |
| // would match, but the left-most would be returned by std::lower_bound. | |
| RTC_DCHECK(it == blocks_.begin() || (it - 1)->last.next_value() != tsn); | |
| // Expand to the left. | |
| it->first = tsn; | |
| return true; | |
| } | |
| // Need to create a new block in the middle. | |
| blocks_.emplace(it, tsn, tsn); | |
| return true; | |
| } | |
| void DataTracker::AdditionalTsnBlocks::EraseTo(UnwrappedTSN tsn) { | |
| // Find the block that is greater than or equals `tsn`. | |
| auto it = absl::c_lower_bound( | |
| blocks_, tsn, [&](const TsnRange& elem, const UnwrappedTSN& t) { | |
| return elem.last < t; | |
| }); | |
| // The block that is found is greater or equal (or possibly ::end, when no | |
| // block is greater or equal). All blocks before this block can be safely | |
| // removed. the TSN might be within this block, so possibly truncate it. | |
| bool tsn_is_within_block = it != blocks_.end() && tsn >= it->first; | |
| blocks_.erase(blocks_.begin(), it); | |
| if (tsn_is_within_block) { | |
| blocks_.front().first = tsn.next_value(); | |
| } | |
| } | |
| void DataTracker::AdditionalTsnBlocks::PopFront() { | |
| RTC_DCHECK(!blocks_.empty()); | |
| blocks_.erase(blocks_.begin()); | |
| } | |
| bool DataTracker::IsTSNValid(TSN tsn) const { | |
| UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.PeekUnwrap(tsn); | |
| // Note that this method doesn't return `false` for old DATA chunks, as those | |
| // are actually valid, and receiving those may affect the generated SACK | |
| // response (by setting "duplicate TSNs"). | |
| uint32_t difference = | |
| UnwrappedTSN::Difference(unwrapped_tsn, last_cumulative_acked_tsn_); | |
| if (difference > kMaxAcceptedOutstandingFragments) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool DataTracker::Observe(TSN tsn, | |
| AnyDataChunk::ImmediateAckFlag immediate_ack) { | |
| bool is_duplicate = false; | |
| UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(tsn); | |
| // IsTSNValid must be called prior to calling this method. | |
| RTC_DCHECK( | |
| UnwrappedTSN::Difference(unwrapped_tsn, last_cumulative_acked_tsn_) <= | |
| kMaxAcceptedOutstandingFragments); | |
| // Old chunk already seen before? | |
| if (unwrapped_tsn <= last_cumulative_acked_tsn_) { | |
| if (duplicate_tsns_.size() < kMaxDuplicateTsnReported) { | |
| duplicate_tsns_.insert(unwrapped_tsn.Wrap()); | |
| } | |
| // https://datatracker.ietf.org/doc/html/rfc4960#section-6.2 | |
| // "When a packet arrives with duplicate DATA chunk(s) and with no new DATA | |
| // chunk(s), the endpoint MUST immediately send a SACK with no delay. If a | |
| // packet arrives with duplicate DATA chunk(s) bundled with new DATA chunks, | |
| // the endpoint MAY immediately send a SACK." | |
| UpdateAckState(AckState::kImmediate, "duplicate data"); | |
| is_duplicate = true; | |
| } else { | |
| if (unwrapped_tsn == last_cumulative_acked_tsn_.next_value()) { | |
| last_cumulative_acked_tsn_ = unwrapped_tsn; | |
| // The cumulative acked tsn may be moved even further, if a gap was | |
| // filled. | |
| if (!additional_tsn_blocks_.empty() && | |
| additional_tsn_blocks_.front().first == | |
| last_cumulative_acked_tsn_.next_value()) { | |
| last_cumulative_acked_tsn_ = additional_tsn_blocks_.front().last; | |
| additional_tsn_blocks_.PopFront(); | |
| } | |
| } else { | |
| bool inserted = additional_tsn_blocks_.Add(unwrapped_tsn); | |
| if (!inserted) { | |
| // Already seen before. | |
| if (duplicate_tsns_.size() < kMaxDuplicateTsnReported) { | |
| duplicate_tsns_.insert(unwrapped_tsn.Wrap()); | |
| } | |
| // https://datatracker.ietf.org/doc/html/rfc4960#section-6.2 | |
| // "When a packet arrives with duplicate DATA chunk(s) and with no new | |
| // DATA chunk(s), the endpoint MUST immediately send a SACK with no | |
| // delay. If a packet arrives with duplicate DATA chunk(s) bundled with | |
| // new DATA chunks, the endpoint MAY immediately send a SACK." | |
| // No need to do this. SACKs are sent immediately on packet loss below. | |
| is_duplicate = true; | |
| } | |
| } | |
| } | |
| // https://tools.ietf.org/html/rfc4960#section-6.7 | |
| // "Upon the reception of a new DATA chunk, an endpoint shall examine the | |
| // continuity of the TSNs received. If the endpoint detects a gap in | |
| // the received DATA chunk sequence, it SHOULD send a SACK with Gap Ack | |
| // Blocks immediately. The data receiver continues sending a SACK after | |
| // receipt of each SCTP packet that doesn't fill the gap." | |
| if (!additional_tsn_blocks_.empty()) { | |
| UpdateAckState(AckState::kImmediate, "packet loss"); | |
| } | |
| // https://tools.ietf.org/html/rfc7053#section-5.2 | |
| // "Upon receipt of an SCTP packet containing a DATA chunk with the I | |
| // bit set, the receiver SHOULD NOT delay the sending of the corresponding | |
| // SACK chunk, i.e., the receiver SHOULD immediately respond with the | |
| // corresponding SACK chunk." | |
| if (*immediate_ack) { | |
| UpdateAckState(AckState::kImmediate, "immediate-ack bit set"); | |
| } | |
| if (!seen_packet_) { | |
| // https://tools.ietf.org/html/rfc4960#section-5.1 | |
| // "After the reception of the first DATA chunk in an association the | |
| // endpoint MUST immediately respond with a SACK to acknowledge the DATA | |
| // chunk." | |
| seen_packet_ = true; | |
| UpdateAckState(AckState::kImmediate, "first DATA chunk"); | |
| } | |
| // https://tools.ietf.org/html/rfc4960#section-6.2 | |
| // "Specifically, an acknowledgement SHOULD be generated for at least | |
| // every second packet (not every second DATA chunk) received, and SHOULD be | |
| // generated within 200 ms of the arrival of any unacknowledged DATA chunk." | |
| if (ack_state_ == AckState::kIdle) { | |
| UpdateAckState(AckState::kBecomingDelayed, "received DATA when idle"); | |
| } else if (ack_state_ == AckState::kDelayed) { | |
| UpdateAckState(AckState::kImmediate, "received DATA when already delayed"); | |
| } | |
| return !is_duplicate; | |
| } | |
| void DataTracker::HandleForwardTsn(TSN new_cumulative_ack) { | |
| // ForwardTSN is sent to make the receiver (this socket) "forget" about partly | |
| // received (or not received at all) data, up until `new_cumulative_ack`. | |
| UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(new_cumulative_ack); | |
| UnwrappedTSN prev_last_cum_ack_tsn = last_cumulative_acked_tsn_; | |
| // Old chunk already seen before? | |
| if (unwrapped_tsn <= last_cumulative_acked_tsn_) { | |
| // https://tools.ietf.org/html/rfc3758#section-3.6 | |
| // "Note, if the "New Cumulative TSN" value carried in the arrived | |
| // FORWARD TSN chunk is found to be behind or at the current cumulative TSN | |
| // point, the data receiver MUST treat this FORWARD TSN as out-of-date and | |
| // MUST NOT update its Cumulative TSN. The receiver SHOULD send a SACK to | |
| // its peer (the sender of the FORWARD TSN) since such a duplicate may | |
| // indicate the previous SACK was lost in the network." | |
| UpdateAckState(AckState::kImmediate, | |
| "FORWARD_TSN new_cumulative_tsn was behind"); | |
| return; | |
| } | |
| // https://tools.ietf.org/html/rfc3758#section-3.6 | |
| // "When a FORWARD TSN chunk arrives, the data receiver MUST first update | |
| // its cumulative TSN point to the value carried in the FORWARD TSN chunk, and | |
| // then MUST further advance its cumulative TSN point locally if possible, as | |
| // shown by the following example..." | |
| // The `new_cumulative_ack` will become the current | |
| // `last_cumulative_acked_tsn_`, and if there have been prior "gaps" that are | |
| // now overlapping with the new value, remove them. | |
| last_cumulative_acked_tsn_ = unwrapped_tsn; | |
| additional_tsn_blocks_.EraseTo(unwrapped_tsn); | |
| // See if the `last_cumulative_acked_tsn_` can be moved even further: | |
| if (!additional_tsn_blocks_.empty() && | |
| additional_tsn_blocks_.front().first == | |
| last_cumulative_acked_tsn_.next_value()) { | |
| last_cumulative_acked_tsn_ = additional_tsn_blocks_.front().last; | |
| additional_tsn_blocks_.PopFront(); | |
| } | |
| RTC_DLOG(LS_VERBOSE) << log_prefix_ << "FORWARD_TSN, cum_ack_tsn=" | |
| << *prev_last_cum_ack_tsn.Wrap() << "->" | |
| << *new_cumulative_ack << "->" | |
| << *last_cumulative_acked_tsn_.Wrap(); | |
| // https://tools.ietf.org/html/rfc3758#section-3.6 | |
| // "Any time a FORWARD TSN chunk arrives, for the purposes of sending a | |
| // SACK, the receiver MUST follow the same rules as if a DATA chunk had been | |
| // received (i.e., follow the delayed sack rules specified in ..." | |
| if (ack_state_ == AckState::kIdle) { | |
| UpdateAckState(AckState::kBecomingDelayed, | |
| "received FORWARD_TSN when idle"); | |
| } else if (ack_state_ == AckState::kDelayed) { | |
| UpdateAckState(AckState::kImmediate, | |
| "received FORWARD_TSN when already delayed"); | |
| } | |
| } | |
| SackChunk DataTracker::CreateSelectiveAck(size_t a_rwnd) { | |
| // Note that in SCTP, the receiver side is allowed to discard received data | |
| // and signal that to the sender, but only chunks that have previously been | |
| // reported in the gap-ack-blocks. However, this implementation will never do | |
| // that. So this SACK produced is more like a NR-SACK as explained in | |
| // https://ieeexplore.ieee.org/document/4697037 and which there is an RFC | |
| // draft at https://tools.ietf.org/html/draft-tuexen-tsvwg-sctp-multipath-17. | |
| std::set<TSN> duplicate_tsns; | |
| duplicate_tsns_.swap(duplicate_tsns); | |
| return SackChunk(last_cumulative_acked_tsn_.Wrap(), a_rwnd, | |
| CreateGapAckBlocks(), std::move(duplicate_tsns)); | |
| } | |
| std::vector<SackChunk::GapAckBlock> DataTracker::CreateGapAckBlocks() const { | |
| const auto& blocks = additional_tsn_blocks_.blocks(); | |
| std::vector<SackChunk::GapAckBlock> gap_ack_blocks; | |
| gap_ack_blocks.reserve(std::min(blocks.size(), kMaxGapAckBlocksReported)); | |
| for (size_t i = 0; i < blocks.size() && i < kMaxGapAckBlocksReported; ++i) { | |
| auto start_diff = | |
| UnwrappedTSN::Difference(blocks[i].first, last_cumulative_acked_tsn_); | |
| auto end_diff = | |
| UnwrappedTSN::Difference(blocks[i].last, last_cumulative_acked_tsn_); | |
| gap_ack_blocks.emplace_back(static_cast<uint16_t>(start_diff), | |
| static_cast<uint16_t>(end_diff)); | |
| } | |
| return gap_ack_blocks; | |
| } | |
| bool DataTracker::ShouldSendAck(bool also_if_delayed) { | |
| if (ack_state_ == AckState::kImmediate || | |
| (also_if_delayed && (ack_state_ == AckState::kBecomingDelayed || | |
| ack_state_ == AckState::kDelayed))) { | |
| UpdateAckState(AckState::kIdle, "sending SACK"); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool DataTracker::will_increase_cum_ack_tsn(TSN tsn) const { | |
| UnwrappedTSN unwrapped = tsn_unwrapper_.PeekUnwrap(tsn); | |
| return unwrapped == last_cumulative_acked_tsn_.next_value(); | |
| } | |
| void DataTracker::ForceImmediateSack() { | |
| ack_state_ = AckState::kImmediate; | |
| } | |
| void DataTracker::HandleDelayedAckTimerExpiry() { | |
| UpdateAckState(AckState::kImmediate, "delayed ack timer expired"); | |
| } | |
| void DataTracker::ObservePacketEnd() { | |
| if (ack_state_ == AckState::kBecomingDelayed) { | |
| UpdateAckState(AckState::kDelayed, "packet end"); | |
| } | |
| } | |
| void DataTracker::UpdateAckState(AckState new_state, absl::string_view reason) { | |
| if (new_state != ack_state_) { | |
| RTC_DLOG(LS_VERBOSE) << log_prefix_ << "State changed from " | |
| << ToString(ack_state_) << " to " | |
| << ToString(new_state) << " due to " << reason; | |
| if (ack_state_ == AckState::kDelayed) { | |
| delayed_ack_timer_.Stop(); | |
| } else if (new_state == AckState::kDelayed) { | |
| delayed_ack_timer_.Start(); | |
| } | |
| ack_state_ = new_state; | |
| } | |
| } | |
| absl::string_view DataTracker::ToString(AckState ack_state) { | |
| switch (ack_state) { | |
| case AckState::kIdle: | |
| return "IDLE"; | |
| case AckState::kBecomingDelayed: | |
| return "BECOMING_DELAYED"; | |
| case AckState::kDelayed: | |
| return "DELAYED"; | |
| case AckState::kImmediate: | |
| return "IMMEDIATE"; | |
| } | |
| } | |
| HandoverReadinessStatus DataTracker::GetHandoverReadiness() const { | |
| HandoverReadinessStatus status; | |
| if (!additional_tsn_blocks_.empty()) { | |
| status.Add(HandoverUnreadinessReason::kDataTrackerTsnBlocksPending); | |
| } | |
| return status; | |
| } | |
| void DataTracker::AddHandoverState(DcSctpSocketHandoverState& state) { | |
| state.rx.last_cumulative_acked_tsn = last_cumulative_acked_tsn().value(); | |
| state.rx.seen_packet = seen_packet_; | |
| } | |
| void DataTracker::RestoreFromState(const DcSctpSocketHandoverState& state) { | |
| // Validate that the component is in pristine state. | |
| RTC_DCHECK(additional_tsn_blocks_.empty()); | |
| RTC_DCHECK(duplicate_tsns_.empty()); | |
| RTC_DCHECK(!seen_packet_); | |
| seen_packet_ = state.rx.seen_packet; | |
| last_cumulative_acked_tsn_ = | |
| tsn_unwrapper_.Unwrap(TSN(state.rx.last_cumulative_acked_tsn)); | |
| } | |
| } // namespace dcsctp |