const LOSS_LOW_THRESHOLD: f32 = 0.005; const LOSS_HIGH_THRESHOLD: f32 = 0.02; const AI_FRACTION: f64 = 0.08; const MD_FACTOR: f64 = 0.85;
#[derive(Debug, Clone)]
pub struct AimdController {
bitrate_bps: u64,
min_bps: u64,
max_bps: u64,
}
impl AimdController {
pub fn new(initial_bps: u64, min_bps: u64, max_bps: u64) -> Self {
Self {
bitrate_bps: initial_bps,
min_bps,
max_bps,
}
}
pub fn update_loss(&mut self, loss_fraction: f32) -> u64 {
debug_assert!(
loss_fraction.is_finite(),
"loss_fraction must be finite, got {loss_fraction}"
);
let loss_fraction = loss_fraction.clamp(0.0, 1.0);
if loss_fraction < LOSS_LOW_THRESHOLD {
let increase = (self.bitrate_bps as f64 * AI_FRACTION) as u64;
self.bitrate_bps = (self.bitrate_bps + increase.max(8_000)).min(self.max_bps);
} else if loss_fraction > LOSS_HIGH_THRESHOLD {
self.bitrate_bps = ((self.bitrate_bps as f64 * MD_FACTOR) as u64).max(self.min_bps);
}
self.bitrate_bps
}
pub fn on_overuse(&mut self) -> u64 {
self.bitrate_bps = ((self.bitrate_bps as f64 * MD_FACTOR) as u64).max(self.min_bps);
self.bitrate_bps
}
pub fn current(&self) -> u64 {
self.bitrate_bps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn low_loss_increases_bitrate() {
let mut c = AimdController::new(500_000, 100_000, 2_000_000);
let before = c.current();
c.update_loss(0.001);
assert!(c.current() > before);
}
#[test]
fn high_loss_decreases_bitrate() {
let mut c = AimdController::new(500_000, 100_000, 2_000_000);
let before = c.current();
c.update_loss(0.05);
assert!(c.current() < before);
}
#[test]
fn neutral_loss_holds_bitrate() {
let mut c = AimdController::new(500_000, 100_000, 2_000_000);
let before = c.current();
c.update_loss(0.01);
assert_eq!(c.current(), before);
}
#[test]
fn overuse_decreases_bitrate() {
let mut c = AimdController::new(1_000_000, 100_000, 2_000_000);
let before = c.current();
c.on_overuse();
assert!(c.current() < before);
}
#[test]
fn bitrate_never_exceeds_max() {
let mut c = AimdController::new(1_900_000, 100_000, 2_000_000);
for _ in 0..100 {
c.update_loss(0.0);
}
assert!(c.current() <= 2_000_000);
}
#[test]
fn bitrate_never_goes_below_min() {
let mut c = AimdController::new(200_000, 100_000, 2_000_000);
for _ in 0..100 {
c.on_overuse();
}
assert!(c.current() >= 100_000);
}
}