| /* |
| * 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 <map> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #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<LifecycleId> acked_lifecycle_ids; |
| // The set of lifecycle IDs that were acked, but had been abandoned. |
| std::vector<LifecycleId> abandoned_lifecycle_ids; |
| }; |
| |
| OutstandingData( |
| size_t data_chunk_header_size, |
| UnwrappedTSN next_tsn, |
| UnwrappedTSN last_cumulative_tsn_ack, |
| std::function<bool(StreamID, OutgoingMessageId)> 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<const SackChunk::GapAckBlock> 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<std::pair<TSN, Data>> GetChunksToBeFastRetransmitted( |
| size_t max_size); |
| |
| // Given `max_size` of space left in a packet, which chunks can be added to |
| // it? |
| std::vector<std::pair<TSN, Data>> 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<UnwrappedTSN> 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 `PlusInfinity()`. |
| webrtc::TimeDelta MeasureRTT(TimeMs now, UnwrappedTSN tsn) const; |
| |
| // Returns the internal state of all queued chunks. This is only used in |
| // unit-tests. |
| std::vector<std::pair<TSN, State>> 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<const SackChunk::GapAckBlock> 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<const SackChunk::GapAckBlock> 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<UnwrappedTSN, Item>::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<std::pair<TSN, Data>> ExtractChunksThatCanFit( |
| std::set<UnwrappedTSN>& 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<bool(StreamID, OutgoingMessageId)> discard_from_send_queue_; |
| |
| std::map<UnwrappedTSN, Item> 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<UnwrappedTSN> to_be_fast_retransmitted_; |
| // Data chunks that are to be retransmitted. |
| std::set<UnwrappedTSN> 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<UnwrappedTSN> stream_reset_breakpoint_tsns_; |
| }; |
| } // namespace dcsctp |
| #endif // NET_DCSCTP_TX_OUTSTANDING_DATA_H_ |