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}