aeronet_transport/
rtt.rs

1//! See [`RttEstimator`].
2
3use {core::time::Duration, typesize::derive::TypeSize};
4
5/// Computes an RTT estimation for a network path.
6///
7/// This is based on [`quinn-proto`'s `RttEstimator`](https://github.com/quinn-rs/quinn/blob/411abe9/quinn-proto/src/connection/paths.rs#L151).
8#[derive(Debug, Clone, TypeSize)]
9pub struct RttEstimator {
10    latest: Duration,
11    smoothed: Duration,
12    var: Duration,
13    min: Duration,
14}
15
16const TIMER_GRANULARITY: Duration = Duration::from_millis(1);
17
18impl RttEstimator {
19    /// Creates a new estimator from a given initial RTT.
20    #[must_use]
21    pub fn new(initial_rtt: Duration) -> Self {
22        Self {
23            latest: initial_rtt,
24            smoothed: initial_rtt,
25            var: initial_rtt / 2,
26            min: initial_rtt,
27        }
28    }
29
30    /// Gets the current best RTT estimation.
31    #[must_use]
32    pub const fn get(&self) -> Duration {
33        self.smoothed
34    }
35
36    /// Gets a conservative estimate of RTT.
37    ///
38    /// Takes the maximum of smoothed and latest RTT, as recommended
39    /// in 6.1.2 of the recovery spec (draft 29).
40    #[must_use]
41    pub fn conservative(&self) -> Duration {
42        self.get().max(self.latest)
43    }
44
45    /// Gets the minimum RTT registered so far for this estimator.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// # use {aeronet_transport::rtt::RttEstimator, core::time::Duration};
51    /// let mut rtt = RttEstimator::new(Duration::from_millis(500));
52    /// assert_eq!(Duration::from_millis(500), rtt.min());
53    ///
54    /// rtt.update(Duration::from_millis(750));
55    /// assert_eq!(Duration::from_millis(500), rtt.min());
56    ///
57    /// rtt.update(Duration::from_millis(250));
58    /// assert_eq!(Duration::from_millis(250), rtt.min());
59    /// ```
60    #[must_use]
61    pub const fn min(&self) -> Duration {
62        self.min
63    }
64
65    /// Computes the probe timeout duration (PTO) as described in
66    /// [RFC 9002 Section 6.2.1].
67    ///
68    /// [RFC 9002 Section 6.2.1]: https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1
69    #[must_use]
70    pub fn pto(&self) -> Duration {
71        self.get() + (self.var * 4).max(TIMER_GRANULARITY)
72    }
73
74    /// Adds an RTT sample to this estimation.
75    pub fn update(&mut self, rtt: Duration) {
76        self.latest = rtt;
77        self.min = self.min.min(rtt);
78
79        let var_sample = self.smoothed.abs_diff(rtt);
80        self.var = (3 * self.var + var_sample) / 4;
81        self.smoothed = (7 * self.smoothed + rtt) / 8;
82    }
83}
84
85/// Default initial RTT to use for [`RttEstimator`] before any RTT samples have
86/// been provided.
87///
88/// This value is based on [RFC 9002 Section 6.2.2].
89///
90/// [RFC 9002 Section 6.2.2]: https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2-1
91pub const DEFAULT_INITIAL_RTT: Duration = Duration::from_millis(333);
92
93impl Default for RttEstimator {
94    fn default() -> Self {
95        Self::new(DEFAULT_INITIAL_RTT)
96    }
97}