s2n_quic_transport/path/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module contains the Path implementation
5
6use crate::{
7    connection,
8    contexts::WriteContext,
9    endpoint,
10    endpoint::Type,
11    recovery::{congestion_controller, CongestionController, RttEstimator},
12    transmission::{self, Mode},
13};
14use s2n_quic_core::{
15    counter::{Counter, Saturating},
16    event::{self, builder::DatagramDropReason, IntoEvent},
17    frame,
18    inet::DatagramInfo,
19    packet, random,
20    time::{timer, Timestamp},
21};
22
23mod challenge;
24mod manager;
25
26pub use challenge::Challenge;
27pub use manager::*;
28
29/// re-export core
30pub use s2n_quic_core::path::*;
31
32#[derive(Debug, Clone, Copy, PartialEq)]
33enum State {
34    /// Path has no transmission limitations
35    Validated,
36
37    /// Path has not been validated and is subject to amplification limits
38    AmplificationLimited {
39        tx_allowance: Counter<u32, Saturating>,
40    },
41}
42
43#[derive(Debug)]
44pub struct Path<Config: endpoint::Config> {
45    /// The peer's socket address
46    pub handle: Config::PathHandle,
47    /// The connection id of the peer
48    pub peer_connection_id: connection::PeerId,
49    /// The local connection id which the peer sends to
50    pub local_connection_id: connection::LocalId,
51    /// The path owns the roundtrip between peers
52    pub rtt_estimator: RttEstimator,
53    /// The congestion controller for the path
54    pub congestion_controller: <Config::CongestionControllerEndpoint as congestion_controller::Endpoint>::CongestionController,
55    /// Probe timeout backoff multiplier
56    pub pto_backoff: u32,
57    /// Tracks whether this path has passed Address or Path validation
58    state: State,
59    /// Controller for determining the maximum transmission unit of the path
60    pub mtu_controller: mtu::Controller,
61    /// Controller for determining the ECN capability of the path
62    pub ecn_controller: ecn::Controller,
63
64    /// True if the path has been validated by the peer
65    peer_validated: bool,
66
67    /// Challenge sent to the peer in a PATH_CHALLENGE
68    challenge: Challenge,
69
70    /// Received a Challenge and should echo back data in PATH_RESPONSE
71    response_data: Option<challenge::Data>,
72
73    /// True if the path is currently or at some point been an active path.
74    ///
75    /// A path becomes an active path if it receives a non-path-validation-probing
76    /// packet. `activated` is a one way state used to mark paths that have been the
77    /// active path at some point in the connection. This parameter is used to
78    /// determine if the path should become the last_known_active_validated_path.
79    activated: bool,
80
81    /// True if the path is currently active
82    is_active: bool,
83    anti_amplification_multiplier: u8,
84    /// PTO jitter percentage (0-50)
85    pto_jitter_percentage: u8,
86}
87
88impl<Config: endpoint::Config> Clone for Path<Config> {
89    fn clone(&self) -> Self {
90        Self {
91            handle: self.handle,
92            peer_connection_id: self.peer_connection_id,
93            local_connection_id: self.local_connection_id,
94            rtt_estimator: self.rtt_estimator,
95            congestion_controller: self.congestion_controller.clone(),
96            pto_backoff: self.pto_backoff,
97            state: self.state,
98            mtu_controller: self.mtu_controller.clone(),
99            ecn_controller: self.ecn_controller.clone(),
100            peer_validated: self.peer_validated,
101            challenge: self.challenge.clone(),
102            response_data: self.response_data,
103            activated: self.activated,
104            is_active: self.is_active,
105            anti_amplification_multiplier: self.anti_amplification_multiplier,
106            pto_jitter_percentage: self.pto_jitter_percentage,
107        }
108    }
109}
110
111/// A Path holds the local and peer socket addresses, connection ids, and state. It can be
112/// validated or pending validation.
113impl<Config: endpoint::Config> Path<Config> {
114    pub fn new(
115        handle: Config::PathHandle,
116        peer_connection_id: connection::PeerId,
117        local_connection_id: connection::LocalId,
118        rtt_estimator: RttEstimator,
119        congestion_controller: <Config::CongestionControllerEndpoint as congestion_controller::Endpoint>::CongestionController,
120        peer_validated: bool,
121        mtu_config: mtu::Config,
122        anti_amplification_multiplier: u8,
123        pto_jitter_percentage: u8,
124    ) -> Path<Config> {
125        let state = match Config::ENDPOINT_TYPE {
126            Type::Server => {
127                //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4
128                //# If the client IP address has changed, the
129                //# server MUST adhere to the anti-amplification limit; see Section 8.
130                // Start each path in State::AmplificationLimited until it has been validated.
131                State::AmplificationLimited {
132                    tx_allowance: Default::default(),
133                }
134            }
135            //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
136            //# Clients are only constrained by the congestion controller.
137            Type::Client => State::Validated,
138        };
139        let peer_socket_address = handle.remote_address();
140        Path {
141            handle,
142            peer_connection_id,
143            local_connection_id,
144            rtt_estimator,
145            congestion_controller,
146            pto_backoff: INITIAL_PTO_BACKOFF,
147            state,
148            mtu_controller: mtu::Controller::new(mtu_config, &peer_socket_address),
149            ecn_controller: ecn::Controller::default(),
150            peer_validated,
151            challenge: Challenge::disabled(),
152            response_data: None,
153            activated: false,
154            is_active: false,
155            anti_amplification_multiplier,
156            pto_jitter_percentage,
157        }
158    }
159
160    #[inline]
161    pub fn remote_address(&self) -> RemoteAddress {
162        self.handle.remote_address()
163    }
164
165    #[inline]
166    pub fn local_address(&self) -> LocalAddress {
167        self.handle.local_address()
168    }
169
170    #[inline]
171    pub fn set_challenge(&mut self, challenge: Challenge) {
172        self.challenge = challenge;
173    }
174
175    #[inline]
176    pub fn abandon_challenge<Pub: event::ConnectionPublisher>(
177        &mut self,
178        publisher: &mut Pub,
179        path_id: u64,
180    ) {
181        self.challenge
182            .abandon(publisher, path_event!(self, path_id));
183    }
184
185    #[inline]
186    pub fn is_active(&self) -> bool {
187        self.is_active
188    }
189
190    /// Called when bytes have been transmitted on this path
191    #[inline]
192    pub fn on_bytes_transmitted(&mut self, bytes: usize) {
193        if bytes == 0 {
194            return;
195        }
196
197        debug_assert_ne!(
198            self.clamp_datagram_size(bytes, transmission::Mode::Normal),
199            0,
200            "path should not transmit when amplification limited; tried to transmit {bytes}"
201        );
202
203        if let State::AmplificationLimited { tx_allowance, .. } = &mut self.state {
204            *tx_allowance -= bytes as u32
205        }
206    }
207
208    /// Called when bytes have been received on this path
209    /// Returns true if receiving these bytes unblocked the
210    /// path from being amplification limited
211    #[inline]
212    pub fn on_bytes_received(&mut self, bytes: usize) -> AmplificationOutcome {
213        let was_at_amplification_limit = self.at_amplification_limit();
214
215        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
216        //# For the purposes of
217        //# avoiding amplification prior to address validation, servers MUST
218        //# count all of the payload bytes received in datagrams that are
219        //# uniquely attributed to a single connection.
220        //
221        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
222        //# Prior to validating the client address, servers MUST NOT send more
223        //# than three times as many bytes as the number of bytes they have
224        //# received.
225        if let State::AmplificationLimited { tx_allowance } = &mut self.state {
226            *tx_allowance +=
227                bytes.saturating_mul(self.anti_amplification_multiplier as usize) as u32;
228        }
229
230        let unblocked = was_at_amplification_limit && !self.at_amplification_limit();
231
232        match (unblocked, self.is_active()) {
233            (true, true) => AmplificationOutcome::ActivePathUnblocked,
234            (true, false) => AmplificationOutcome::InactivePathUnblocked,
235            _ => AmplificationOutcome::Unchanged,
236        }
237    }
238
239    #[inline]
240    pub fn on_datagram_received(
241        &mut self,
242        path_handle: &Config::PathHandle,
243        datagram: &DatagramInfo,
244        valid_initial_received: bool,
245    ) -> Result<AmplificationOutcome, DatagramDropReason> {
246        let source_cid_changed = datagram
247            .source_connection_id
248            .is_some_and(|scid| scid != self.peer_connection_id && valid_initial_received);
249
250        if source_cid_changed {
251            //= https://www.rfc-editor.org/rfc/rfc9000#section-7.2
252            //# Once a client has received a valid Initial packet from the server, it MUST
253            //# discard any subsequent packet it receives on that connection with a
254            //# different Source Connection ID.
255
256            //= https://www.rfc-editor.org/rfc/rfc9000#section-7.2
257            //# Any further changes to the Destination Connection ID are only
258            //# permitted if the values are taken from NEW_CONNECTION_ID frames; if
259            //# subsequent Initial packets include a different Source Connection ID,
260            //# they MUST be discarded.
261
262            return Err(DatagramDropReason::InvalidSourceConnectionId);
263        }
264
265        // Update the address if it was resolved
266        //
267        // NOTE: We don't update the server address since this would cause the client to drop
268        // packets from the server.
269
270        //= https://www.rfc-editor.org/rfc/rfc9000#section-9
271        //# If a client receives packets from an unknown server address, the client MUST discard these packets.
272
273        //= https://www.rfc-editor.org/rfc/rfc9000#section-9
274        //# If the peer sent the disable_active_migration transport parameter, an endpoint also MUST NOT send
275        //# packets (including probing packets; see Section 9.1) from a different local address to the address
276        //# the peer used during the handshake, unless the endpoint has acted on a preferred_address transport
277        //# parameter from the peer.
278        if Config::ENDPOINT_TYPE.is_client() {
279            self.handle.maybe_update(path_handle);
280        }
281
282        let amplification_outcome = self.on_bytes_received(datagram.payload_len);
283
284        Ok(amplification_outcome)
285    }
286
287    #[inline]
288    pub fn on_timeout<Pub: event::ConnectionPublisher>(
289        &mut self,
290        timestamp: Timestamp,
291        path_id: Id,
292        random_generator: &mut dyn random::Generator,
293        publisher: &mut Pub,
294    ) {
295        self.challenge
296            .on_timeout(timestamp, publisher, path_event!(self, path_id));
297        self.mtu_controller.on_timeout(timestamp);
298        self.ecn_controller.on_timeout(
299            timestamp,
300            path_event!(self, path_id),
301            random_generator,
302            self.rtt_estimator.smoothed_rtt(),
303            publisher,
304        );
305    }
306
307    /// Returns true if this path is able to transmit packets at the given timestamp
308    #[inline]
309    pub fn can_transmit(&self, timestamp: Timestamp) -> bool {
310        !self.at_amplification_limit()
311            && self
312                .congestion_controller
313                .earliest_departure_time()
314                .is_none_or(|edt| edt.has_elapsed(timestamp))
315    }
316
317    /// Only PATH_CHALLENGE and PATH_RESPONSE frames should be transmitted here.
318    #[inline]
319    pub fn on_transmit<W: WriteContext>(&mut self, context: &mut W) {
320        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
321        //# A PATH_RESPONSE frame MUST be sent on the network path where the
322        //# PATH_CHALLENGE frame was received.
323        if let Some(response_data) = &mut self.response_data {
324            let frame = frame::PathResponse {
325                data: response_data,
326            };
327            if context.write_frame(&frame).is_some() {
328                //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
329                //# An endpoint MUST NOT send more than one PATH_RESPONSE frame in
330                //# response to one PATH_CHALLENGE frame; see Section 13.3.
331                self.response_data = None;
332            }
333        }
334
335        self.challenge.on_transmit(context)
336    }
337
338    /// Check if path validation was attempted and failed.
339    #[inline]
340    pub fn failed_validation(&self) -> bool {
341        // PATH_CHALLENGE is not used for validating the initial path and is disabled. Check if
342        // the challenge is disabled before executing the following block since there won't be
343        // a last_known_validated_path.
344        !self.challenge.is_disabled() && !self.is_validated() && !self.is_challenge_pending()
345    }
346
347    #[inline]
348    pub fn is_challenge_pending(&self) -> bool {
349        self.challenge.is_pending()
350    }
351
352    #[inline]
353    pub fn is_response_pending(&self) -> bool {
354        self.response_data.is_some()
355    }
356
357    #[inline]
358    pub fn on_path_challenge(&mut self, response: &challenge::Data) {
359        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
360        //# On receiving a PATH_CHALLENGE frame, an endpoint MUST respond by
361        //# echoing the data contained in the PATH_CHALLENGE frame in a
362        //# PATH_RESPONSE frame.
363        self.response_data = Some(*response);
364    }
365
366    /// Validates the path if the PATH_RESPONSE data matches the PATH_CHALLENGE data
367    /// and returns if the path was validated.
368    #[inline]
369    pub fn on_path_response(&mut self, response: &[u8]) -> bool {
370        if self.challenge.on_validated(response) {
371            self.on_validated();
372
373            return true;
374
375            //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3
376            //= type=TODO
377            //# After verifying a new client address, the server SHOULD send new
378            //# address validation tokens (Section 8) to the client.
379        }
380
381        false
382    }
383
384    /// Called when a handshake packet is received.
385    ///
386    /// Receiving a handshake packet acts as path validation for the initial path
387    #[inline]
388    pub fn on_handshake_packet(&mut self) {
389        self.on_validated();
390    }
391
392    /// Checks if the peer has started using a different destination Connection Id.
393    ///
394    /// The CleartextShort packet guarantees the packet has been validated
395    /// (authenticated and de-duped).
396    pub fn on_process_local_connection_id<Pub: event::ConnectionPublisher>(
397        &mut self,
398        path_id: Id,
399        packet: &packet::short::CleartextShort<'_>,
400        local_connection_id: &connection::LocalId,
401        publisher: &mut Pub,
402    ) {
403        debug_assert_eq!(
404            packet.destination_connection_id(),
405            local_connection_id.as_ref()
406        );
407
408        if &self.local_connection_id != local_connection_id {
409            publisher.on_connection_id_updated(event::builder::ConnectionIdUpdated {
410                path_id: path_id.into_event(),
411                cid_consumer: endpoint::Location::Remote,
412                previous: self.local_connection_id.into_event(),
413                current: local_connection_id.into_event(),
414            });
415            self.local_connection_id = *local_connection_id;
416        }
417    }
418
419    /// Called when the path is validated
420    #[inline]
421    fn on_validated(&mut self) {
422        self.state = State::Validated;
423
424        if self.is_peer_validated() {
425            self.on_fully_validated();
426        }
427    }
428
429    /// Returns whether this path has passed address validation
430    #[inline]
431    pub fn is_validated(&self) -> bool {
432        self.state == State::Validated
433    }
434
435    /// The path received a non-path-validation-probing packet so mark it as activated.
436    #[inline]
437    pub fn on_activated(&mut self) {
438        self.activated = true;
439    }
440
441    /// Returns if the path is currently or at some point been an active path.
442    #[inline]
443    pub fn is_activated(&self) -> bool {
444        self.activated
445    }
446
447    /// Marks the path as peer validated
448    #[inline]
449    pub fn on_peer_validated(&mut self) {
450        self.peer_validated = true;
451
452        if self.is_validated() {
453            self.on_fully_validated();
454        }
455    }
456
457    /// Returns whether this path has been validated by the peer
458    #[inline]
459    pub fn is_peer_validated(&self) -> bool {
460        self.peer_validated
461    }
462
463    /// Called when the path has been validated locally, and also by the peer
464    fn on_fully_validated(&mut self) {
465        // Enable the mtu controller to allow for PMTU discovery
466        self.mtu_controller.enable()
467    }
468
469    #[inline]
470    pub fn max_datagram_size(&self, transmission_mode: transmission::Mode) -> usize {
471        match transmission_mode {
472            // Use the minimum max datagram size for loss recovery probes to allow detection of
473            // packets lost when the previously confirmed path MTU is no longer supported.
474            //
475            // The priority during PathValidationOnly is to validate the path, so the
476            // minimum max datagram size is used to avoid packet loss due to MTU limits.
477            Mode::LossRecoveryProbing | Mode::PathValidationOnly => {
478                MINIMUM_MAX_DATAGRAM_SIZE as usize
479            }
480            // When MTU Probing, clamp to the max datagram size we are attempting to validate
481            Mode::MtuProbing => self.mtu_controller.probed_sized(),
482            // Otherwise use the confirmed max datagram size
483            Mode::Normal => self.mtu_controller.max_datagram_size(),
484        }
485    }
486
487    //= https://www.rfc-editor.org/rfc/rfc9000#section-14.1
488    //# The server MUST also limit the number of bytes it sends before
489    //# validating the address of the client; see Section 8.
490
491    //= https://www.rfc-editor.org/rfc/rfc9000#section-14.2
492    //# All QUIC
493    //# packets that are not sent in a PMTU probe SHOULD be sized to fit
494    //# within the maximum datagram size to avoid the datagram being
495    //# fragmented or dropped [RFC8085].
496
497    //= https://www.rfc-editor.org/rfc/rfc8899#section-3
498    //# A PL MUST NOT send a datagram (other than a probe
499    //# packet) with a size at the PL that is larger than the current
500    //# PLPMTU.
501
502    /// Clamps payload sizes to the current max datagram size for the path
503    ///
504    /// # Panics
505    ///
506    /// Panics if this is called when the path is amplification limited
507    #[inline]
508    pub fn clamp_datagram_size(
509        &self,
510        requested_size: usize,
511        transmission_mode: transmission::Mode,
512    ) -> usize {
513        debug_assert!(
514            !self.at_amplification_limit(),
515            "amplification limits should be checked before clamping datagram size values"
516        );
517
518        requested_size.min(self.max_datagram_size(transmission_mode))
519    }
520
521    #[inline]
522    pub fn transmission_constraint(&self) -> transmission::Constraint {
523        if self.at_amplification_limit() {
524            //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
525            //# Prior to validating the client address, servers MUST NOT send more
526            //# than three times as many bytes as the number of bytes they have
527            //# received.
528            transmission::Constraint::AmplificationLimited
529        } else if self.congestion_controller.is_congestion_limited() {
530            if self.congestion_controller.requires_fast_retransmission() {
531                //= https://www.rfc-editor.org/rfc/rfc9002#section-7.3.2
532                //# If the congestion window is reduced immediately, a
533                //# single packet can be sent prior to reduction.  This speeds up loss
534                //# recovery if the data in the lost packet is retransmitted and is
535                //# similar to TCP as described in Section 5 of [RFC6675].
536                transmission::Constraint::RetransmissionOnly
537            } else {
538                //= https://www.rfc-editor.org/rfc/rfc9002#section-7
539                //# An endpoint MUST NOT send a packet if it would cause bytes_in_flight
540                //# (see Appendix B.2) to be larger than the congestion window, unless
541                //# the packet is sent on a PTO timer expiration (see Section 6.2) or
542                //# when entering recovery (see Section 7.3.2).
543                transmission::Constraint::CongestionLimited
544            }
545        } else {
546            transmission::Constraint::None
547        }
548    }
549
550    /// Returns whether this path should be limited according to connection establishment amplification limits
551    ///
552    /// Note: As long as the path has _any_ TX credits we don't consider it to be amplification-limited.
553    ///       This may result in sending slightly more than 3x bytes but networking infrastructure mostly
554    ///       cares about the number of packets rather than bytes.
555    #[inline]
556    pub fn at_amplification_limit(&self) -> bool {
557        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
558        //# Prior to validating the client address, servers MUST NOT send more
559        //# than three times as many bytes as the number of bytes they have
560        //# received.
561        match self.state {
562            State::Validated => false,
563            State::AmplificationLimited { tx_allowance } => tx_allowance == 0,
564        }
565    }
566
567    /// Returns the current PTO period without jitter
568    #[inline]
569    pub fn pto_period(
570        &self,
571        space: s2n_quic_core::packet::number::PacketNumberSpace,
572    ) -> core::time::Duration {
573        self.rtt_estimator.pto_period(self.pto_backoff, space)
574    }
575
576    /// Returns the current PTO period with jitter applied if configured
577    #[inline]
578    pub fn pto_period_with_jitter(
579        &self,
580        space: s2n_quic_core::packet::number::PacketNumberSpace,
581        random_generator: &mut dyn random::Generator,
582    ) -> core::time::Duration {
583        self.rtt_estimator.pto_period_with_jitter(
584            self.pto_backoff,
585            space,
586            self.pto_jitter_percentage,
587            random_generator,
588        )
589    }
590
591    /// Resets the PTO backoff to the initial value
592    #[inline]
593    pub fn reset_pto_backoff(&mut self) {
594        self.pto_backoff = INITIAL_PTO_BACKOFF;
595    }
596
597    /// Returns `true` if the congestion window does not have sufficient space for a packet of the maximum
598    /// datagram size considering the current bytes in flight and the additional `bytes_sent` provided
599    #[inline]
600    pub fn is_congestion_limited(&self, bytes_sent: usize) -> bool {
601        let cwnd = self.congestion_controller.congestion_window();
602        let bytes_in_flight = self
603            .congestion_controller
604            .bytes_in_flight()
605            .saturating_add(bytes_sent as u32);
606        let max_datagram_size = self.max_datagram_size(transmission::Mode::Normal) as u32;
607
608        cwnd.saturating_sub(bytes_in_flight) < max_datagram_size
609    }
610
611    /// Compare a Path based on its PathHandle.
612    ///
613    /// QUIC only considers the remote address when identifying paths
614    //= https://www.rfc-editor.org/rfc/rfc9000#section-9.3
615    //# Receiving a packet from a new peer address containing a non-probing frame
616    //# indicates that the peer has migrated to that address.
617    #[inline]
618    fn eq_by_handle(&self, handle: &Config::PathHandle) -> bool {
619        self.handle
620            .remote_address()
621            .unmapped_eq(&handle.remote_address())
622    }
623}
624
625impl<Config: endpoint::Config> timer::Provider for Path<Config> {
626    #[inline]
627    fn timers<Q: timer::Query>(&self, query: &mut Q) -> timer::Result {
628        self.challenge.timers(query)?;
629        self.mtu_controller.timers(query)?;
630        self.ecn_controller.timers(query)?;
631
632        Ok(())
633    }
634}
635
636impl<Config: endpoint::Config> transmission::interest::Provider for Path<Config> {
637    /// Indicate if the path is interested in transmitting PATH_CHALLENGE or
638    /// PATH_RESPONSE frames.
639    #[inline]
640    fn transmission_interest<Q: transmission::interest::Query>(
641        &self,
642        query: &mut Q,
643    ) -> transmission::interest::Result {
644        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
645        //# An endpoint MUST NOT delay transmission of a
646        //# packet containing a PATH_RESPONSE frame unless constrained by
647        //# congestion control.
648        if self.response_data.is_some() {
649            query.on_new_data()?;
650        }
651
652        self.challenge.transmission_interest(query)?;
653
654        Ok(())
655    }
656}
657
658#[cfg(any(test, feature = "testing"))]
659pub mod testing {
660    use crate::{endpoint, path::Path};
661    use core::time::Duration;
662    use s2n_quic_core::{
663        connection, connection::limits::ANTI_AMPLIFICATION_MULTIPLIER, path::mtu,
664        recovery::RttEstimator,
665    };
666
667    pub fn helper_path_server() -> Path<endpoint::testing::Server> {
668        Path::new(
669            Default::default(),
670            connection::PeerId::try_from_bytes(&[]).unwrap(),
671            connection::LocalId::TEST_ID,
672            RttEstimator::new(Duration::from_millis(30)),
673            Default::default(),
674            true,
675            mtu::Config::default(),
676            ANTI_AMPLIFICATION_MULTIPLIER,
677            0, // Default to no jitter for tests
678        )
679    }
680
681    pub fn helper_path_client() -> Path<endpoint::testing::Client> {
682        Path::new(
683            Default::default(),
684            connection::PeerId::try_from_bytes(&[]).unwrap(),
685            connection::LocalId::TEST_ID,
686            RttEstimator::new(Duration::from_millis(30)),
687            Default::default(),
688            false,
689            mtu::Config::default(),
690            ANTI_AMPLIFICATION_MULTIPLIER,
691            0, // Default to no jitter for tests
692        )
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699    use crate::{
700        contexts::testing::{MockWriteContext, OutgoingFrameBuffer},
701        endpoint::testing::Server as Config,
702        path,
703        path::{challenge::testing::helper_challenge, testing, testing::helper_path_client},
704    };
705    use core::time::Duration;
706    use s2n_quic_core::{
707        connection,
708        connection::limits::ANTI_AMPLIFICATION_MULTIPLIER,
709        endpoint,
710        event::testing::Publisher,
711        path::MINIMUM_MAX_DATAGRAM_SIZE,
712        recovery::{CongestionController, RttEstimator},
713        time::{Clock, NoopClock},
714        transmission,
715    };
716
717    type Path = super::Path<Config>;
718
719    #[test]
720    fn custom_anti_amplification_multiplier() {
721        let bytes_received = 100;
722
723        // allowance should increase based on the default anti_amplification_multiplier
724        let mut default_path = testing::helper_path_server();
725        let _ = default_path.on_bytes_received(bytes_received);
726        let allowance = match default_path.state {
727            path::State::AmplificationLimited { tx_allowance } => tx_allowance,
728            _ => unreachable!("path is amplification limited"),
729        };
730        let expected = ANTI_AMPLIFICATION_MULTIPLIER as u32 * bytes_received as u32;
731        assert_eq!(allowance, Counter::new(expected));
732
733        // allowance should increase based on the custom anti_amplification_multiplier
734        let mut custom_path = testing::helper_path_server();
735        custom_path.anti_amplification_multiplier = ANTI_AMPLIFICATION_MULTIPLIER + 10;
736        let _ = custom_path.on_bytes_received(bytes_received);
737        let allowance = match custom_path.state {
738            path::State::AmplificationLimited { tx_allowance } => tx_allowance,
739            _ => unreachable!("path is amplification limited"),
740        };
741        let expected = (ANTI_AMPLIFICATION_MULTIPLIER + 10) as u32 * bytes_received as u32;
742        assert_eq!(allowance, Counter::new(expected));
743    }
744
745    #[test]
746    fn response_data_should_only_be_sent_once() {
747        // Setup:
748        let mut path = testing::helper_path_server();
749        let now = NoopClock {}.get_time();
750
751        let mut frame_buffer = OutgoingFrameBuffer::new();
752        let mut context = MockWriteContext::new(
753            now,
754            &mut frame_buffer,
755            transmission::Constraint::None,
756            transmission::Mode::Normal,
757            endpoint::Type::Client,
758        );
759
760        // set response data
761        let expected_data: [u8; 8] = [0; 8];
762        path.on_path_challenge(&expected_data);
763        assert_eq!(path.response_data.unwrap(), expected_data);
764
765        // Trigger:
766        path.on_transmit(&mut context); // send response data
767
768        // Expectation:
769        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
770        //= type=test
771        //# An endpoint MUST NOT send more than one PATH_RESPONSE frame in
772        //# response to one PATH_CHALLENGE frame; see Section 13.3.
773        assert!(path.response_data.is_none());
774
775        assert_eq!(context.frame_buffer.len(), 1);
776        let written_data = match context.frame_buffer.pop_front().unwrap().as_frame() {
777            frame::Frame::PathResponse(frame) => Some(*frame.data),
778            _ => None,
779        };
780        assert_eq!(written_data.unwrap(), expected_data);
781    }
782
783    #[test]
784    fn on_timeout_should_set_challenge_to_none_on_challenge_abandonment() {
785        // Setup:
786        let mut publisher = Publisher::snapshot();
787        let mut path = testing::helper_path_server();
788        let helper_challenge = helper_challenge();
789        let expiration_time = helper_challenge.now + helper_challenge.abandon_duration;
790        path.set_challenge(helper_challenge.challenge);
791
792        let mut frame_buffer = OutgoingFrameBuffer::new();
793        let mut context = MockWriteContext::new(
794            helper_challenge.now,
795            &mut frame_buffer,
796            transmission::Constraint::None,
797            transmission::Mode::Normal,
798            endpoint::Type::Client,
799        );
800        path.on_transmit(&mut context); // send challenge and arm timer
801
802        // Expectation:
803        assert!(path.is_challenge_pending());
804        assert!(path.challenge.is_pending());
805
806        // Trigger:
807        path.on_timeout(
808            expiration_time + Duration::from_millis(10),
809            path::Id::test_id(),
810            &mut random::testing::Generator(123),
811            &mut publisher,
812        );
813
814        // Expectation:
815        assert!(!path.is_challenge_pending());
816        assert!(!path.challenge.is_pending());
817    }
818
819    #[test]
820    fn is_challenge_pending_should_return_false_if_challenge_is_not_set() {
821        // Setup:
822        let mut path = testing::helper_path_server();
823        let helper_challenge = helper_challenge();
824
825        // Expectation:
826        assert!(!path.is_challenge_pending());
827        assert!(!path.challenge.is_pending());
828
829        // Trigger:
830        path.set_challenge(helper_challenge.challenge);
831
832        // Expectation:
833        assert!(path.is_challenge_pending());
834        assert!(path.challenge.is_pending());
835    }
836
837    #[test]
838    fn first_path_in_disabled_state_cant_fail_validation() {
839        // Setup:
840        let path = testing::helper_path_server();
841
842        // Expectation:
843        assert!(path.challenge.is_disabled());
844        assert!(!path.is_challenge_pending());
845        assert!(!path.is_validated());
846
847        assert!(!path.failed_validation());
848    }
849
850    #[test]
851    fn failed_validation() {
852        // Setup:
853        let mut publisher = Publisher::snapshot();
854        let mut path = testing::helper_path_server();
855        let helper_challenge = helper_challenge();
856
857        path.set_challenge(helper_challenge.challenge);
858        let mut frame_buffer = OutgoingFrameBuffer::new();
859        let mut context = MockWriteContext::new(
860            helper_challenge.now,
861            &mut frame_buffer,
862            transmission::Constraint::None,
863            transmission::Mode::Normal,
864            endpoint::Type::Client,
865        );
866        path.on_transmit(&mut context); // send challenge and arm timer
867
868        let expiration_time = helper_challenge.now + helper_challenge.abandon_duration;
869
870        // Trigger:
871        path.on_timeout(
872            expiration_time + Duration::from_millis(10),
873            path::Id::test_id(),
874            &mut random::testing::Generator(123),
875            &mut publisher,
876        );
877
878        // Expectation:
879        assert!(!path.challenge.is_disabled());
880        assert!(!path.is_challenge_pending());
881        assert!(!path.is_validated());
882
883        assert!(path.failed_validation());
884    }
885
886    #[test]
887    fn abandon_challenge() {
888        // Setup:
889        let mut path = testing::helper_path_server();
890        let helper_challenge = helper_challenge();
891        path.set_challenge(helper_challenge.challenge);
892        let mut publisher = event::testing::Publisher::snapshot();
893
894        // Trigger:
895        path.abandon_challenge(&mut publisher, 0);
896
897        // Expectation:
898        assert!(!path.challenge.is_pending());
899    }
900
901    #[test]
902    fn on_path_challenge_should_set_response_data() {
903        // Setup:
904        let mut path = testing::helper_path_server();
905
906        // Expectation:
907        assert!(path.response_data.is_none());
908
909        // Trigger:
910        let expected_data: [u8; 8] = [0; 8];
911        path.on_path_challenge(&expected_data);
912
913        // Expectation:
914        assert!(path.response_data.is_some());
915    }
916
917    //= https://www.rfc-editor.org/rfc/rfc9000#section-8.2.2
918    //= type=test
919    //# On receiving a PATH_CHALLENGE frame, an endpoint MUST respond by
920    //# echoing the data contained in the PATH_CHALLENGE frame in a
921    //# PATH_RESPONSE frame.
922    #[test]
923    fn on_path_challenge_should_replace_response_data() {
924        // Setup:
925        let mut path = testing::helper_path_server();
926        let expected_data: [u8; 8] = [0; 8];
927
928        // Trigger 1:
929        path.on_path_challenge(&expected_data);
930
931        // Expectation 1:
932        assert_eq!(path.response_data.unwrap(), expected_data);
933
934        // Trigger 2:
935        let new_expected_data: [u8; 8] = [1; 8];
936        path.on_path_challenge(&new_expected_data);
937
938        // Expectation 2:
939        assert_ne!(expected_data, new_expected_data);
940        assert_eq!(path.response_data.unwrap(), new_expected_data);
941    }
942
943    #[test]
944    fn validate_path_response_should_only_validate_if_challenge_is_set() {
945        // Setup:
946        let mut path = testing::helper_path_server();
947        let helper_challenge = helper_challenge();
948
949        // Expectation:
950        assert!(!path.is_validated());
951
952        // Trigger:
953        path.set_challenge(helper_challenge.challenge);
954        assert!(path.on_path_response(&helper_challenge.expected_data));
955
956        // Expectation:
957        assert!(path.is_validated());
958    }
959
960    #[test]
961    fn on_validated_should_change_state_to_validated_and_clear_challenge() {
962        // Setup:
963        let mut path = testing::helper_path_server();
964        let helper_challenge = helper_challenge();
965        path.set_challenge(helper_challenge.challenge);
966
967        assert!(!path.is_validated());
968        assert!(path.challenge.is_pending());
969
970        // Trigger:
971        path.on_validated();
972
973        // Expectation:
974        assert!(path.is_validated());
975        assert!(path.challenge.is_pending());
976    }
977
978    #[test]
979    fn on_validated_when_already_validated_does_nothing() {
980        // Setup:
981        let mut path = testing::helper_path_server();
982        path.set_challenge(helper_challenge().challenge);
983        path.on_validated();
984
985        // Trigger:
986        path.on_validated();
987
988        // Expectation:
989        assert!(path.is_validated());
990        assert!(path.challenge.is_pending());
991    }
992
993    #[test]
994    fn amplification_limit_test() {
995        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4
996        //= type=test
997        //# If the client IP address has changed, the server MUST
998        //# adhere to the anti-amplification limit; see Section 8.
999        // This is tested here by verifying a new Path starts in State::AmplificationLimited
1000
1001        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
1002        //= type=test
1003        //# For the purposes of
1004        //# avoiding amplification prior to address validation, servers MUST
1005        //# count all of the payload bytes received in datagrams that are
1006        //# uniquely attributed to a single connection.
1007
1008        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4
1009        //= type=test
1010        //# If the client IP address has changed, the server MUST
1011        //# adhere to the anti-amplification limit; see Section 8.
1012        // This tests the IP change because a new path is created when a new peer_address is
1013        // detected. This new path should always start in State::Pending.
1014        let mut path = testing::helper_path_server();
1015
1016        // Verify we enforce the amplification limit if we can't send
1017        // at least 1 minimum sized packet
1018        let mut amplification_outcome = path.on_bytes_received(1200);
1019        assert!(amplification_outcome.is_inactivate_path_unblocked());
1020        path.on_bytes_transmitted((1200 * 2) + 1);
1021
1022        // we round up to the nearest mtu
1023        assert!(!path.at_amplification_limit());
1024        assert_eq!(
1025            path.transmission_constraint(),
1026            transmission::Constraint::None
1027        );
1028
1029        amplification_outcome = path.on_bytes_received(1200);
1030        assert!(!path.at_amplification_limit());
1031        assert_eq!(
1032            path.transmission_constraint(),
1033            transmission::Constraint::None
1034        );
1035        // If we were not amplification limited previously, receiving
1036        // more bytes doesn't unblock
1037        assert!(amplification_outcome.is_unchanged());
1038
1039        path.on_bytes_transmitted((1200 * 6) + 1);
1040        assert!(path.at_amplification_limit());
1041        assert_eq!(
1042            path.transmission_constraint(),
1043            transmission::Constraint::AmplificationLimited
1044        );
1045        amplification_outcome = path.on_bytes_received(1200);
1046        assert!(amplification_outcome.is_inactivate_path_unblocked());
1047
1048        path.on_validated();
1049        path.on_bytes_transmitted(24);
1050        // Validated paths should always be able to transmit
1051        assert!(!path.at_amplification_limit());
1052        assert_eq!(
1053            path.transmission_constraint(),
1054            transmission::Constraint::None
1055        );
1056
1057        // If we were already not amplification limited, receiving
1058        // more bytes doesn't unblock
1059        amplification_outcome = path.on_bytes_received(1200);
1060        assert!(amplification_outcome.is_unchanged());
1061
1062        // Clients are not amplification limited
1063        let path = helper_path_client();
1064        assert!(path.is_validated());
1065    }
1066
1067    #[test]
1068    fn amplification_limited_mtu_test() {
1069        //= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
1070        //= type=test
1071        //# Prior to validating the client address, servers MUST NOT send more
1072        //# than three times as many bytes as the number of bytes they have
1073        //# received.
1074
1075        //= https://www.rfc-editor.org/rfc/rfc9000#section-14.1
1076        //= type=test
1077        //# The server MUST also limit the number of bytes it sends before
1078        //# validating the address of the client; see Section 8.
1079
1080        //= https://www.rfc-editor.org/rfc/rfc9000#section-14.2
1081        //= type=test
1082        //# All QUIC
1083        //# packets that are not sent in a PMTU probe SHOULD be sized to fit
1084        //# within the maximum datagram size to avoid the datagram being
1085        //# fragmented or dropped [RFC8085].
1086
1087        //= https://www.rfc-editor.org/rfc/rfc8899#section-3
1088        //= type=test
1089        //# A PL MUST NOT send a datagram (other than a probe
1090        //# packet) with a size at the PL that is larger than the current
1091        //# PLPMTU.
1092
1093        // TODO this would work better as a fuzz test
1094
1095        for &transmission_mode in &[
1096            Mode::Normal,
1097            Mode::PathValidationOnly,
1098            Mode::MtuProbing,
1099            Mode::LossRecoveryProbing,
1100        ] {
1101            let mut path = testing::helper_path_server();
1102            // Verify we can transmit up to the mtu
1103            let max_datagram_size = path.max_datagram_size(transmission_mode);
1104
1105            let amplification_outcome = path.on_bytes_received(3);
1106            path.on_bytes_transmitted(8);
1107
1108            assert!(amplification_outcome.is_inactivate_path_unblocked());
1109            assert_eq!(path.clamp_datagram_size(1, transmission_mode), 1);
1110            assert_eq!(path.clamp_datagram_size(10, transmission_mode), 10);
1111            assert_eq!(
1112                path.clamp_datagram_size(1800, transmission_mode),
1113                max_datagram_size
1114            );
1115
1116            path.on_bytes_transmitted(1);
1117            // Verify we can't transmit any more bytes
1118            assert!(path.at_amplification_limit());
1119
1120            let amplification_outcome = path.on_bytes_received(1);
1121            // Verify we can transmit up to 3 more bytes
1122            assert!(amplification_outcome.is_inactivate_path_unblocked());
1123            assert_eq!(path.clamp_datagram_size(1, transmission_mode), 1);
1124            assert_eq!(path.clamp_datagram_size(10, transmission_mode), 10);
1125            assert_eq!(
1126                path.clamp_datagram_size(1800, transmission_mode),
1127                max_datagram_size
1128            );
1129
1130            path.on_validated();
1131            // Validated paths should always be able to transmit
1132            assert_eq!(path.clamp_datagram_size(4, transmission_mode), 4);
1133        }
1134    }
1135
1136    #[test]
1137    fn clamp_mtu_for_validated_path() {
1138        let mut path = testing::helper_path_server();
1139        path.on_validated();
1140        let mtu = 1472;
1141        let probed_size = 1500;
1142        path.mtu_controller = mtu::testing::test_controller(mtu, probed_size);
1143
1144        assert_eq!(
1145            path.mtu_controller.max_datagram_size(),
1146            path.clamp_datagram_size(10000, transmission::Mode::Normal)
1147        );
1148        assert_eq!(
1149            MINIMUM_MAX_DATAGRAM_SIZE as usize,
1150            path.clamp_datagram_size(10000, transmission::Mode::PathValidationOnly)
1151        );
1152        assert_eq!(
1153            MINIMUM_MAX_DATAGRAM_SIZE as usize,
1154            path.clamp_datagram_size(10000, transmission::Mode::LossRecoveryProbing)
1155        );
1156        assert_eq!(
1157            path.mtu_controller.probed_sized(),
1158            path.clamp_datagram_size(10000, transmission::Mode::MtuProbing)
1159        );
1160    }
1161
1162    #[test]
1163    fn path_mtu() {
1164        let mut path = testing::helper_path_server();
1165        let amplification_outcome = path.on_bytes_received(1);
1166        assert!(amplification_outcome.is_inactivate_path_unblocked());
1167
1168        let mtu = 1472;
1169        let probed_size = 1500;
1170        path.mtu_controller = mtu::testing::test_controller(mtu, probed_size);
1171
1172        assert_eq!(
1173            path.mtu_controller.max_datagram_size(),
1174            path.max_datagram_size(transmission::Mode::Normal)
1175        );
1176        assert_eq!(
1177            MINIMUM_MAX_DATAGRAM_SIZE as usize,
1178            path.max_datagram_size(transmission::Mode::PathValidationOnly)
1179        );
1180        assert_eq!(
1181            MINIMUM_MAX_DATAGRAM_SIZE as usize,
1182            path.max_datagram_size(transmission::Mode::LossRecoveryProbing)
1183        );
1184        assert_eq!(
1185            path.mtu_controller.probed_sized(),
1186            path.max_datagram_size(transmission::Mode::MtuProbing)
1187        );
1188    }
1189
1190    #[test]
1191    #[should_panic]
1192    fn clamp_mtu_when_tx_more_than_rx() {
1193        let mut path = testing::helper_path_server();
1194        let mtu = 1472;
1195        let probed_size = 1500;
1196        path.mtu_controller = mtu::testing::test_controller(mtu, probed_size);
1197
1198        assert_eq!(
1199            0,
1200            path.clamp_datagram_size(10000, transmission::Mode::Normal)
1201        );
1202    }
1203
1204    #[test]
1205    fn peer_validated_test() {
1206        let mut path = testing::helper_path_client();
1207
1208        assert!(!path.is_peer_validated());
1209
1210        path.on_peer_validated();
1211
1212        assert!(path.is_peer_validated());
1213    }
1214
1215    #[test]
1216    fn transmission_constraint_test() {
1217        let mut path = Path::new(
1218            Default::default(),
1219            connection::PeerId::try_from_bytes(&[]).unwrap(),
1220            connection::LocalId::TEST_ID,
1221            RttEstimator::new(Duration::from_millis(30)),
1222            Default::default(),
1223            false,
1224            mtu::Config::default(),
1225            ANTI_AMPLIFICATION_MULTIPLIER,
1226            0, // Default to no jitter for tests
1227        );
1228        let now = NoopClock.get_time();
1229        let random = &mut random::testing::Generator::default();
1230        let mut publisher = event::testing::Publisher::snapshot();
1231        let mut publisher =
1232            congestion_controller::PathPublisher::new(&mut publisher, path::Id::test_id());
1233        path.on_validated();
1234
1235        assert_eq!(
1236            path.transmission_constraint(),
1237            transmission::Constraint::None
1238        );
1239
1240        // Fill up the congestion controller
1241        let packet_info = path.congestion_controller.on_packet_sent(
1242            now,
1243            path.congestion_controller.congestion_window() as usize,
1244            None,
1245            &path.rtt_estimator,
1246            &mut publisher,
1247        );
1248
1249        assert_eq!(
1250            path.transmission_constraint(),
1251            transmission::Constraint::CongestionLimited
1252        );
1253
1254        // Lose a byte to enter recovery
1255        path.congestion_controller.on_packet_lost(
1256            1,
1257            packet_info,
1258            false,
1259            false,
1260            random,
1261            now,
1262            &mut publisher,
1263        );
1264        path.congestion_controller.requires_fast_retransmission = true;
1265
1266        assert_eq!(
1267            path.transmission_constraint(),
1268            transmission::Constraint::RetransmissionOnly
1269        );
1270
1271        // Lose remaining bytes
1272        path.congestion_controller.on_packet_lost(
1273            path.congestion_controller.congestion_window(),
1274            packet_info,
1275            false,
1276            false,
1277            random,
1278            now,
1279            &mut publisher,
1280        );
1281        path.congestion_controller.requires_fast_retransmission = false;
1282
1283        // Since we are no longer congestion limited, there is no transmission constraint
1284        assert_eq!(
1285            path.transmission_constraint(),
1286            transmission::Constraint::None
1287        );
1288    }
1289
1290    #[test]
1291    fn is_congestion_limited() {
1292        let mut path = testing::helper_path_client();
1293        let max_datagram_size = path.mtu_controller.max_datagram_size() as u32;
1294
1295        path.congestion_controller.congestion_window = 12000;
1296        path.congestion_controller.bytes_in_flight = 12000 - 500 - max_datagram_size;
1297
1298        // There is room for an MTU sized packet after including the 500 bytes, so the path is not congestion limited
1299        assert!(!path.is_congestion_limited(500));
1300
1301        // There isn't room for an MTU sized packet after including the 501 bytes, so the path is congestion limited
1302        assert!(path.is_congestion_limited(501));
1303    }
1304
1305    #[test]
1306    fn pto_period_with_jitter_configuration() {
1307        use s2n_quic_core::{
1308            connection::limits::ANTI_AMPLIFICATION_MULTIPLIER, packet::number::PacketNumberSpace,
1309        };
1310
1311        // Test with zero jitter (should match original behavior)
1312        let path_no_jitter = Path::new(
1313            Default::default(),
1314            connection::PeerId::try_from_bytes(&[]).unwrap(),
1315            connection::LocalId::TEST_ID,
1316            RttEstimator::new(Duration::from_millis(100)),
1317            Default::default(),
1318            false,
1319            mtu::Config::default(),
1320            ANTI_AMPLIFICATION_MULTIPLIER,
1321            0, // No jitter
1322        );
1323
1324        let mut rng = random::testing::Generator::default();
1325        let pto_no_jitter = path_no_jitter.pto_period(PacketNumberSpace::ApplicationData);
1326        let pto_no_jitter_with_method =
1327            path_no_jitter.pto_period_with_jitter(PacketNumberSpace::ApplicationData, &mut rng);
1328
1329        // Test with jitter enabled
1330        let path_with_jitter = Path::new(
1331            Default::default(),
1332            connection::PeerId::try_from_bytes(&[]).unwrap(),
1333            connection::LocalId::TEST_ID,
1334            RttEstimator::new(Duration::from_millis(100)),
1335            Default::default(),
1336            false,
1337            mtu::Config::default(),
1338            ANTI_AMPLIFICATION_MULTIPLIER,
1339            25, // 25% jitter
1340        );
1341
1342        let pto_with_jitter =
1343            path_with_jitter.pto_period_with_jitter(PacketNumberSpace::ApplicationData, &mut rng);
1344
1345        // Zero jitter methods should produce identical results
1346        assert_eq!(pto_no_jitter, pto_no_jitter_with_method);
1347
1348        // Verify the jitter percentage is stored correctly
1349        assert_eq!(path_no_jitter.pto_jitter_percentage, 0);
1350        assert_eq!(path_with_jitter.pto_jitter_percentage, 25);
1351
1352        // Both should be positive durations
1353        assert!(pto_no_jitter > Duration::ZERO);
1354        assert!(pto_with_jitter > Duration::ZERO);
1355    }
1356}