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}