s2n_quic_core/recovery/
persistent_congestion.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{packet::number::PacketNumber, path, recovery::SentPacketInfo, time::Timestamp};
5use core::time::Duration;
6
7#[derive(Debug)]
8pub struct Calculator {
9    current_period: Option<Period>,
10    max_duration: Duration,
11    first_rtt_sample: Option<Timestamp>,
12    path_id: path::Id,
13}
14
15impl Calculator {
16    /// Create a new `Calculator` for the given `path_id`
17    #[inline]
18    pub fn new(first_rtt_sample: Option<Timestamp>, path_id: path::Id) -> Self {
19        Self {
20            current_period: None,
21            max_duration: Duration::ZERO,
22            first_rtt_sample,
23            path_id,
24        }
25    }
26
27    /// Gets the longest persistent congestion period calculated
28    #[inline]
29    pub fn persistent_congestion_duration(&self) -> Duration {
30        self.max_duration
31    }
32
33    /// Called for each packet detected as lost
34    #[inline]
35    pub fn on_lost_packet<PacketInfo>(
36        &mut self,
37        packet_number: PacketNumber,
38        packet_info: &SentPacketInfo<PacketInfo>,
39    ) {
40        //= https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2
41        //# The persistent congestion period SHOULD NOT start until there is at
42        //# least one RTT sample.  Before the first RTT sample, a sender arms its
43        //# PTO timer based on the initial RTT (Section 6.2.2), which could be
44        //# substantially larger than the actual RTT.  Requiring a prior RTT
45        //# sample prevents a sender from establishing persistent congestion with
46        //# potentially too few probes.
47        ensure!(self
48            .first_rtt_sample
49            .is_some_and(|ts| packet_info.time_sent >= ts));
50
51        // Check that this lost packet was sent on the same path
52        //
53        // Persistent congestion is only updated for the path on which we receive
54        // an ack. Managing state for multiple paths requires extra allocations
55        // but is only necessary when also attempting connection_migration; which
56        // should not be very common.
57        ensure!(packet_info.path_id == self.path_id);
58
59        //= https://www.rfc-editor.org/rfc/rfc9000#section-14.4
60        //# Loss of a QUIC packet that is carried in a PMTU probe is therefore not a
61        //# reliable indication of congestion and SHOULD NOT trigger a congestion
62        //# control reaction; see Item 7 in Section 3 of [DPLPMTUD].
63        ensure!(!packet_info.transmission_mode.is_mtu_probing());
64
65        if let Some(current_period) = &mut self.current_period {
66            // We are currently tracking a persistent congestion period
67
68            //= https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2
69            //# A sender establishes persistent congestion after the receipt of an
70            //# acknowledgment if two packets that are ack-eliciting are declared
71            //# lost, and:
72            //#
73            //#     *  across all packet number spaces, none of the packets sent between
74            //#        the send times of these two packets are acknowledged;
75
76            // Check if this lost packet is contiguous with the current period.
77            if current_period.is_contiguous(packet_number) {
78                // Extend the end of the current persistent congestion period
79                current_period.extend(packet_number, packet_info);
80
81                self.max_duration = self.max_duration.max(current_period.duration());
82            } else {
83                // The current persistent congestion period has ended
84                self.current_period = None
85            }
86        }
87
88        if self.current_period.is_none() && packet_info.ack_elicitation.is_ack_eliciting() {
89            // Start tracking a new persistent congestion period
90
91            //= https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2
92            //# These two packets MUST be ack-eliciting, since a receiver is required
93            //# to acknowledge only ack-eliciting packets within its maximum
94            //# acknowledgment delay; see Section 13.2 of [QUIC-TRANSPORT].
95            self.current_period = Some(Period::new(packet_info.time_sent, packet_number));
96        }
97    }
98}
99
100#[derive(Debug)]
101struct Period {
102    start: Timestamp,
103    end: Timestamp,
104    prev_packet: PacketNumber,
105}
106
107impl Period {
108    /// Creates a new `Period`
109    #[inline]
110    fn new(start: Timestamp, packet_number: PacketNumber) -> Self {
111        Self {
112            start,
113            end: start,
114            prev_packet: packet_number,
115        }
116    }
117
118    /// True if the given packet number is 1 more than the last packet in this period
119    #[inline]
120    fn is_contiguous(&self, packet_number: PacketNumber) -> bool {
121        packet_number.checked_distance(self.prev_packet) == Some(1)
122    }
123
124    /// Extends this persistent congestion period
125    #[inline]
126    fn extend<PacketInfo>(
127        &mut self,
128        packet_number: PacketNumber,
129        packet_info: &SentPacketInfo<PacketInfo>,
130    ) {
131        debug_assert!(self.is_contiguous(packet_number));
132        debug_assert!(packet_info.time_sent >= self.start);
133
134        if packet_info.ack_elicitation.is_ack_eliciting() {
135            //= https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2
136            //# These two packets MUST be ack-eliciting, since a receiver is required
137            //# to acknowledge only ack-eliciting packets within its maximum
138            //# acknowledgment delay; see Section 13.2 of [QUIC-TRANSPORT].
139            self.end = packet_info.time_sent;
140        }
141
142        self.prev_packet = packet_number;
143    }
144
145    /// Gets the duration of this persistent congestion period
146    #[inline]
147    fn duration(&self) -> Duration {
148        self.end - self.start
149    }
150}