webrtc/net/dcsctp/rx/data_tracker.h
Victor Boivie b847a43488 dcsctp: Reset synchronously with incoming request
When a sender has requested a stream to be reset, and the last sender
assigned TSN hasn't been received yet, the receiver will enter deferred
reset mode, where it will store any data chunks received after that
given TSN, and replay those later, when the stream has been reset.

Before this CL, leaving deferred mode was done as soon as the sender's
last assigned TSN was received. That's actually not how the RFC
describes the process[1], but was done that way to properly handle some
sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs
again, and realizing that whenever RFC6525 mention "any data arriving",
this also applies to any FORWARD-TSN[2] - it's better to reset streams
synchronously with the incoming requests, and defer not just DATA past
the sender last assigned TSN, but also any FORWARD-TSN after that TSN.

This mostly simplifies the code and is mostly a refactoring, but most
importantly aligns it with how the resetting procedure is explained in
the RFC. It also fixes two bugs:

 * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later
   than the sender's last assigned TSN - see test case. The old
   implementation tried to handle that by exiting the deferred reset
   processing as soon as it reached the sender's last assigned TSN, but
   it didn't manage to do that in all cases.
 * It only defers DATA chunks for streams that are to be reset, not
   all DATA chunks with a TSN > sender's last assigned TSN. This was
   missed in the old implementation, but as it's now implemented
   strictly according to the RFC, this was now done.

[1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2
[2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and
    the combination of these is not covered in the RFCs.

Bug: webrtc:14600
Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40889}
2023-10-09 09:47:57 +00:00

195 lines
7.3 KiB
C++

/*
* 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.
*/
#ifndef NET_DCSCTP_RX_DATA_TRACKER_H_
#define NET_DCSCTP_RX_DATA_TRACKER_H_
#include <stddef.h>
#include <stdint.h>
#include <cstdint>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "net/dcsctp/common/sequence_numbers.h"
#include "net/dcsctp/packet/chunk/data_common.h"
#include "net/dcsctp/packet/chunk/sack_chunk.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/public/dcsctp_handover_state.h"
#include "net/dcsctp/timer/timer.h"
namespace dcsctp {
// Keeps track of received DATA chunks and handles all logic for _when_ to
// create SACKs and also _how_ to generate them.
//
// It only uses TSNs to track delivery and doesn't need to be aware of streams.
//
// SACKs are optimally sent every second packet on connections with no packet
// loss. When packet loss is detected, it's sent for every packet. When SACKs
// are not sent directly, a timer is used to send a SACK delayed (by RTO/2, or
// 200ms, whatever is smallest).
class DataTracker {
public:
// The maximum number of duplicate TSNs that will be reported in a SACK.
static constexpr size_t kMaxDuplicateTsnReported = 20;
// The maximum number of gap-ack-blocks that will be reported in a SACK.
static constexpr size_t kMaxGapAckBlocksReported = 20;
// The maximum number of accepted in-flight DATA chunks. This indicates the
// maximum difference from this buffer's last cumulative ack TSN, and any
// received data. Data received beyond this limit will be dropped, which will
// force the transmitter to send data that actually increases the last
// cumulative acked TSN.
static constexpr uint32_t kMaxAcceptedOutstandingFragments = 100000;
DataTracker(absl::string_view log_prefix,
Timer* delayed_ack_timer,
TSN peer_initial_tsn)
: log_prefix_(log_prefix),
seen_packet_(false),
delayed_ack_timer_(*delayed_ack_timer),
last_cumulative_acked_tsn_(
tsn_unwrapper_.Unwrap(TSN(*peer_initial_tsn - 1))) {}
// Indicates if the provided TSN is valid. If this return false, the data
// should be dropped and not added to any other buffers, which essentially
// means that there is intentional packet loss.
bool IsTSNValid(TSN tsn) const;
// Call for every incoming data chunk. Returns `true` if `tsn` was seen for
// the first time, and `false` if it has been seen before (a duplicate `tsn`).
bool Observe(TSN tsn,
AnyDataChunk::ImmediateAckFlag immediate_ack =
AnyDataChunk::ImmediateAckFlag(false));
// Called at the end of processing an SCTP packet.
void ObservePacketEnd();
// Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks. Indicates if the
// chunk had any effect.
bool HandleForwardTsn(TSN new_cumulative_ack);
// Indicates if a SACK should be sent. There may be other reasons to send a
// SACK, but if this function indicates so, it should be sent as soon as
// possible. Calling this function will make it clear a flag so that if it's
// called again, it will probably return false.
//
// If the delayed ack timer is running, this method will return false _unless_
// `also_if_delayed` is set to true. Then it will return true as well.
bool ShouldSendAck(bool also_if_delayed = false);
// Returns the last cumulative ack TSN - the last seen data chunk's TSN
// value before any packet loss was detected.
TSN last_cumulative_acked_tsn() const {
return TSN(last_cumulative_acked_tsn_.Wrap());
}
bool IsLaterThanCumulativeAckedTsn(TSN tsn) const {
return tsn_unwrapper_.PeekUnwrap(tsn) > last_cumulative_acked_tsn_;
}
// Returns true if the received `tsn` would increase the cumulative ack TSN.
bool will_increase_cum_ack_tsn(TSN tsn) const;
// Forces `ShouldSendSack` to return true.
void ForceImmediateSack();
// Note that this will clear `duplicates_`, so every SackChunk that is
// consumed must be sent.
SackChunk CreateSelectiveAck(size_t a_rwnd);
void HandleDelayedAckTimerExpiry();
HandoverReadinessStatus GetHandoverReadiness() const;
void AddHandoverState(DcSctpSocketHandoverState& state);
void RestoreFromState(const DcSctpSocketHandoverState& state);
private:
enum class AckState {
// No need to send an ACK.
kIdle,
// Has received data chunks (but not yet end of packet).
kBecomingDelayed,
// Has received data chunks and the end of a packet. Delayed ack timer is
// running and a SACK will be sent on expiry, or if DATA is sent, or after
// next packet with data.
kDelayed,
// Send a SACK immediately after handling this packet.
kImmediate,
};
// Represents ranges of TSNs that have been received that are not directly
// following the last cumulative acked TSN. This information is returned to
// the sender in the "gap ack blocks" in the SACK chunk. The blocks are always
// non-overlapping and non-adjacent.
class AdditionalTsnBlocks {
public:
// Represents an inclusive range of received TSNs, i.e. [first, last].
struct TsnRange {
TsnRange(UnwrappedTSN first, UnwrappedTSN last)
: first(first), last(last) {}
UnwrappedTSN first;
UnwrappedTSN last;
};
// Adds a TSN to the set. This will try to expand any existing block and
// might merge blocks to ensure that all blocks are non-adjacent. If a
// current block can't be expanded, a new block is created.
//
// The return value indicates if `tsn` was added. If false is returned, the
// `tsn` was already represented in one of the blocks.
bool Add(UnwrappedTSN tsn);
// Erases all TSNs up to, and including `tsn`. This will remove all blocks
// that are completely below `tsn` and may truncate a block where `tsn` is
// within that block. In that case, the frontmost block's start TSN will be
// the next following tsn after `tsn`.
void EraseTo(UnwrappedTSN tsn);
// Removes the first block. Must not be called on an empty set.
void PopFront();
const std::vector<TsnRange>& blocks() const { return blocks_; }
bool empty() const { return blocks_.empty(); }
const TsnRange& front() const { return blocks_.front(); }
private:
// A sorted vector of non-overlapping and non-adjacent blocks.
std::vector<TsnRange> blocks_;
};
std::vector<SackChunk::GapAckBlock> CreateGapAckBlocks() const;
void UpdateAckState(AckState new_state, absl::string_view reason);
static absl::string_view ToString(AckState ack_state);
const absl::string_view log_prefix_;
// If a packet has ever been seen.
bool seen_packet_;
Timer& delayed_ack_timer_;
AckState ack_state_ = AckState::kIdle;
UnwrappedTSN::Unwrapper tsn_unwrapper_;
// All TSNs up until (and including) this value have been seen.
UnwrappedTSN last_cumulative_acked_tsn_;
// Received TSNs that are not directly following `last_cumulative_acked_tsn_`.
AdditionalTsnBlocks additional_tsn_blocks_;
std::set<TSN> duplicate_tsns_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_RX_DATA_TRACKER_H_