/* * 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_TX_OUTSTANDING_DATA_H_ #define NET_DCSCTP_TX_OUTSTANDING_DATA_H_ #include #include #include #include #include "absl/types/optional.h" #include "net/dcsctp/common/internal_types.h" #include "net/dcsctp/common/sequence_numbers.h" #include "net/dcsctp/packet/chunk/forward_tsn_chunk.h" #include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h" #include "net/dcsctp/packet/chunk/sack_chunk.h" #include "net/dcsctp/packet/data.h" #include "net/dcsctp/public/types.h" #include "rtc_base/containers/flat_set.h" namespace dcsctp { // This class keeps track of outstanding data chunks (sent, not yet acked) and // handles acking, nacking, rescheduling and abandoning. class OutstandingData { public: // State for DATA chunks (message fragments) in the queue - used in tests. enum class State { // The chunk has been sent but not received yet (from the sender's point of // view, as no SACK has been received yet that reference this chunk). kInFlight, // A SACK has been received which explicitly marked this chunk as missing - // it's now NACKED and may be retransmitted if NACKED enough times. kNacked, // A chunk that will be retransmitted when possible. kToBeRetransmitted, // A SACK has been received which explicitly marked this chunk as received. kAcked, // A chunk whose message has expired or has been retransmitted too many // times (RFC3758). It will not be retransmitted anymore. kAbandoned, }; // Contains variables scoped to a processing of an incoming SACK. struct AckInfo { explicit AckInfo(UnwrappedTSN cumulative_tsn_ack) : highest_tsn_acked(cumulative_tsn_ack) {} // Bytes acked by increasing cumulative_tsn_ack and gap_ack_blocks. size_t bytes_acked = 0; // Indicates if this SACK indicates that packet loss has occurred. Just // because a packet is missing in the SACK doesn't necessarily mean that // there is packet loss as that packet might be in-flight and received // out-of-order. But when it has been reported missing consecutive times, it // will eventually be considered "lost" and this will be set. bool has_packet_loss = false; // Highest TSN Newly Acknowledged, an SCTP variable. UnwrappedTSN highest_tsn_acked; // The set of lifecycle IDs that were acked using cumulative_tsn_ack. std::vector acked_lifecycle_ids; // The set of lifecycle IDs that were acked, but had been abandoned. std::vector abandoned_lifecycle_ids; }; OutstandingData( size_t data_chunk_header_size, UnwrappedTSN next_tsn, UnwrappedTSN last_cumulative_tsn_ack, std::function discard_from_send_queue) : data_chunk_header_size_(data_chunk_header_size), next_tsn_(next_tsn), last_cumulative_tsn_ack_(last_cumulative_tsn_ack), discard_from_send_queue_(std::move(discard_from_send_queue)) {} AckInfo HandleSack( UnwrappedTSN cumulative_tsn_ack, rtc::ArrayView gap_ack_blocks, bool is_in_fast_recovery); // Returns as many of the chunks that are eligible for fast retransmissions // and that would fit in a single packet of `max_size`. The eligible chunks // that didn't fit will be marked for (normal) retransmission and will not be // returned if this method is called again. std::vector> GetChunksToBeFastRetransmitted( size_t max_size); // Given `max_size` of space left in a packet, which chunks can be added to // it? std::vector> GetChunksToBeRetransmitted(size_t max_size); size_t outstanding_bytes() const { return outstanding_bytes_; } // Returns the number of DATA chunks that are in-flight. size_t outstanding_items() const { return outstanding_items_; } // Given the current time `now_ms`, expire and abandon outstanding (sent at // least once) chunks that have a limited lifetime. void ExpireOutstandingChunks(TimeMs now); bool empty() const { return outstanding_data_.empty(); } bool has_data_to_be_fast_retransmitted() const { return !to_be_fast_retransmitted_.empty(); } bool has_data_to_be_retransmitted() const { return !to_be_retransmitted_.empty() || !to_be_fast_retransmitted_.empty(); } UnwrappedTSN last_cumulative_tsn_ack() const { return last_cumulative_tsn_ack_; } UnwrappedTSN next_tsn() const { return next_tsn_; } UnwrappedTSN highest_outstanding_tsn() const; // Schedules `data` to be sent, with the provided partial reliability // parameters. Returns the TSN if the item was actually added and scheduled to // be sent, and absl::nullopt if it shouldn't be sent. absl::optional Insert( OutgoingMessageId message_id, const Data& data, TimeMs time_sent, MaxRetransmits max_retransmissions = MaxRetransmits::NoLimit(), TimeMs expires_at = TimeMs::InfiniteFuture(), LifecycleId lifecycle_id = LifecycleId::NotSet()); // Nacks all outstanding data. void NackAll(); // Creates a FORWARD-TSN chunk. ForwardTsnChunk CreateForwardTsn() const; // Creates an I-FORWARD-TSN chunk. IForwardTsnChunk CreateIForwardTsn() const; // Given the current time and a TSN, it returns the measured RTT between when // the chunk was sent and now. It takes into acccount Karn's algorithm, so if // the chunk has ever been retransmitted, it will return absl::nullopt. absl::optional MeasureRTT(TimeMs now, UnwrappedTSN tsn) const; // Returns the internal state of all queued chunks. This is only used in // unit-tests. std::vector> GetChunkStatesForTesting() const; // Returns true if the next chunk that is not acked by the peer has been // abandoned, which means that a FORWARD-TSN should be sent. bool ShouldSendForwardTsn() const; // Sets the next TSN to be used. This is used in handover. void ResetSequenceNumbers(UnwrappedTSN next_tsn, UnwrappedTSN last_cumulative_tsn); // Called when an outgoing stream reset is sent, marking the last assigned TSN // as a breakpoint that a FORWARD-TSN shouldn't cross. void BeginResetStreams(); private: // A fragmented message's DATA chunk while in the retransmission queue, and // its associated metadata. class Item { public: enum class NackAction { kNothing, kRetransmit, kAbandon, }; Item(OutgoingMessageId message_id, Data data, TimeMs time_sent, MaxRetransmits max_retransmissions, TimeMs expires_at, LifecycleId lifecycle_id) : message_id_(message_id), time_sent_(time_sent), max_retransmissions_(max_retransmissions), expires_at_(expires_at), lifecycle_id_(lifecycle_id), data_(std::move(data)) {} Item(const Item&) = delete; Item& operator=(const Item&) = delete; OutgoingMessageId message_id() const { return message_id_; } TimeMs time_sent() const { return time_sent_; } const Data& data() const { return data_; } // Acks an item. void Ack(); // Nacks an item. If it has been nacked enough times, or if `retransmit_now` // is set, it might be marked for retransmission. If the item has reached // its max retransmission value, it will instead be abandoned. The action // performed is indicated as return value. NackAction Nack(bool retransmit_now); // Prepares the item to be retransmitted. Sets it as outstanding and // clears all nack counters. void MarkAsRetransmitted(); // Marks this item as abandoned. void Abandon(); bool is_outstanding() const { return ack_state_ == AckState::kUnacked; } bool is_acked() const { return ack_state_ == AckState::kAcked; } bool is_nacked() const { return ack_state_ == AckState::kNacked; } bool is_abandoned() const { return lifecycle_ == Lifecycle::kAbandoned; } // Indicates if this chunk should be retransmitted. bool should_be_retransmitted() const { return lifecycle_ == Lifecycle::kToBeRetransmitted; } // Indicates if this chunk has ever been retransmitted. bool has_been_retransmitted() const { return num_retransmissions_ > 0; } // Given the current time, and the current state of this DATA chunk, it will // indicate if it has expired (SCTP Partial Reliability Extension). bool has_expired(TimeMs now) const; LifecycleId lifecycle_id() const { return lifecycle_id_; } private: enum class Lifecycle : uint8_t { // The chunk is alive (sent, received, etc) kActive, // The chunk is scheduled to be retransmitted, and will then transition to // become active. kToBeRetransmitted, // The chunk has been abandoned. This is a terminal state. kAbandoned }; enum class AckState : uint8_t { // The chunk is in-flight. kUnacked, // The chunk has been received and acknowledged. kAcked, // The chunk has been nacked and is possibly lost. kNacked }; // NOTE: This data structure has been optimized for size, by ordering fields // to avoid unnecessary padding. const OutgoingMessageId message_id_; // When the packet was sent, and placed in this queue. const TimeMs time_sent_; // If the message was sent with a maximum number of retransmissions, this is // set to that number. The value zero (0) means that it will never be // retransmitted. const MaxRetransmits max_retransmissions_; // Indicates the life cycle status of this chunk. Lifecycle lifecycle_ = Lifecycle::kActive; // Indicates the presence of this chunk, if it's in flight (Unacked), has // been received (Acked) or is possibly lost (Nacked). AckState ack_state_ = AckState::kUnacked; // The number of times the DATA chunk has been nacked (by having received a // SACK which doesn't include it). Will be cleared on retransmissions. uint8_t nack_count_ = 0; // The number of times the DATA chunk has been retransmitted. uint16_t num_retransmissions_ = 0; // At this exact millisecond, the item is considered expired. If the message // is not to be expired, this is set to the infinite future. const TimeMs expires_at_; // An optional lifecycle id, which may only be set for the last fragment. const LifecycleId lifecycle_id_; // The actual data to send/retransmit. const Data data_; }; // Returns how large a chunk will be, serialized, carrying the data size_t GetSerializedChunkSize(const Data& data) const; // Given a `cumulative_tsn_ack` from an incoming SACK, will remove those items // in the retransmission queue up until this value and will update `ack_info` // by setting `bytes_acked_by_cumulative_tsn_ack`. void RemoveAcked(UnwrappedTSN cumulative_tsn_ack, AckInfo& ack_info); // Will mark the chunks covered by the `gap_ack_blocks` from an incoming SACK // as "acked" and update `ack_info` by adding new TSNs to `added_tsns`. void AckGapBlocks(UnwrappedTSN cumulative_tsn_ack, rtc::ArrayView gap_ack_blocks, AckInfo& ack_info); // Mark chunks reported as "missing", as "nacked" or "to be retransmitted" // depending how many times this has happened. Only packets up until // `ack_info.highest_tsn_acked` (highest TSN newly acknowledged) are // nacked/retransmitted. The method will set `ack_info.has_packet_loss`. void NackBetweenAckBlocks( UnwrappedTSN cumulative_tsn_ack, rtc::ArrayView gap_ack_blocks, bool is_in_fast_recovery, OutstandingData::AckInfo& ack_info); // Process the acknowledgement of the chunk referenced by `iter` and updates // state in `ack_info` and the object's state. void AckChunk(AckInfo& ack_info, std::map::iterator iter); // Helper method to process an incoming nack of an item and perform the // correct operations given the action indicated when nacking an item (e.g. // retransmitting or abandoning). The return value indicate if an action was // performed, meaning that packet loss was detected and acted upon. If // `do_fast_retransmit` is set and if the item has been nacked sufficiently // many times so that it should be retransmitted, this will schedule it to be // "fast retransmitted". This is only done just before going into fast // recovery. bool NackItem(UnwrappedTSN tsn, Item& item, bool retransmit_now, bool do_fast_retransmit); // Given that a message fragment, `item` has been abandoned, abandon all other // fragments that share the same message - both never-before-sent fragments // that are still in the SendQueue and outstanding chunks. void AbandonAllFor(const OutstandingData::Item& item); std::vector> ExtractChunksThatCanFit( std::set& chunks, size_t max_size); bool IsConsistent() const; // The size of the data chunk (DATA/I-DATA) header that is used. const size_t data_chunk_header_size_; // Next TSN to used. UnwrappedTSN next_tsn_; // The last cumulative TSN ack number. UnwrappedTSN last_cumulative_tsn_ack_; // Callback when to discard items from the send queue. std::function discard_from_send_queue_; std::map outstanding_data_; // The number of bytes that are in-flight (sent but not yet acked or nacked). size_t outstanding_bytes_ = 0; // The number of DATA chunks that are in-flight (sent but not yet acked or // nacked). size_t outstanding_items_ = 0; // Data chunks that are eligible for fast retransmission. std::set to_be_fast_retransmitted_; // Data chunks that are to be retransmitted. std::set to_be_retransmitted_; // Wben a stream reset has begun, the "next TSN to assign" is added to this // set, and removed when the cum-ack TSN reaches it. This is used to limit a // FORWARD-TSN to reset streams past a "stream reset last assigned TSN". webrtc::flat_set stream_reset_breakpoint_tsns_; }; } // namespace dcsctp #endif // NET_DCSCTP_TX_OUTSTANDING_DATA_H_