s2n_quic_core/recovery/pto.rs
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5 frame,
6 time::{timer, Timer, Timestamp},
7 transmission::{self, interest::Provider as _},
8};
9use core::{task::Poll, time::Duration};
10
11#[derive(Debug, Default)]
12pub struct Pto {
13 timer: Timer,
14 state: State,
15}
16
17impl Pto {
18 /// Called when a timeout has occurred. Returns `Ready` if the PTO timer had expired.
19 #[inline]
20 pub fn on_timeout(&mut self, packets_in_flight: bool, timestamp: Timestamp) -> Poll<()> {
21 ensure!(
22 self.timer.poll_expiration(timestamp).is_ready(),
23 Poll::Pending
24 );
25
26 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
27 //# When a PTO timer expires, a sender MUST send at least one ack-
28 //# eliciting packet in the packet number space as a probe.
29
30 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2.1
31 //# Since the server could be blocked until more datagrams are received
32 //# from the client, it is the client's responsibility to send packets to
33 //# unblock the server until it is certain that the server has finished
34 //# its address validation
35
36 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
37 //# An endpoint
38 //# MAY send up to two full-sized datagrams containing ack-eliciting
39 //# packets to avoid an expensive consecutive PTO expiration due to a
40 //# single lost datagram or to transmit data from multiple packet number
41 //# spaces.
42
43 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
44 //# Sending two packets on PTO
45 //# expiration increases resilience to packet drops, thus reducing the
46 //# probability of consecutive PTO events.
47 let transmission_count = if packets_in_flight { 2 } else { 1 };
48
49 self.state = State::RequiresTransmission(transmission_count);
50
51 Poll::Ready(())
52 }
53
54 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.1
55 //# A sender SHOULD restart its PTO timer every time an ack-eliciting
56 //# packet is sent or acknowledged, or when Initial or Handshake keys are
57 //# discarded (Section 4.9 of [QUIC-TLS]).
58 #[inline]
59 pub fn update(&mut self, base_timestamp: Timestamp, pto_period: Duration) {
60 self.timer.set(base_timestamp + pto_period);
61 }
62
63 /// Cancels the PTO timer
64 #[inline]
65 pub fn cancel(&mut self) {
66 self.timer.cancel();
67 }
68
69 /// Returns the number of pending transmissions
70 #[inline]
71 pub fn transmissions(&self) -> u8 {
72 self.state.transmissions()
73 }
74
75 #[inline]
76 pub fn on_transmit_once(&mut self) {
77 self.state.on_transmit();
78 }
79
80 #[inline]
81 pub fn force_transmit(&mut self) {
82 ensure!(matches!(self.state, State::Idle));
83 self.state = State::RequiresTransmission(1);
84 }
85}
86
87impl timer::Provider for Pto {
88 #[inline]
89 fn timers<Q: timer::Query>(&self, query: &mut Q) -> timer::Result {
90 self.timer.timers(query)?;
91 Ok(())
92 }
93}
94
95impl transmission::Provider for Pto {
96 #[inline]
97 fn on_transmit<W: transmission::Writer>(&mut self, context: &mut W) {
98 // If we aren't currently in loss recovery probing mode, don't
99 // send a probe. We could be in this state even if PtoState is
100 // RequiresTransmission if we are just transmitting a ConnectionClose
101 // frame.
102 ensure!(context.transmission_mode().is_loss_recovery_probing());
103
104 // Make sure we actually need to transmit
105 ensure!(self.has_transmission_interest());
106
107 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
108 //# All probe packets sent on a PTO MUST be ack-eliciting.
109 if !context.ack_elicitation().is_ack_eliciting() {
110 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
111 //# When there is no data to send, the sender SHOULD send
112 //# a PING or other ack-eliciting frame in a single packet, re-arming the
113 //# PTO timer.
114 let frame = frame::Ping;
115
116 //= https://www.rfc-editor.org/rfc/rfc9002#section-7.5
117 //# Probe packets MUST NOT be blocked by the congestion controller.
118 ensure!(context.write_frame_forced(&frame).is_some());
119 }
120
121 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2.1
122 //# When the PTO fires, the client MUST send a Handshake packet if it
123 //# has Handshake keys, otherwise it MUST send an Initial packet in a
124 //# UDP datagram with a payload of at least 1200 bytes.
125
126 //= https://www.rfc-editor.org/rfc/rfc9002#appendix-A.9
127 //# // Client sends an anti-deadlock packet: Initial is padded
128 //# // to earn more anti-amplification credit,
129 //# // a Handshake packet proves address ownership.
130
131 // The early transmission will automatically ensure all initial packets sent by the
132 // client are padded to 1200 bytes
133
134 self.on_transmit_once();
135 }
136}
137
138impl transmission::interest::Provider for Pto {
139 #[inline]
140 fn transmission_interest<Q: transmission::interest::Query>(
141 &self,
142 query: &mut Q,
143 ) -> transmission::interest::Result {
144 if self.transmissions() > 0 {
145 query.on_forced()?;
146 }
147
148 Ok(())
149 }
150}
151
152#[derive(Debug, PartialEq)]
153enum State {
154 Idle,
155 RequiresTransmission(u8),
156}
157
158impl Default for State {
159 #[inline]
160 fn default() -> Self {
161 Self::Idle
162 }
163}
164
165impl State {
166 #[inline]
167 fn transmissions(&self) -> u8 {
168 match self {
169 Self::Idle => 0,
170 Self::RequiresTransmission(count) => *count,
171 }
172 }
173
174 #[inline]
175 fn on_transmit(&mut self) {
176 match self {
177 Self::Idle | Self::RequiresTransmission(0) => {
178 debug_assert!(false, "transmitted pto in idle state");
179 }
180 Self::RequiresTransmission(1) => {
181 *self = Self::Idle;
182 }
183 Self::RequiresTransmission(remaining) => {
184 *remaining -= 1;
185 }
186 }
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::{
194 endpoint,
195 time::{Clock as _, NoopClock},
196 transmission::{writer::testing, Provider as _, Writer as _},
197 };
198
199 #[test]
200 fn on_transmit() {
201 let clock = NoopClock;
202
203 let mut frame_buffer = testing::OutgoingFrameBuffer::new();
204 let mut context = testing::Writer::new(
205 clock.get_time(),
206 &mut frame_buffer,
207 transmission::Constraint::CongestionLimited, // Recovery manager ignores constraints
208 transmission::Mode::LossRecoveryProbing,
209 endpoint::Type::Client,
210 );
211
212 let mut pto = Pto::default();
213
214 // Already idle
215 pto.on_transmit(&mut context);
216 assert_eq!(pto.state, State::Idle);
217
218 // No transmissions required
219 pto.state = State::RequiresTransmission(0);
220 pto.on_transmit(&mut context);
221 assert_eq!(pto.state, State::RequiresTransmission(0));
222
223 // One transmission required, not ack eliciting
224 pto.state = State::RequiresTransmission(1);
225 context.write_frame_forced(&frame::Padding { length: 1 });
226 assert!(!context.ack_elicitation().is_ack_eliciting());
227 pto.on_transmit(&mut context);
228
229 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
230 //= type=test
231 //# All probe packets sent on a PTO MUST be ack-eliciting.
232
233 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
234 //= type=test
235 //# When a PTO timer expires, a sender MUST send at least one ack-
236 //# eliciting packet in the packet number space as a probe.
237
238 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
239 //= type=test
240 //# When there is no data to send, the sender SHOULD send
241 //# a PING or other ack-eliciting frame in a single packet, re-arming the
242 //# PTO timer.
243 assert!(context.ack_elicitation().is_ack_eliciting());
244 assert_eq!(pto.state, State::Idle);
245
246 // One transmission required, ack eliciting
247 pto.state = State::RequiresTransmission(1);
248 context.write_frame_forced(&frame::Ping);
249 pto.on_transmit(&mut context);
250 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
251 //= type=test
252 //# All probe packets sent on a PTO MUST be ack-eliciting.
253
254 //= https://www.rfc-editor.org/rfc/rfc9002#section-6.2.4
255 //= type=test
256 //# When a PTO timer expires, a sender MUST send at least one ack-
257 //# eliciting packet in the packet number space as a probe.
258 assert!(context.ack_elicitation().is_ack_eliciting());
259 assert_eq!(pto.state, State::Idle);
260
261 // Two transmissions required
262 pto.state = State::RequiresTransmission(2);
263 pto.on_transmit(&mut context);
264 assert_eq!(pto.state, State::RequiresTransmission(1));
265 }
266
267 #[test]
268 fn on_transmit_normal_transmission_mode() {
269 let clock = NoopClock;
270
271 let mut frame_buffer = testing::OutgoingFrameBuffer::new();
272 let mut context = testing::Writer::new(
273 clock.get_time(),
274 &mut frame_buffer,
275 transmission::Constraint::CongestionLimited, // Recovery manager ignores constraints
276 transmission::Mode::Normal,
277 endpoint::Type::Client,
278 );
279
280 let mut pto = Pto {
281 state: State::RequiresTransmission(2),
282 ..Default::default()
283 };
284
285 pto.on_transmit(&mut context);
286 assert_eq!(0, frame_buffer.frames.len());
287 assert_eq!(pto.state, State::RequiresTransmission(2));
288 }
289
290 #[test]
291 fn transmission_interest() {
292 let mut pto = Pto::default();
293
294 assert!(!pto.has_transmission_interest());
295
296 pto.state = State::RequiresTransmission(0);
297 assert!(!pto.has_transmission_interest());
298
299 pto.state = State::RequiresTransmission(1);
300 assert!(pto.has_transmission_interest());
301
302 pto.state = State::RequiresTransmission(2);
303 assert!(pto.has_transmission_interest());
304 }
305}