pub use self::aimd::AimdController;
pub use self::trendline::{BandwidthState, TrendlineDetector};
mod aimd;
mod trendline;
pub const GOOGCC_INITIAL_BPS: u64 = 500_000;
pub const GOOGCC_MIN_BPS: u64 = 100_000;
pub const GOOGCC_MAX_BPS: u64 = 2_500_000;
#[derive(Debug, Clone)]
pub struct GoogCcEstimator {
trendline: TrendlineDetector,
aimd: AimdController,
last_arrival_ms: Option<f64>,
last_send_ms: Option<f64>,
}
impl GoogCcEstimator {
pub fn new() -> Self {
Self::with_bounds(GOOGCC_INITIAL_BPS, GOOGCC_MIN_BPS, GOOGCC_MAX_BPS)
}
pub fn with_bounds(initial_bps: u64, min_bps: u64, max_bps: u64) -> Self {
Self {
trendline: TrendlineDetector::new(),
aimd: AimdController::new(initial_bps, min_bps, max_bps),
last_arrival_ms: None,
last_send_ms: None,
}
}
pub fn on_receive(&mut self, arrival_ms: f64, send_ms: f64, loss_fraction: f32) -> u64 {
if let (Some(la), Some(ls)) = (self.last_arrival_ms, self.last_send_ms) {
let arr_delta = arrival_ms - la;
let snd_delta = send_ms - ls;
self.trendline.update(arr_delta, snd_delta);
if self.trendline.overuse() {
self.last_arrival_ms = Some(arrival_ms);
self.last_send_ms = Some(send_ms);
return self.aimd.on_overuse();
}
}
self.last_arrival_ms = Some(arrival_ms);
self.last_send_ms = Some(send_ms);
self.aimd.update_loss(loss_fraction)
}
pub fn current_bps(&self) -> u64 {
self.aimd.current()
}
#[cfg(any(test, feature = "test-utils"))]
#[doc(hidden)]
pub fn force_bps_for_tests(&mut self, bps: u64) {
self.aimd = AimdController::new(bps, GOOGCC_MIN_BPS, GOOGCC_MAX_BPS);
}
}
impl Default for GoogCcEstimator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn initial_bitrate_is_default() {
let est = GoogCcEstimator::new();
assert_eq!(est.current_bps(), GOOGCC_INITIAL_BPS);
}
#[test]
fn low_loss_raises_bitrate() {
let mut est = GoogCcEstimator::new();
for i in 0u32..30 {
let t = 20.0 * (1.0 + i as f64);
est.on_receive(t, t, 0.001);
}
assert!(
est.current_bps() > GOOGCC_INITIAL_BPS,
"low-loss stable link should increase bitrate"
);
}
#[test]
fn overuse_signal_decreases_bitrate() {
let mut est = GoogCcEstimator::new();
for i in 0u32..30 {
let t = 20.0 * (1.0 + i as f64);
est.on_receive(t, t, 0.001);
}
let high_bps = est.current_bps();
for i in 0u32..30 {
let base = 20.0 * (31.0 + i as f64);
est.on_receive(base + i as f64 * 50.0, base, 0.001);
}
assert!(
est.current_bps() < high_bps,
"overuse should reduce bitrate"
);
}
#[test]
fn high_loss_keeps_bitrate_low() {
let mut est = GoogCcEstimator::new();
for i in 0u32..10 {
let t = 20.0 * (1.0 + i as f64);
est.on_receive(t, t, 0.05); }
assert!(
est.current_bps() <= GOOGCC_INITIAL_BPS,
"high loss should not increase bitrate"
);
}
#[test]
fn with_bounds_respects_min_max() {
let mut est = GoogCcEstimator::with_bounds(200_000, 100_000, 300_000);
for i in 0u32..100 {
let t = 20.0 * (1.0 + i as f64);
est.on_receive(t, t, 0.0);
}
assert!(est.current_bps() <= 300_000, "must not exceed max_bps");
}
}