s2n_quic_core/recovery/
loss.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, time::Timestamp};
5use core::time::Duration;
6
7//= https://www.rfc-editor.org/rfc/rfc9002#section-6.1.1
8//# The RECOMMENDED initial value for the packet reordering threshold
9//# (kPacketThreshold) is 3, based on best practices for TCP loss
10//# detection [RFC5681] [RFC6675].  In order to remain similar to TCP,
11//# implementations SHOULD NOT use a packet threshold less than 3; see
12//# [RFC5681].
13pub const K_PACKET_THRESHOLD: u64 = 3;
14
15#[derive(Debug, PartialEq, Eq)]
16pub enum Outcome {
17    /// The packet is not lost yet, but will be considered lost at the
18    /// given `lost_time` if not acknowledged by then
19    NotLostYet { lost_time: Timestamp },
20    /// The packet is lost
21    Lost,
22}
23
24/// Detect if the given packet number is lost based on how long ago
25/// it was sent and how far from the largest acked packet number it is.
26pub fn detect(
27    time_threshold: Duration,
28    time_sent: Timestamp,
29    packet_number_threshold: u64,
30    packet_number: PacketNumber,
31    largest_acked_packet_number: PacketNumber,
32    now: Timestamp,
33) -> Outcome {
34    // Tail packets are not considered lost until an acknowledgement is received for a packet sent
35    // after the tail packets. Therefore the `detect` method must only be called for packets sent
36    // prior to the largest acknowledged that could possibly be considered lost.
37    debug_assert!(
38        largest_acked_packet_number > packet_number,
39        "only packets sent before the largest acknowledged packet may be considered lost"
40    );
41
42    // Calculate at what time this particular packet is considered
43    // lost based on the `time_threshold`
44    let packet_lost_time = time_sent + time_threshold;
45
46    // If the `packet_lost_time` exceeds the current time, it's lost
47    let time_threshold_exceeded = packet_lost_time.has_elapsed(now);
48
49    let packet_number_threshold_exceeded = largest_acked_packet_number
50        .checked_distance(packet_number)
51        .expect("largest_acked_packet_number > packet_number")
52        >= packet_number_threshold;
53
54    //= https://www.rfc-editor.org/rfc/rfc9002#section-6.1
55    //# A packet is declared lost if it meets all of the following
56    //# conditions:
57    //#
58    //#     *  The packet is unacknowledged, in flight, and was sent prior to an
59    //#        acknowledged packet.
60    //#
61    //#     *  The packet was sent kPacketThreshold packets before an
62    //#        acknowledged packet (Section 6.1.1), or it was sent long enough in
63    //#        the past (Section 6.1.2).
64    if time_threshold_exceeded || packet_number_threshold_exceeded {
65        return Outcome::Lost;
66    }
67
68    Outcome::NotLostYet {
69        lost_time: packet_lost_time,
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::{packet::number::PacketNumberSpace, time::testing::now};
77
78    //= https://www.rfc-editor.org/rfc/rfc9002#section-6.1.1
79    //= type=test
80    //# The RECOMMENDED initial value for the packet reordering threshold
81    //# (kPacketThreshold) is 3, based on best practices for TCP loss
82    //# detection [RFC5681] [RFC6675].
83
84    //= https://www.rfc-editor.org/rfc/rfc9002#section-6.1.1
85    //= type=test
86    //# In order to remain similar to TCP,
87    //# implementations SHOULD NOT use a packet threshold less than 3; see
88    //# [RFC5681].
89    #[allow(clippy::assertions_on_constants)]
90    #[test]
91    fn packet_reorder_threshold_at_least_three() {
92        assert!(K_PACKET_THRESHOLD >= 3);
93    }
94
95    #[test]
96    fn time_threshold() {
97        let time_threshold = Duration::from_secs(5);
98        let packet_number = new_packet_number(1);
99        // largest acked is within the K_PACKET_THRESHOLD
100        let largest_acked_packet_number =
101            new_packet_number(packet_number.as_u64() + K_PACKET_THRESHOLD - 1);
102
103        let time_sent = now();
104        let current_time = time_sent + time_threshold;
105
106        let outcome = detect(
107            time_threshold,
108            time_sent,
109            K_PACKET_THRESHOLD,
110            packet_number,
111            largest_acked_packet_number,
112            current_time,
113        );
114
115        assert_eq!(Outcome::Lost, outcome);
116
117        let time_sent = now();
118        let current_time = time_sent + time_threshold - Duration::from_secs(1);
119
120        let outcome = detect(
121            time_threshold,
122            time_sent,
123            K_PACKET_THRESHOLD,
124            packet_number,
125            largest_acked_packet_number,
126            current_time,
127        );
128
129        assert_eq!(
130            Outcome::NotLostYet {
131                lost_time: current_time + Duration::from_secs(1)
132            },
133            outcome
134        );
135    }
136
137    #[test]
138    fn packet_number_threshold() {
139        let time_threshold = Duration::from_secs(5);
140        let time_sent = now();
141        // packet was sent less than the time threshold in the past
142        let current_time = time_sent + time_threshold - Duration::from_secs(1);
143
144        let packet_number = new_packet_number(1);
145        // largest acked is K_PACKET_THRESHOLD larger than the current packet
146        let largest_acked_packet_number =
147            new_packet_number(packet_number.as_u64() + K_PACKET_THRESHOLD);
148
149        let outcome = detect(
150            time_threshold,
151            time_sent,
152            K_PACKET_THRESHOLD,
153            packet_number,
154            largest_acked_packet_number,
155            current_time,
156        );
157
158        assert_eq!(Outcome::Lost, outcome);
159
160        // largest acked is within the K_PACKET_THRESHOLD
161        let largest_acked_packet_number =
162            new_packet_number(packet_number.as_u64() + K_PACKET_THRESHOLD - 1);
163
164        let outcome = detect(
165            time_threshold,
166            time_sent,
167            K_PACKET_THRESHOLD,
168            packet_number,
169            largest_acked_packet_number,
170            current_time,
171        );
172
173        assert_eq!(
174            Outcome::NotLostYet {
175                lost_time: current_time + Duration::from_secs(1)
176            },
177            outcome
178        );
179    }
180
181    fn new_packet_number(packet_number: u64) -> PacketNumber {
182        PacketNumberSpace::ApplicationData.new_packet_number(packet_number.try_into().unwrap())
183    }
184}