s2n_quic_core/connection/
limits.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3#[cfg(feature = "alloc")]
4use crate::application::ServerName;
5use crate::{
6    ack,
7    event::{api::SocketAddress, IntoEvent},
8    inet, recovery, stream,
9    transport::parameters::{
10        AckDelayExponent, ActiveConnectionIdLimit, InitialFlowControlLimits, InitialMaxData,
11        InitialMaxStreamDataBidiLocal, InitialMaxStreamDataBidiRemote, InitialMaxStreamDataUni,
12        InitialMaxStreamsBidi, InitialMaxStreamsUni, InitialStreamLimits, MaxAckDelay,
13        MaxDatagramFrameSize, MaxIdleTimeout, MigrationSupport, TransportParameters,
14    },
15};
16#[cfg(feature = "alloc")]
17use bytes::Bytes;
18use core::time::Duration;
19use s2n_codec::decoder_invariant;
20
21pub use crate::transport::parameters::ValidationError;
22
23const MAX_HANDSHAKE_DURATION_DEFAULT: Duration = Duration::from_secs(10);
24
25//= https://www.rfc-editor.org/rfc/rfc9000#section-10.1.2
26//# A connection will time out if no packets are sent or received for a
27//# period longer than the time negotiated using the max_idle_timeout
28//# transport parameter; see Section 10.  However, state in middleboxes
29//# might time out earlier than that.  Though REQ-5 in [RFC4787]
30//# recommends a 2-minute timeout interval, experience shows that sending
31//# packets every 30 seconds is necessary to prevent the majority of
32//# middleboxes from losing state for UDP flows [GATEWAY].
33const MAX_KEEP_ALIVE_PERIOD_DEFAULT: Duration = Duration::from_secs(30);
34
35//= https://www.rfc-editor.org/rfc/rfc9000#section-8.1
36//# Prior to validating the client address, servers MUST NOT send more
37//# than three times as many bytes as the number of bytes they have
38//# received.
39pub const ANTI_AMPLIFICATION_MULTIPLIER: u8 = 3;
40
41pub const DEFAULT_STREAM_BATCH_SIZE: u8 = 1;
42
43// Maximum allowed PTO jitter percentage. Limited to 50% to prevent PTO timers
44// from becoming too short (which could cause premature timeouts) or too long
45// (which could delay loss recovery).
46pub const MAX_PTO_JITTER_PERCENTAGE: u8 = 50;
47pub const DEFAULT_PTO_JITTER_PERCENTAGE: u8 = 0;
48
49#[non_exhaustive]
50#[derive(Debug)]
51pub struct ConnectionInfo<'a> {
52    pub remote_address: SocketAddress<'a>,
53}
54
55impl<'a> ConnectionInfo<'a> {
56    #[inline]
57    #[doc(hidden)]
58    pub fn new(remote_address: &'a inet::SocketAddress) -> Self {
59        Self {
60            remote_address: remote_address.into_event(),
61        }
62    }
63}
64
65#[non_exhaustive]
66#[derive(Debug)]
67#[cfg(feature = "alloc")]
68pub struct HandshakeInfo<'a> {
69    pub remote_address: SocketAddress<'a>,
70    pub server_name: Option<&'a ServerName>,
71    pub application_protocol: &'a Bytes,
72}
73
74#[cfg(feature = "alloc")]
75impl<'a> HandshakeInfo<'a> {
76    pub fn new(
77        remote_address: &'a inet::SocketAddress,
78        server_name: Option<&'a ServerName>,
79        application_protocol: &'a Bytes,
80    ) -> HandshakeInfo<'a> {
81        Self {
82            remote_address: remote_address.into_event(),
83            server_name,
84            application_protocol,
85        }
86    }
87}
88
89#[derive(Clone, Copy, Debug)]
90pub struct Limits {
91    pub(crate) max_idle_timeout: MaxIdleTimeout,
92    pub(crate) data_window: InitialMaxData,
93    pub(crate) bidirectional_local_data_window: InitialMaxStreamDataBidiLocal,
94    pub(crate) bidirectional_remote_data_window: InitialMaxStreamDataBidiRemote,
95    pub(crate) unidirectional_data_window: InitialMaxStreamDataUni,
96    pub(crate) max_open_local_bidirectional_streams: stream::limits::LocalBidirectional,
97    pub(crate) max_open_local_unidirectional_streams: stream::limits::LocalUnidirectional,
98    pub(crate) max_open_remote_bidirectional_streams: InitialMaxStreamsBidi,
99    pub(crate) max_open_remote_unidirectional_streams: InitialMaxStreamsUni,
100    pub(crate) max_ack_delay: MaxAckDelay,
101    pub(crate) ack_delay_exponent: AckDelayExponent,
102    pub(crate) max_active_connection_ids: ActiveConnectionIdLimit,
103    pub(crate) ack_elicitation_interval: u8,
104    pub(crate) ack_ranges_limit: u8,
105    pub(crate) max_send_buffer_size: stream::limits::MaxSendBufferSize,
106    pub(crate) max_handshake_duration: Duration,
107    pub(crate) max_keep_alive_period: Duration,
108    pub(crate) max_datagram_frame_size: MaxDatagramFrameSize,
109    pub(crate) initial_round_trip_time: Duration,
110    pub(crate) migration_support: MigrationSupport,
111    pub(crate) anti_amplification_multiplier: u8,
112    pub(crate) stream_batch_size: u8,
113    pub(crate) pto_jitter_percentage: u8,
114}
115
116impl Default for Limits {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122macro_rules! setter {
123    ($(#[doc = $doc:literal])* $name:ident, $field:ident, $inner:ty $(, |$validate_value:ident| $validation:block)?) => {
124        $(#[doc = $doc])*
125        pub fn $name(mut self, value: $inner) -> Result<Self, ValidationError> {
126            $(
127                let $validate_value = value;
128                $validation
129            )?
130            self.$field = value.try_into()?;
131            Ok(self)
132        }
133    };
134}
135
136impl Limits {
137    pub const fn new() -> Self {
138        Self {
139            max_idle_timeout: MaxIdleTimeout::RECOMMENDED,
140            data_window: InitialMaxData::RECOMMENDED,
141            bidirectional_local_data_window: InitialMaxStreamDataBidiLocal::RECOMMENDED,
142            bidirectional_remote_data_window: InitialMaxStreamDataBidiRemote::RECOMMENDED,
143            unidirectional_data_window: InitialMaxStreamDataUni::RECOMMENDED,
144            max_open_local_bidirectional_streams: stream::limits::LocalBidirectional::RECOMMENDED,
145            max_open_local_unidirectional_streams: stream::limits::LocalUnidirectional::RECOMMENDED,
146            max_open_remote_bidirectional_streams: InitialMaxStreamsBidi::RECOMMENDED,
147            max_open_remote_unidirectional_streams: InitialMaxStreamsUni::RECOMMENDED,
148            max_ack_delay: MaxAckDelay::RECOMMENDED,
149            ack_delay_exponent: AckDelayExponent::RECOMMENDED,
150            max_active_connection_ids: ActiveConnectionIdLimit::RECOMMENDED,
151            ack_elicitation_interval: ack::Settings::RECOMMENDED.ack_elicitation_interval,
152            ack_ranges_limit: ack::Settings::RECOMMENDED.ack_ranges_limit,
153            max_send_buffer_size: stream::Limits::RECOMMENDED.max_send_buffer_size,
154            max_handshake_duration: MAX_HANDSHAKE_DURATION_DEFAULT,
155            max_keep_alive_period: MAX_KEEP_ALIVE_PERIOD_DEFAULT,
156            max_datagram_frame_size: MaxDatagramFrameSize::DEFAULT,
157            initial_round_trip_time: recovery::DEFAULT_INITIAL_RTT,
158            migration_support: MigrationSupport::RECOMMENDED,
159            anti_amplification_multiplier: ANTI_AMPLIFICATION_MULTIPLIER,
160            stream_batch_size: DEFAULT_STREAM_BATCH_SIZE,
161            pto_jitter_percentage: DEFAULT_PTO_JITTER_PERCENTAGE,
162        }
163    }
164
165    // We limit the initial data limit to u32::MAX (4GB), which far
166    // exceeds the reasonable amount of data a connection is
167    // initially allowed to send.
168    //
169    // By representing the flow control value as a u32, we save space
170    // on the connection state.
171    setter!(with_data_window, data_window, u64, |validate_value| {
172        decoder_invariant!(
173            validate_value <= u32::MAX.into(),
174            "data_window must be <= u32::MAX"
175        );
176    });
177    setter!(
178        with_bidirectional_local_data_window,
179        bidirectional_local_data_window,
180        u64,
181        |validate_value| {
182            decoder_invariant!(
183                validate_value <= u32::MAX.into(),
184                "bidirectional_local_data_window must be <= u32::MAX"
185            );
186        }
187    );
188    setter!(
189        with_bidirectional_remote_data_window,
190        bidirectional_remote_data_window,
191        u64,
192        |validate_value| {
193            decoder_invariant!(
194                validate_value <= u32::MAX.into(),
195                "bidirectional_remote_data_window must be <= u32::MAX"
196            );
197        }
198    );
199    setter!(
200        with_unidirectional_data_window,
201        unidirectional_data_window,
202        u64,
203        |validate_value| {
204            decoder_invariant!(
205                validate_value <= u32::MAX.into(),
206                "unidirectional_data_window must be <= u32::MAX"
207            );
208        }
209    );
210
211    setter!(
212        /// Sets the max_idle_timeout duration to advertise to the peer. If a max_idle_timeout is
213        /// specified by either endpoint, the connection is silently closed if it remains idle for
214        /// longer than the minimum of the max_idle_timeout value advertised by both endpoints.
215        with_max_idle_timeout,
216        max_idle_timeout,
217        Duration);
218
219    /// Sets both the max local and remote limits for bidirectional streams.
220    #[deprecated(
221        note = "use with_max_open_local_bidirectional_streams and with_max_open_remote_bidirectional_streams instead"
222    )]
223    pub fn with_max_open_bidirectional_streams(
224        mut self,
225        value: u64,
226    ) -> Result<Self, ValidationError> {
227        self.max_open_local_bidirectional_streams = value.try_into()?;
228        self.max_open_remote_bidirectional_streams = value.try_into()?;
229        Ok(self)
230    }
231
232    /// Sets the max local limits for bidirectional streams
233    ///
234    /// The value set is used instead of `with_max_open_bidirectional_streams` when set.
235    pub fn with_max_open_local_bidirectional_streams(
236        mut self,
237        value: u64,
238    ) -> Result<Self, ValidationError> {
239        self.max_open_local_bidirectional_streams = value.try_into()?;
240        Ok(self)
241    }
242
243    /// Sets the max remote limits for bidirectional streams.
244    ///
245    /// The value set is used instead of `with_max_open_bidirectional_streams` when set.
246    pub fn with_max_open_remote_bidirectional_streams(
247        mut self,
248        value: u64,
249    ) -> Result<Self, ValidationError> {
250        self.max_open_remote_bidirectional_streams = value.try_into()?;
251        Ok(self)
252    }
253
254    setter!(
255        with_max_open_local_unidirectional_streams,
256        max_open_local_unidirectional_streams,
257        u64
258    );
259    setter!(
260        with_max_open_remote_unidirectional_streams,
261        max_open_remote_unidirectional_streams,
262        u64
263    );
264    setter!(with_max_ack_delay, max_ack_delay, Duration);
265    setter!(
266        with_max_active_connection_ids,
267        max_active_connection_ids,
268        u64
269    );
270    setter!(with_stream_batch_size, stream_batch_size, u8);
271    setter!(with_ack_elicitation_interval, ack_elicitation_interval, u8);
272    setter!(with_max_ack_ranges, ack_ranges_limit, u8);
273    setter!(
274        /// Sets the maximum send buffer size for a Stream
275        ///
276        /// The send buffer contains unacknowledged application data. Constraining the maximum
277        /// size of this buffer limits the amount of memory a given Stream may consume. On
278        /// high bandwidth/high RTT connections this may act as a bottleneck, as the connection may be
279        /// waiting for data to be acknowledged by the peer before allowing more data to be sent.
280        /// Increasing this value should be carefully weighed against the potential downsides
281        /// of additional memory utilization as well as increased latency due to the capacity of the
282        /// send buffer exceeding the rate at which the network link and peer are able to drain from it.
283        /// Ideally, the max_send_buffer_size is configured to the minimum value that can support the
284        /// throughput requirements for the connection.
285        with_max_send_buffer_size,
286        max_send_buffer_size,
287        u32
288    );
289    setter!(
290        with_max_handshake_duration,
291        max_handshake_duration,
292        Duration
293    );
294    setter!(
295        /// Sets the period at which the connection will send a ping to its peer.
296        ///
297        /// Note: This setting will not have any effect unless the connection [`keep_alive`] function is
298        /// enabled. Additionally this setting should be smaller than the max_idle_timeout value. Typically
299        /// this value is used to prevent middleboxes from losing state for UDP flows.
300        with_max_keep_alive_period,
301        max_keep_alive_period,
302        Duration);
303    /// Sets whether active connection migration is supported for a server endpoint (default: true)
304    ///
305    /// If set to false, the `disable_active_migration` transport parameter will be sent to the
306    /// peer, and any attempt by the peer to perform an active connection migration will be ignored.
307    pub fn with_active_connection_migration(
308        mut self,
309        enabled: bool,
310    ) -> Result<Self, ValidationError> {
311        if enabled {
312            self.migration_support = MigrationSupport::Enabled
313        } else {
314            self.migration_support = MigrationSupport::Disabled
315        }
316        Ok(self)
317    }
318
319    /// Sets the initial round trip time (RTT) for use in recovery mechanisms prior to
320    /// measuring an actual RTT sample.
321    ///
322    /// This is useful for environments where RTTs are mostly predictable (e.g. data centers)
323    /// and are much lower than the default 333 milliseconds.
324    pub fn with_initial_round_trip_time(
325        mut self,
326        value: Duration,
327    ) -> Result<Self, ValidationError> {
328        ensure!(
329            value >= recovery::MIN_RTT,
330            Err(ValidationError(
331                "provided value must be at least 1 microsecond",
332            ))
333        );
334
335        self.initial_round_trip_time = value;
336        Ok(self)
337    }
338
339    #[cfg(feature = "unstable-limits")]
340    setter!(
341        /// Limit how many bytes the Server sends prior to address validation (default: 3)
342        ///
343        /// Prior to validating the client address, servers will not send more
344        /// than `anti_amplification_multiplier` times as many bytes as the
345        /// number of bytes it has received.
346        with_anti_amplification_multiplier,
347        anti_amplification_multiplier,
348        u8
349    );
350
351    /// Sets the PTO jitter percentage (default: 0)
352    ///
353    /// Adds random jitter to Probe Timeout (PTO) calculations to prevent synchronized
354    /// timeouts across multiple connections. The jitter is applied as a percentage
355    /// of the base PTO period, with values between -X% and +X% where X is the
356    /// configured percentage.
357    ///
358    /// Valid range: 0-50%
359    /// - 0%: No jitter (default)
360    /// - 1-50%: Applies random jitter within ±percentage of base PTO
361    pub fn with_pto_jitter_percentage(mut self, value: u8) -> Result<Self, ValidationError> {
362        ensure!(
363            value <= MAX_PTO_JITTER_PERCENTAGE,
364            Err(ValidationError(
365                "PTO jitter percentage must be between 0 and 50"
366            ))
367        );
368        self.pto_jitter_percentage = value;
369        Ok(self)
370    }
371
372    #[doc(hidden)]
373    #[inline]
374    pub fn pto_jitter_percentage(&self) -> u8 {
375        self.pto_jitter_percentage
376    }
377
378    // internal APIs
379
380    #[doc(hidden)]
381    #[inline]
382    pub fn load_peer<A, B, C, D>(&mut self, peer_parameters: &TransportParameters<A, B, C, D>) {
383        self.max_idle_timeout
384            .load_peer(&peer_parameters.max_idle_timeout);
385    }
386
387    #[doc(hidden)]
388    #[inline]
389    pub const fn ack_settings(&self) -> ack::Settings {
390        ack::Settings {
391            ack_delay_exponent: self.ack_delay_exponent.as_u8(),
392            max_ack_delay: self.max_ack_delay.as_duration(),
393            ack_ranges_limit: self.ack_ranges_limit,
394            ack_elicitation_interval: self.ack_elicitation_interval,
395        }
396    }
397
398    #[doc(hidden)]
399    #[inline]
400    pub const fn initial_flow_control_limits(&self) -> InitialFlowControlLimits {
401        InitialFlowControlLimits {
402            stream_limits: self.initial_stream_limits(),
403            max_data: self.data_window.as_varint(),
404            max_open_remote_bidirectional_streams: self
405                .max_open_remote_bidirectional_streams
406                .as_varint(),
407            max_open_remote_unidirectional_streams: self
408                .max_open_remote_unidirectional_streams
409                .as_varint(),
410        }
411    }
412
413    #[doc(hidden)]
414    #[inline]
415    pub const fn initial_stream_limits(&self) -> InitialStreamLimits {
416        InitialStreamLimits {
417            max_data_bidi_local: self.bidirectional_local_data_window.as_varint(),
418            max_data_bidi_remote: self.bidirectional_remote_data_window.as_varint(),
419            max_data_uni: self.unidirectional_data_window.as_varint(),
420        }
421    }
422
423    #[doc(hidden)]
424    #[inline]
425    pub fn stream_limits(&self) -> stream::Limits {
426        stream::Limits {
427            max_send_buffer_size: self.max_send_buffer_size,
428            max_open_local_unidirectional_streams: self.max_open_local_unidirectional_streams,
429            max_open_local_bidirectional_streams: self.max_open_local_bidirectional_streams,
430        }
431    }
432
433    #[doc(hidden)]
434    #[inline]
435    pub fn max_idle_timeout(&self) -> Option<Duration> {
436        self.max_idle_timeout.as_duration()
437    }
438
439    #[doc(hidden)]
440    #[inline]
441    pub fn max_handshake_duration(&self) -> Duration {
442        self.max_handshake_duration
443    }
444
445    #[doc(hidden)]
446    #[inline]
447    pub fn max_keep_alive_period(&self) -> Duration {
448        self.max_keep_alive_period
449    }
450
451    #[doc(hidden)]
452    #[inline]
453    pub fn initial_round_trip_time(&self) -> Duration {
454        self.initial_round_trip_time
455    }
456
457    #[doc(hidden)]
458    #[inline]
459    pub fn active_migration_enabled(&self) -> bool {
460        matches!(self.migration_support, MigrationSupport::Enabled)
461    }
462
463    #[doc(hidden)]
464    #[inline]
465    pub fn anti_amplification_multiplier(&self) -> u8 {
466        self.anti_amplification_multiplier
467    }
468
469    #[doc(hidden)]
470    #[inline]
471    pub fn stream_batch_size(&self) -> u8 {
472        self.stream_batch_size
473    }
474}
475
476#[must_use]
477#[derive(Debug)]
478pub struct UpdatableLimits<'a>(&'a mut Limits);
479
480impl<'a> UpdatableLimits<'a> {
481    pub fn new(limits: &'a mut Limits) -> UpdatableLimits<'a> {
482        UpdatableLimits(limits)
483    }
484
485    pub fn with_stream_batch_size(&mut self, size: u8) {
486        self.0.stream_batch_size = size;
487    }
488}
489
490/// Creates limits for a given connection
491pub trait Limiter: 'static + Send {
492    fn on_connection(&mut self, info: &ConnectionInfo) -> Limits;
493
494    /// Provides another opportunity to change connection limits with information
495    /// from the handshake
496    #[inline]
497    #[cfg(feature = "alloc")]
498    fn on_post_handshake(&mut self, info: &HandshakeInfo, limits: &mut UpdatableLimits) {
499        let _ = info;
500        let _ = limits;
501    }
502}
503
504/// Implement Limiter for a Limits struct
505impl Limiter for Limits {
506    fn on_connection(&mut self, _into: &ConnectionInfo) -> Limits {
507        *self
508    }
509    #[cfg(feature = "alloc")]
510    fn on_post_handshake(&mut self, _info: &HandshakeInfo, _limits: &mut UpdatableLimits) {}
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516
517    // Local max data limits should be <= u32::MAX
518    #[test]
519    fn limit_validation() {
520        let mut data = u32::MAX as u64 + 1;
521        let limits = Limits::default();
522        assert!(limits.with_data_window(data).is_err());
523        assert!(limits.with_bidirectional_local_data_window(data).is_err());
524        assert!(limits.with_bidirectional_remote_data_window(data).is_err());
525        assert!(limits.with_unidirectional_data_window(data).is_err());
526
527        data = u32::MAX as u64;
528        assert!(limits.with_data_window(data).is_ok());
529        assert!(limits.with_bidirectional_local_data_window(data).is_ok());
530        assert!(limits.with_bidirectional_remote_data_window(data).is_ok());
531        assert!(limits.with_unidirectional_data_window(data).is_ok());
532    }
533
534    // Limits can be updated through the UpdatableLimits wrapper
535    #[test]
536    fn updatable_limits() {
537        let mut limits = Limits::default();
538        assert_eq!(limits.stream_batch_size, 1);
539        let mut updatable_limits = UpdatableLimits::new(&mut limits);
540        let new_size = 10;
541        updatable_limits.with_stream_batch_size(new_size);
542        assert_eq!(limits.stream_batch_size, new_size);
543    }
544
545    #[test]
546    fn pto_jitter_percentage_default() {
547        let limits = Limits::new();
548        assert_eq!(
549            limits.pto_jitter_percentage(),
550            DEFAULT_PTO_JITTER_PERCENTAGE
551        );
552
553        let limits = Limits::default();
554        assert_eq!(
555            limits.pto_jitter_percentage(),
556            DEFAULT_PTO_JITTER_PERCENTAGE
557        );
558    }
559
560    #[test]
561    fn pto_jitter_percentage_valid_values() {
562        let limits = Limits::new();
563
564        // Test valid values (0-MAX_PTO_JITTER_PERCENTAGE)
565        for value in 0..=MAX_PTO_JITTER_PERCENTAGE {
566            let result = limits.with_pto_jitter_percentage(value);
567            assert!(result.is_ok(), "Value {} should be valid", value);
568            let limits = result.unwrap();
569            assert_eq!(limits.pto_jitter_percentage(), value);
570        }
571    }
572
573    #[test]
574    fn pto_jitter_percentage_invalid_values() {
575        let limits = Limits::new();
576
577        // Test invalid values (> MAX_PTO_JITTER_PERCENTAGE)
578        for value in (MAX_PTO_JITTER_PERCENTAGE + 1)..=255 {
579            let result = limits.with_pto_jitter_percentage(value);
580            assert!(result.is_err(), "Value {} should be invalid", value);
581
582            if let Err(ValidationError(msg)) = result {
583                assert_eq!(msg, "PTO jitter percentage must be between 0 and 50");
584            } else {
585                panic!("Expected ValidationError for value {}", value);
586            }
587        }
588    }
589
590    #[test]
591    fn pto_jitter_percentage_edge_cases() {
592        let limits = Limits::new();
593
594        // Test boundary values
595        let result = limits.with_pto_jitter_percentage(0);
596        assert!(result.is_ok());
597        assert_eq!(result.unwrap().pto_jitter_percentage(), 0);
598
599        let result = limits.with_pto_jitter_percentage(MAX_PTO_JITTER_PERCENTAGE);
600        assert!(result.is_ok());
601        assert_eq!(
602            result.unwrap().pto_jitter_percentage(),
603            MAX_PTO_JITTER_PERCENTAGE
604        );
605
606        let result = limits.with_pto_jitter_percentage(MAX_PTO_JITTER_PERCENTAGE + 1);
607        assert!(result.is_err());
608    }
609
610    #[test]
611    fn pto_jitter_percentage_chaining() {
612        // Test that the setter can be chained with other setters
613        let result = Limits::new()
614            .with_pto_jitter_percentage(25)
615            .and_then(|l| l.with_stream_batch_size(5));
616
617        assert!(result.is_ok());
618        let limits = result.unwrap();
619        assert_eq!(limits.pto_jitter_percentage(), 25);
620        assert_eq!(limits.stream_batch_size(), 5);
621    }
622
623    #[test]
624    fn pto_jitter_percentage_getter() {
625        let mut limits = Limits::new();
626
627        // Test initial value
628        assert_eq!(limits.pto_jitter_percentage(), 0);
629
630        // Test after setting value
631        limits = limits.with_pto_jitter_percentage(30).unwrap();
632        assert_eq!(limits.pto_jitter_percentage(), 30);
633
634        // Test that getter returns the correct value
635        limits = limits.with_pto_jitter_percentage(15).unwrap();
636        assert_eq!(limits.pto_jitter_percentage(), 15);
637    }
638}