use core::time::Duration;
pub const INITIAL_RTT: Duration = Duration::from_millis(100);
pub const INITIAL_RTT_VAR: Duration = Duration::from_millis(50);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RttEstimator {
rtt: Duration,
rtt_var: Duration,
}
impl Default for RttEstimator {
fn default() -> Self {
RttEstimator {
rtt: INITIAL_RTT,
rtt_var: INITIAL_RTT_VAR,
}
}
}
impl RttEstimator {
pub fn new() -> Self {
Self::default()
}
pub fn rtt(&self) -> Duration {
self.rtt
}
pub fn rtt_var(&self) -> Duration {
self.rtt_var
}
pub fn rtt_us(&self) -> u32 {
self.rtt.as_micros().min(u128::from(u32::MAX)) as u32
}
pub fn rtt_var_us(&self) -> u32 {
self.rtt_var.as_micros().min(u128::from(u32::MAX)) as u32
}
pub fn update(&mut self, sample: Duration) {
let rtt_us = self.rtt.as_micros() as i64;
let rtt_var_us = self.rtt_var.as_micros() as i64;
let sample_us = sample.as_micros() as i64;
let new_rtt_us = (7 * rtt_us + sample_us) / 8;
let new_rtt_var_us = (3 * rtt_var_us + (rtt_us - sample_us).abs()) / 4;
self.rtt = Duration::from_micros(new_rtt_us.max(0) as u64);
self.rtt_var = Duration::from_micros(new_rtt_var_us.max(0) as u64);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn initial_values_match_rule_31() {
let e = RttEstimator::new();
assert_eq!(e.rtt(), Duration::from_millis(100));
assert_eq!(e.rtt_var(), Duration::from_millis(50));
assert_eq!(e.rtt_us(), 100_000);
assert_eq!(e.rtt_var_us(), 50_000);
}
#[test]
fn one_update_matches_hand_computed_formula() {
let mut e = RttEstimator::new();
e.update(Duration::from_micros(20_000));
assert_eq!(e.rtt_us(), 90_000);
assert_eq!(e.rtt_var_us(), 57_500);
}
#[test]
fn matching_sample_shrinks_rttvar_and_holds_rtt() {
let mut e = RttEstimator::new();
e.update(Duration::from_micros(100_000));
assert_eq!(e.rtt_us(), 100_000);
assert_eq!(e.rtt_var_us(), 37_500); }
#[test]
fn repeated_sampling_converges_toward_the_injected_rtt() {
let mut e = RttEstimator::new();
let target = Duration::from_millis(30);
for _ in 0..40 {
e.update(target);
}
let got = e.rtt().as_micros() as i64;
let want = target.as_micros() as i64;
assert!(
(got - want).abs() < 2_000,
"expected convergence within 2ms of {want}us, got {got}us"
);
}
}