use std::{cmp, time::Duration};
use log::trace;
const K: u32 = 4;
const ALPHA: f32 = 0.125; const BETA: f32 = 0.25;
pub const DEFAULT_GRANULARITY: Duration = Duration::from_millis(1);
fn abs_diff(a: Duration, b: Duration) -> Duration {
if a > b {
a - b
} else {
b - a
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RttCalcuator {
rto: Duration,
srtt: Duration,
rttvar: Duration,
granularity: Duration,
configured_rto: Duration,
}
impl RttCalcuator {
pub fn new(rto: Duration, granularity: Duration) -> Self {
Self {
srtt: Duration::default(),
rttvar: Duration::default(),
rto,
granularity,
configured_rto: rto,
}
}
pub fn reset(&mut self) {
self.srtt = Duration::default();
self.rttvar = Duration::default();
self.rto = self.configured_rto;
}
pub fn update(&mut self, r: Duration) {
if self.srtt == Duration::default() {
self.srtt = r;
self.rttvar = r / 2;
self.rto = self.srtt + cmp::max(self.granularity, self.rttvar * K);
trace!(
"First RTT measurement: srtt={}ms, rttvar={}ms, rto={}ms",
self.srtt.as_millis(),
self.rttvar.as_millis(),
self.rto.as_millis()
);
} else {
self.rttvar = self.rttvar.mul_f32(1.0 - BETA) + abs_diff(self.srtt, r).mul_f32(BETA);
self.srtt = self.srtt.mul_f32(1.0 - ALPHA) + r.mul_f32(ALPHA);
self.rto = self.srtt + cmp::max(self.granularity, self.rttvar.mul_f32(K as f32));
trace!(
"Subsequent RTT measurements: srtt={}ms, rttvar={}ms, rto={}ms",
self.srtt.as_millis(),
self.rttvar.as_millis(),
self.rto.as_millis()
);
}
}
pub fn rto(&self) -> Duration {
self.rto
}
}
#[cfg(test)]
mod rtt_calculator_tests {
use super::*;
fn init_logging() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[test]
fn test_rtt_calculator_stun() {
init_logging();
let rto = Duration::from_millis(500);
let granularity = Duration::from_millis(1);
let mut rtt = RttCalcuator::new(rto, granularity);
rtt.update(Duration::from_millis(100));
assert_eq!(
rtt.rto().as_millis(),
Duration::from_millis(300).as_millis()
);
rtt.update(Duration::from_millis(200));
assert_eq!(
rtt.rto().as_millis(),
Duration::from_millis(362).as_millis()
);
rtt.update(Duration::from_millis(300));
assert_eq!(
rtt.rto().as_millis(),
Duration::from_millis(510).as_millis()
);
}
#[test]
fn test_rtt_calculator_stun_granularity() {
init_logging();
let rto = Duration::from_millis(500);
let granularity = Duration::from_millis(1);
let mut rtt = RttCalcuator::new(rto, granularity);
rtt.update(Duration::from_nanos(10));
assert_eq!(rtt.rto().as_millis(), granularity.as_millis());
let rto = Duration::from_millis(500);
let granularity = Duration::from_millis(1);
let mut rtt = RttCalcuator::new(rto, granularity);
rtt.update(Duration::from_millis(25));
assert_eq!(rtt.rto().as_millis(), Duration::from_millis(75).as_millis());
for _i in 0..50 {
rtt.update(Duration::from_nanos(1));
}
assert_eq!(rtt.rto().as_millis(), granularity.as_millis());
}
}