use std::time::{Duration, Instant};
const DEFAULT_INITIAL_RTT_MS: u64 = 100;
const RTT_ALPHA: f64 = 0.125;
const RTT_BETA: f64 = 0.25;
const MIN_CWND: u32 = 2;
#[derive(Debug, Clone)]
pub struct RttEstimator {
srtt: Option<u64>,
rttvar: Option<u64>,
min_rtt: Option<u64>,
last_update: Option<Instant>,
}
impl Default for RttEstimator {
fn default() -> Self {
Self::new()
}
}
impl RttEstimator {
pub fn new() -> Self {
Self {
srtt: None,
rttvar: None,
min_rtt: None,
last_update: None,
}
}
pub fn add_sample(&mut self, rtt_us: u64) {
self.min_rtt = Some(self.min_rtt.map_or(rtt_us, |m| m.min(rtt_us)));
if self.srtt.is_none() {
self.srtt = Some(rtt_us);
self.rttvar = Some(rtt_us / 2);
} else {
let srtt = self.srtt.unwrap() as f64;
let rttvar = self.rttvar.unwrap() as f64;
let rtt = rtt_us as f64;
let new_srtt = (1.0 - RTT_ALPHA) * srtt + RTT_ALPHA * rtt;
let new_rttvar = (1.0 - RTT_BETA) * rttvar + RTT_BETA * (new_srtt - rtt).abs();
self.srtt = Some(new_srtt as u64);
self.rttvar = Some(new_rttvar as u64);
}
self.last_update = Some(Instant::now());
}
pub fn srtt_us(&self) -> u64 {
self.srtt.unwrap_or(DEFAULT_INITIAL_RTT_MS * 1000)
}
pub fn srtt(&self) -> Duration {
Duration::from_micros(self.srtt_us())
}
pub fn rttvar_us(&self) -> u64 {
self.rttvar.unwrap_or(DEFAULT_INITIAL_RTT_MS * 500)
}
pub fn min_rtt_us(&self) -> Option<u64> {
self.min_rtt
}
pub fn min_rtt(&self) -> Option<Duration> {
self.min_rtt.map(Duration::from_micros)
}
pub fn rto(&self) -> Duration {
let srtt = self.srtt_us();
let rttvar = self.rttvar_us();
let rto_us = srtt.saturating_add(4 * rttvar);
let rto_clamped = rto_us.clamp(200_000, 2_000_000);
Duration::from_micros(rto_clamped)
}
pub fn has_samples(&self) -> bool {
self.srtt.is_some()
}
pub fn reset(&mut self) {
self.srtt = None;
self.rttvar = None;
self.min_rtt = None;
self.last_update = None;
}
}
#[derive(Debug, Clone)]
pub struct DelayEstimator {
base_delay: Option<u64>,
current_delay: u64,
delay_history: Vec<u64>,
max_history: usize,
target_delay_us: u64,
}
impl Default for DelayEstimator {
fn default() -> Self {
Self::new()
}
}
impl DelayEstimator {
pub fn new() -> Self {
Self {
base_delay: None,
current_delay: 0,
delay_history: Vec::with_capacity(60),
max_history: 60, target_delay_us: 100_000, }
}
pub fn with_target_delay(target_delay: Duration) -> Self {
Self {
target_delay_us: target_delay.as_micros() as u64,
..Self::new()
}
}
pub fn add_sample(&mut self, delay_us: u64) {
self.current_delay = delay_us;
self.delay_history.push(delay_us);
if self.delay_history.len() > self.max_history {
self.delay_history.remove(0);
}
self.base_delay = self.delay_history.iter().min().copied();
}
pub fn current_delay_us(&self) -> u64 {
self.current_delay
}
pub fn current_delay(&self) -> Duration {
Duration::from_micros(self.current_delay)
}
pub fn base_delay_us(&self) -> Option<u64> {
self.base_delay
}
pub fn base_delay(&self) -> Option<Duration> {
self.base_delay.map(Duration::from_micros)
}
pub fn queuing_delay_us(&self) -> u64 {
self.current_delay
.saturating_sub(self.base_delay.unwrap_or(0))
}
pub fn queuing_delay(&self) -> Duration {
Duration::from_micros(self.queuing_delay_us())
}
pub fn target_delay(&self) -> Duration {
Duration::from_micros(self.target_delay_us)
}
pub fn delay_offset_us(&self) -> i64 {
let queuing = self.queuing_delay_us() as i64;
let target = self.target_delay_us as i64;
queuing - target
}
pub fn is_congested(&self) -> bool {
self.queuing_delay_us() > self.target_delay_us
}
pub fn reset(&mut self) {
self.base_delay = None;
self.current_delay = 0;
self.delay_history.clear();
}
}
#[derive(Debug, Clone)]
pub struct BandwidthEstimator {
bytes_in_window: u64,
window_start: Option<Instant>,
window_duration: Duration,
samples: Vec<f64>,
max_samples: usize,
smoothed_bps: Option<f64>,
}
impl Default for BandwidthEstimator {
fn default() -> Self {
Self::new()
}
}
impl BandwidthEstimator {
pub fn new() -> Self {
Self {
bytes_in_window: 0,
window_start: None,
window_duration: Duration::from_millis(100),
samples: Vec::with_capacity(10),
max_samples: 10,
smoothed_bps: None,
}
}
pub fn with_window_duration(window: Duration) -> Self {
Self {
window_duration: window,
..Self::new()
}
}
pub fn record_bytes(&mut self, bytes: u64) {
let now = Instant::now();
if self.window_start.is_none() {
self.window_start = Some(now);
}
self.bytes_in_window += bytes;
if let Some(start) = self.window_start
&& now.duration_since(start) >= self.window_duration
{
self.finalize_window();
}
}
fn finalize_window(&mut self) {
if let Some(start) = self.window_start {
let elapsed = start.elapsed();
if elapsed.as_secs_f64() > 0.0 {
let bps = (self.bytes_in_window as f64) / elapsed.as_secs_f64();
self.add_sample(bps);
}
}
self.bytes_in_window = 0;
self.window_start = Some(Instant::now());
}
fn add_sample(&mut self, bps: f64) {
self.samples.push(bps);
if self.samples.len() > self.max_samples {
self.samples.remove(0);
}
if !self.samples.is_empty() {
let sum: f64 = self.samples.iter().sum();
self.smoothed_bps = Some(sum / self.samples.len() as f64);
}
}
pub fn bytes_per_second(&self) -> Option<f64> {
self.smoothed_bps
}
pub fn bits_per_second(&self) -> Option<f64> {
self.smoothed_bps.map(|bps| bps * 8.0)
}
pub fn bandwidth_string(&self) -> String {
if let Some(bps) = self.bits_per_second() {
if bps >= 1_000_000_000.0 {
format!("{:.2} Gbps", bps / 1_000_000_000.0)
} else if bps >= 1_000_000.0 {
format!("{:.2} Mbps", bps / 1_000_000.0)
} else if bps >= 1_000.0 {
format!("{:.2} Kbps", bps / 1_000.0)
} else {
format!("{:.0} bps", bps)
}
} else {
"Unknown".to_string()
}
}
pub fn has_estimate(&self) -> bool {
self.smoothed_bps.is_some()
}
pub fn sample_count(&self) -> usize {
self.samples.len()
}
pub fn reset(&mut self) {
self.bytes_in_window = 0;
self.window_start = None;
self.samples.clear();
self.smoothed_bps = None;
}
pub fn update(&mut self) {
if self.bytes_in_window > 0 {
self.finalize_window();
}
}
}
#[derive(Debug, Clone)]
pub struct CongestionController {
cwnd: u32,
ssthresh: u32,
bytes_in_flight: u32,
max_wnd: u32,
in_slow_start: bool,
}
impl Default for CongestionController {
fn default() -> Self {
Self::new()
}
}
impl CongestionController {
pub fn new() -> Self {
Self {
cwnd: 2 * 1500, ssthresh: u32::MAX,
bytes_in_flight: 0,
max_wnd: 1024 * 1024, in_slow_start: true,
}
}
pub fn cwnd(&self) -> u32 {
self.cwnd
}
pub fn ssthresh(&self) -> u32 {
self.ssthresh
}
pub fn bytes_in_flight(&self) -> u32 {
self.bytes_in_flight
}
pub fn can_send(&self) -> bool {
self.bytes_in_flight < self.cwnd
}
pub fn available_window(&self) -> u32 {
self.cwnd.saturating_sub(self.bytes_in_flight)
}
pub fn on_send(&mut self, bytes: u32) {
self.bytes_in_flight = self.bytes_in_flight.saturating_add(bytes);
}
pub fn on_ack(&mut self, bytes: u32, delay_offset_us: i64) {
self.bytes_in_flight = self.bytes_in_flight.saturating_sub(bytes);
if self.in_slow_start {
self.cwnd = self.cwnd.saturating_add(bytes);
if self.cwnd >= self.ssthresh {
self.in_slow_start = false;
}
} else {
let gain = self.calculate_gain(delay_offset_us);
self.cwnd = (self.cwnd as f64 * gain) as u32;
}
self.cwnd = self.cwnd.clamp(MIN_CWND * 1500, self.max_wnd);
}
fn calculate_gain(&self, delay_offset_us: i64) -> f64 {
const TARGET_DELAY_US: i64 = 100_000; const GAIN: f64 = 1.0 / TARGET_DELAY_US as f64;
if delay_offset_us > 0 {
1.0 - GAIN * delay_offset_us as f64 / 1000.0
} else {
1.0 + GAIN * (-delay_offset_us) as f64 / 1000.0
}
}
pub fn on_loss(&mut self) {
self.ssthresh = self.cwnd / 2;
self.cwnd = self.ssthresh.max(MIN_CWND * 1500);
self.in_slow_start = false;
}
pub fn on_timeout(&mut self) {
self.ssthresh = self.cwnd / 2;
self.cwnd = MIN_CWND * 1500;
self.in_slow_start = true;
self.bytes_in_flight = 0;
}
pub fn reset(&mut self) {
self.cwnd = 2 * 1500;
self.ssthresh = u32::MAX;
self.bytes_in_flight = 0;
self.in_slow_start = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rtt_estimator_initial_state() {
let estimator = RttEstimator::new();
assert!(!estimator.has_samples());
assert_eq!(estimator.srtt_us(), DEFAULT_INITIAL_RTT_MS * 1000);
}
#[test]
fn test_rtt_estimator_first_sample() {
let mut estimator = RttEstimator::new();
estimator.add_sample(100_000);
assert!(estimator.has_samples());
assert_eq!(estimator.srtt_us(), 100_000);
assert_eq!(estimator.min_rtt_us(), Some(100_000));
}
#[test]
fn test_rtt_estimator_multiple_samples() {
let mut estimator = RttEstimator::new();
estimator.add_sample(100_000); estimator.add_sample(150_000); estimator.add_sample(120_000);
assert!(estimator.has_samples());
assert!(estimator.srtt_us() > 0);
assert_eq!(estimator.min_rtt_us(), Some(100_000));
}
#[test]
fn test_rtt_estimator_rto() {
let mut estimator = RttEstimator::new();
estimator.add_sample(100_000);
let rto = estimator.rto();
assert!(rto >= Duration::from_millis(200));
assert!(rto <= Duration::from_secs(2));
}
#[test]
fn test_rtt_estimator_reset() {
let mut estimator = RttEstimator::new();
estimator.add_sample(100_000);
assert!(estimator.has_samples());
estimator.reset();
assert!(!estimator.has_samples());
}
#[test]
fn test_delay_estimator_initial_state() {
let estimator = DelayEstimator::new();
assert_eq!(estimator.current_delay_us(), 0);
assert!(estimator.base_delay_us().is_none());
}
#[test]
fn test_delay_estimator_add_sample() {
let mut estimator = DelayEstimator::new();
estimator.add_sample(50_000); assert_eq!(estimator.current_delay_us(), 50_000);
assert_eq!(estimator.base_delay_us(), Some(50_000));
estimator.add_sample(80_000); assert_eq!(estimator.current_delay_us(), 80_000);
assert_eq!(estimator.base_delay_us(), Some(50_000)); }
#[test]
fn test_delay_estimator_queuing_delay() {
let mut estimator = DelayEstimator::new();
estimator.add_sample(50_000); estimator.add_sample(150_000);
assert_eq!(estimator.queuing_delay_us(), 100_000); }
#[test]
fn test_delay_estimator_congestion() {
let mut estimator = DelayEstimator::new();
estimator.add_sample(50_000);
assert!(!estimator.is_congested());
estimator.add_sample(200_000);
assert!(estimator.is_congested());
}
#[test]
fn test_delay_estimator_delay_offset() {
let mut estimator = DelayEstimator::new();
estimator.add_sample(50_000);
estimator.add_sample(120_000);
let offset = estimator.delay_offset_us();
assert!(offset < 0);
}
#[test]
fn test_bandwidth_estimator_initial_state() {
let estimator = BandwidthEstimator::new();
assert!(!estimator.has_estimate());
assert_eq!(estimator.sample_count(), 0);
}
#[test]
fn test_bandwidth_estimator_record_bytes() {
let mut estimator = BandwidthEstimator::new();
estimator.record_bytes(1024);
assert!(!estimator.has_estimate());
}
#[test]
fn test_bandwidth_estimator_with_window() {
let mut estimator = BandwidthEstimator::with_window_duration(Duration::from_millis(10));
estimator.record_bytes(1024);
std::thread::sleep(Duration::from_millis(15));
estimator.update();
assert!(estimator.has_estimate());
}
#[test]
fn test_bandwidth_estimator_bandwidth_string() {
let mut estimator = BandwidthEstimator::with_window_duration(Duration::from_millis(10));
estimator.record_bytes(12_500);
std::thread::sleep(Duration::from_millis(15));
estimator.update();
let bw_string = estimator.bandwidth_string();
assert!(!bw_string.is_empty());
}
#[test]
fn test_bandwidth_estimator_reset() {
let mut estimator = BandwidthEstimator::new();
estimator.record_bytes(1024);
estimator.reset();
assert!(!estimator.has_estimate());
assert_eq!(estimator.sample_count(), 0);
}
#[test]
fn test_congestion_controller_initial_state() {
let cc = CongestionController::new();
assert!(cc.can_send());
assert_eq!(cc.bytes_in_flight(), 0);
}
#[test]
fn test_congestion_controller_send_and_ack() {
let mut cc = CongestionController::new();
cc.on_send(1500);
assert_eq!(cc.bytes_in_flight(), 1500);
cc.on_ack(1500, -10_000); assert_eq!(cc.bytes_in_flight(), 0);
}
#[test]
fn test_congestion_controller_loss() {
let mut cc = CongestionController::new();
cc.on_ack(1500, -10_000); cc.on_ack(1500, -10_000);
let initial_cwnd = cc.cwnd();
assert!(initial_cwnd > 2 * 1500);
cc.on_loss();
assert!(cc.cwnd() < initial_cwnd);
assert!(!cc.in_slow_start);
}
#[test]
fn test_congestion_controller_timeout() {
let mut cc = CongestionController::new();
cc.on_send(1500);
cc.on_send(1500);
assert_eq!(cc.bytes_in_flight(), 3000);
cc.on_timeout();
assert_eq!(cc.bytes_in_flight(), 0);
assert!(cc.in_slow_start);
}
#[test]
fn test_congestion_controller_available_window() {
let mut cc = CongestionController::new();
let available = cc.available_window();
assert!(available > 0);
cc.on_send(available);
assert_eq!(cc.available_window(), 0);
assert!(!cc.can_send());
}
}