1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::{packet::number::PacketNumber, time::Timestamp};
use core::time::Duration;

//= https://www.rfc-editor.org/rfc/rfc9002#section-6.1.1
//# The RECOMMENDED initial value for the packet reordering threshold
//# (kPacketThreshold) is 3, based on best practices for TCP loss
//# detection [RFC5681] [RFC6675].  In order to remain similar to TCP,
//# implementations SHOULD NOT use a packet threshold less than 3; see
//# [RFC5681].
pub const K_PACKET_THRESHOLD: u64 = 3;

#[derive(Debug, PartialEq, Eq)]
pub enum Outcome {
    /// The packet is not lost yet, but will be considered lost at the
    /// given `lost_time` if not acknowledged by then
    NotLostYet { lost_time: Timestamp },
    /// The packet is lost
    Lost,
}

/// Detect if the given packet number is lost based on how long ago
/// it was sent and how far from the largest acked packet number it is.
pub fn detect(
    time_threshold: Duration,
    time_sent: Timestamp,
    packet_number_threshold: u64,
    packet_number: PacketNumber,
    largest_acked_packet_number: PacketNumber,
    now: Timestamp,
) -> Outcome {
    // Tail packets are not considered lost until an acknowledgement is received for a packet sent
    // after the tail packets. Therefore the `detect` method must only be called for packets sent
    // prior to the largest acknowledged that could possibly be considered lost.
    debug_assert!(
        largest_acked_packet_number > packet_number,
        "only packets sent before the largest acknowledged packet may be considered lost"
    );

    // Calculate at what time this particular packet is considered
    // lost based on the `time_threshold`
    let packet_lost_time = time_sent + time_threshold;

    // If the `packet_lost_time` exceeds the current time, it's lost
    let time_threshold_exceeded = packet_lost_time.has_elapsed(now);

    let packet_number_threshold_exceeded = largest_acked_packet_number
        .checked_distance(packet_number)
        .expect("largest_acked_packet_number > packet_number")
        >= packet_number_threshold;

    //= https://www.rfc-editor.org/rfc/rfc9002#section-6.1
    //# A packet is declared lost if it meets all of the following
    //# conditions:
    //#
    //#     *  The packet is unacknowledged, in flight, and was sent prior to an
    //#        acknowledged packet.
    //#
    //#     *  The packet was sent kPacketThreshold packets before an
    //#        acknowledged packet (Section 6.1.1), or it was sent long enough in
    //#        the past (Section 6.1.2).
    if time_threshold_exceeded || packet_number_threshold_exceeded {
        return Outcome::Lost;
    }

    Outcome::NotLostYet {
        lost_time: packet_lost_time,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{packet::number::PacketNumberSpace, time::testing::now};

    //= https://www.rfc-editor.org/rfc/rfc9002#section-6.1.1
    //= type=test
    //# The RECOMMENDED initial value for the packet reordering threshold
    //# (kPacketThreshold) is 3, based on best practices for TCP loss
    //# detection [RFC5681] [RFC6675].

    //= https://www.rfc-editor.org/rfc/rfc9002#section-6.1.1
    //= type=test
    //# In order to remain similar to TCP,
    //# implementations SHOULD NOT use a packet threshold less than 3; see
    //# [RFC5681].
    #[allow(clippy::assertions_on_constants)]
    #[test]
    fn packet_reorder_threshold_at_least_three() {
        assert!(K_PACKET_THRESHOLD >= 3);
    }

    #[test]
    fn time_threshold() {
        let time_threshold = Duration::from_secs(5);
        let packet_number = new_packet_number(1);
        // largest acked is within the K_PACKET_THRESHOLD
        let largest_acked_packet_number =
            new_packet_number(packet_number.as_u64() + K_PACKET_THRESHOLD - 1);

        let time_sent = now();
        let current_time = time_sent + time_threshold;

        let outcome = detect(
            time_threshold,
            time_sent,
            K_PACKET_THRESHOLD,
            packet_number,
            largest_acked_packet_number,
            current_time,
        );

        assert_eq!(Outcome::Lost, outcome);

        let time_sent = now();
        let current_time = time_sent + time_threshold - Duration::from_secs(1);

        let outcome = detect(
            time_threshold,
            time_sent,
            K_PACKET_THRESHOLD,
            packet_number,
            largest_acked_packet_number,
            current_time,
        );

        assert_eq!(
            Outcome::NotLostYet {
                lost_time: current_time + Duration::from_secs(1)
            },
            outcome
        );
    }

    #[test]
    fn packet_number_threshold() {
        let time_threshold = Duration::from_secs(5);
        let time_sent = now();
        // packet was sent less than the time threshold in the past
        let current_time = time_sent + time_threshold - Duration::from_secs(1);

        let packet_number = new_packet_number(1);
        // largest acked is K_PACKET_THRESHOLD larger than the current packet
        let largest_acked_packet_number =
            new_packet_number(packet_number.as_u64() + K_PACKET_THRESHOLD);

        let outcome = detect(
            time_threshold,
            time_sent,
            K_PACKET_THRESHOLD,
            packet_number,
            largest_acked_packet_number,
            current_time,
        );

        assert_eq!(Outcome::Lost, outcome);

        // largest acked is within the K_PACKET_THRESHOLD
        let largest_acked_packet_number =
            new_packet_number(packet_number.as_u64() + K_PACKET_THRESHOLD - 1);

        let outcome = detect(
            time_threshold,
            time_sent,
            K_PACKET_THRESHOLD,
            packet_number,
            largest_acked_packet_number,
            current_time,
        );

        assert_eq!(
            Outcome::NotLostYet {
                lost_time: current_time + Duration::from_secs(1)
            },
            outcome
        );
    }

    fn new_packet_number(packet_number: u64) -> PacketNumber {
        PacketNumberSpace::ApplicationData.new_packet_number(packet_number.try_into().unwrap())
    }
}