// QUIC RTT estimation (RFC 9002 §5): from each round-trip sample an endpoint keeps the
// latest, minimum, smoothed, and mean-deviation RTT, which drive the probe-timeout and loss
// thresholds of loss recovery. The first sample seeds smoothed_rtt directly and rttvar at half
// the sample; later samples fold in an exponentially weighted moving average, discounting the
// peer's reported ack delay (capped at max_ack_delay) so only propagation time is measured. All
// durations are microseconds.
///|
/// An endpoint's RTT estimate for one packet-number space (RFC 9002 §5).
pub struct RttEstimator {
mut latest_rtt : Int64
mut min_rtt : Int64
mut smoothed_rtt : Int64
mut rttvar : Int64
mut has_sample : Bool
}
///|
/// A fresh estimator with no samples yet.
pub fn RttEstimator::new() -> RttEstimator {
{ latest_rtt: 0, min_rtt: 0, smoothed_rtt: 0, rttvar: 0, has_sample: false, }
}
///|
/// Fold in a new RTT sample (RFC 9002 §5.3). `latest_rtt` is the measured round trip and
/// `ack_delay` the peer's reported delay before sending the ACK, both in microseconds;
/// `ack_delay` is capped at `max_ack_delay` and subtracted only when doing so keeps the sample
/// at or above min_rtt. The first sample seeds the estimate; later ones update the EWMA.
pub fn RttEstimator::update(
self : RttEstimator,
latest_rtt : Int64,
ack_delay : Int64,
max_ack_delay : Int64,
) -> Unit {
self.latest_rtt = latest_rtt
if self.has_sample {
if latest_rtt < self.min_rtt {
self.min_rtt = latest_rtt
}
let capped_delay = if ack_delay < max_ack_delay {
ack_delay
} else {
max_ack_delay
}
let adjusted_rtt = if latest_rtt >= self.min_rtt + capped_delay {
latest_rtt - capped_delay
} else {
latest_rtt
}
let diff = self.smoothed_rtt - adjusted_rtt
let abs_diff = if diff < 0L { -diff } else { diff }
self.rttvar = (3L * self.rttvar + abs_diff) / 4L
self.smoothed_rtt = (7L * self.smoothed_rtt + adjusted_rtt) / 8L
} else {
self.min_rtt = latest_rtt
self.smoothed_rtt = latest_rtt
self.rttvar = latest_rtt / 2L
self.has_sample = true
}
}
///|
/// The latest RTT sample (microseconds).
pub fn RttEstimator::latest(self : RttEstimator) -> Int64 {
self.latest_rtt
}
///|
/// The minimum RTT seen (microseconds).
pub fn RttEstimator::min(self : RttEstimator) -> Int64 {
self.min_rtt
}
///|
/// The smoothed RTT (microseconds).
pub fn RttEstimator::smoothed(self : RttEstimator) -> Int64 {
self.smoothed_rtt
}
///|
/// The RTT variation, the mean deviation estimate (microseconds).
pub fn RttEstimator::variation(self : RttEstimator) -> Int64 {
self.rttvar
}
///|
/// The Probe Timeout duration (RFC 9002 §6.2.1): `smoothed_rtt + max(4·rttvar, granularity) +
/// max_ack_delay`. Before the first sample it is the initial RTT-based `2·initial_rtt`; here,
/// with no sample, it falls back to `granularity + max_ack_delay`.
pub fn RttEstimator::pto(
self : RttEstimator,
max_ack_delay : Int64,
granularity : Int64,
) -> Int64 {
let variation_term = {
let four_var = 4L * self.rttvar
if four_var > granularity {
four_var
} else {
granularity
}
}
self.smoothed_rtt + variation_term + max_ack_delay
}