use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use crate::simulation::{RealTime, TimeSource};
use super::bbr::DeliveryRateToken;
use super::bbr::{BbrConfig, BbrController, BbrStats};
use super::fixed_rate::{FixedRateConfig, FixedRateController};
use super::ledbat::{LedbatConfig, LedbatController, LedbatStats};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum CongestionControlAlgorithm {
Bbr,
Ledbat,
#[default]
FixedRate,
}
impl fmt::Display for CongestionControlAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CongestionControlAlgorithm::Bbr => write!(f, "BBR"),
CongestionControlAlgorithm::Ledbat => write!(f, "LEDBAT++"),
CongestionControlAlgorithm::FixedRate => write!(f, "FixedRate"),
}
}
}
#[derive(Debug, Clone)]
pub struct CongestionControlStats {
pub algorithm: CongestionControlAlgorithm,
pub cwnd: usize,
pub flightsize: usize,
pub queuing_delay: Duration,
pub base_delay: Duration,
pub peak_cwnd: usize,
pub total_losses: usize,
pub total_timeouts: usize,
pub ssthresh: usize,
}
impl CongestionControlStats {
pub fn effective_bandwidth(&self, rtt: Duration) -> usize {
if rtt.is_zero() {
return 0;
}
let rtt_secs = rtt.as_secs_f64();
let bandwidth = self.cwnd as f64 / rtt_secs;
if !bandwidth.is_finite() || bandwidth > usize::MAX as f64 {
usize::MAX
} else if bandwidth < 0.0 {
0
} else {
bandwidth as usize
}
}
}
pub trait CongestionControl: Send + Sync {
fn on_send(&self, bytes: usize);
fn on_ack(&self, rtt_sample: Duration, bytes_acked: usize);
fn on_ack_without_rtt(&self, bytes_acked: usize);
fn on_loss(&self);
fn on_timeout(&self);
fn current_cwnd(&self) -> usize;
fn current_rate(&self, rtt: Duration) -> usize;
fn flightsize(&self) -> usize;
fn base_delay(&self) -> Duration;
fn queuing_delay(&self) -> Duration;
fn peak_cwnd(&self) -> usize;
fn stats(&self) -> CongestionControlStats;
fn algorithm(&self) -> CongestionControlAlgorithm;
}
pub enum CongestionController<T: TimeSource = RealTime> {
Bbr(BbrController<T>),
Ledbat(LedbatController<T>),
FixedRate(FixedRateController<T>),
}
impl<T: TimeSource> fmt::Debug for CongestionController<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bbr(c) => f
.debug_struct("CongestionController::Bbr")
.field("cwnd", &c.current_cwnd())
.field("flightsize", &c.flightsize())
.finish_non_exhaustive(),
Self::Ledbat(c) => f
.debug_struct("CongestionController::Ledbat")
.field("cwnd", &c.current_cwnd())
.field("flightsize", &c.flightsize())
.finish_non_exhaustive(),
Self::FixedRate(c) => f
.debug_struct("CongestionController::FixedRate")
.field("rate", &c.rate())
.field("flightsize", &c.flightsize())
.finish_non_exhaustive(),
}
}
}
impl<T: TimeSource> CongestionControl for CongestionController<T> {
fn on_send(&self, bytes: usize) {
match self {
Self::Bbr(c) => {
c.on_send(bytes);
}
Self::Ledbat(c) => c.on_send(bytes),
Self::FixedRate(c) => c.on_send(bytes),
}
}
fn on_ack(&self, rtt_sample: Duration, bytes_acked: usize) {
match self {
Self::Bbr(c) => c.on_ack(rtt_sample, bytes_acked),
Self::Ledbat(c) => c.on_ack(rtt_sample, bytes_acked),
Self::FixedRate(c) => c.on_ack(rtt_sample, bytes_acked),
}
}
fn on_ack_without_rtt(&self, bytes_acked: usize) {
match self {
Self::Bbr(c) => c.on_ack_without_rtt(bytes_acked),
Self::Ledbat(c) => c.on_ack_without_rtt(bytes_acked),
Self::FixedRate(c) => c.on_ack_without_rtt(bytes_acked),
}
}
fn on_loss(&self) {
match self {
Self::Bbr(c) => c.on_loss(0), Self::Ledbat(c) => c.on_loss(),
Self::FixedRate(c) => c.on_loss(),
}
}
fn on_timeout(&self) {
match self {
Self::Bbr(c) => c.on_timeout(),
Self::Ledbat(c) => c.on_timeout(),
Self::FixedRate(c) => c.on_timeout(),
}
}
fn current_cwnd(&self) -> usize {
match self {
Self::Bbr(c) => c.current_cwnd(),
Self::Ledbat(c) => c.current_cwnd(),
Self::FixedRate(c) => c.current_cwnd(),
}
}
fn current_rate(&self, rtt: Duration) -> usize {
match self {
Self::Bbr(c) => c.current_rate(rtt) as usize,
Self::Ledbat(c) => c.current_rate(rtt),
Self::FixedRate(c) => c.current_rate(rtt),
}
}
fn flightsize(&self) -> usize {
match self {
Self::Bbr(c) => c.flightsize(),
Self::Ledbat(c) => c.flightsize(),
Self::FixedRate(c) => c.flightsize(),
}
}
fn base_delay(&self) -> Duration {
match self {
Self::Bbr(c) => c.base_delay().unwrap_or(Duration::ZERO),
Self::Ledbat(c) => c.base_delay(),
Self::FixedRate(c) => c.base_delay(),
}
}
fn queuing_delay(&self) -> Duration {
match self {
Self::Bbr(c) => c.queuing_delay().unwrap_or(Duration::ZERO),
Self::Ledbat(c) => c.queuing_delay(),
Self::FixedRate(c) => c.queuing_delay(),
}
}
fn peak_cwnd(&self) -> usize {
match self {
Self::Bbr(c) => c.current_cwnd(), Self::Ledbat(c) => c.peak_cwnd(),
Self::FixedRate(c) => c.peak_cwnd(),
}
}
fn stats(&self) -> CongestionControlStats {
match self {
Self::Bbr(c) => {
let s = c.stats();
CongestionControlStats {
algorithm: CongestionControlAlgorithm::Bbr,
cwnd: s.cwnd,
flightsize: s.flightsize,
queuing_delay: Duration::ZERO, base_delay: s.min_rtt.unwrap_or(Duration::ZERO),
peak_cwnd: s.cwnd, total_losses: s.lost as usize,
total_timeouts: s.timeouts as usize,
ssthresh: s.bdp, }
}
Self::Ledbat(c) => {
let s = c.stats();
CongestionControlStats {
algorithm: CongestionControlAlgorithm::Ledbat,
cwnd: s.cwnd,
flightsize: s.flightsize,
queuing_delay: s.queuing_delay,
base_delay: s.base_delay,
peak_cwnd: s.peak_cwnd,
total_losses: s.total_losses,
total_timeouts: s.total_timeouts,
ssthresh: s.ssthresh,
}
}
Self::FixedRate(c) => CongestionControlStats {
algorithm: CongestionControlAlgorithm::FixedRate,
cwnd: c.current_cwnd(),
flightsize: c.flightsize(),
queuing_delay: Duration::ZERO,
base_delay: Duration::ZERO,
peak_cwnd: c.current_cwnd(),
total_losses: 0, total_timeouts: 0,
ssthresh: c.rate(), },
}
}
fn algorithm(&self) -> CongestionControlAlgorithm {
match self {
Self::Bbr(_) => CongestionControlAlgorithm::Bbr,
Self::Ledbat(_) => CongestionControlAlgorithm::Ledbat,
Self::FixedRate(_) => CongestionControlAlgorithm::FixedRate,
}
}
}
impl<T: TimeSource> CongestionControl for BbrController<T> {
fn on_send(&self, bytes: usize) {
BbrController::on_send(self, bytes);
}
fn on_ack(&self, rtt_sample: Duration, bytes_acked: usize) {
BbrController::on_ack(self, rtt_sample, bytes_acked)
}
fn on_ack_without_rtt(&self, bytes_acked: usize) {
BbrController::on_ack_without_rtt(self, bytes_acked)
}
fn on_loss(&self) {
BbrController::on_loss(self, 0)
}
fn on_timeout(&self) {
BbrController::on_timeout(self)
}
fn current_cwnd(&self) -> usize {
BbrController::current_cwnd(self)
}
fn current_rate(&self, rtt: Duration) -> usize {
BbrController::current_rate(self, rtt) as usize
}
fn flightsize(&self) -> usize {
BbrController::flightsize(self)
}
fn base_delay(&self) -> Duration {
BbrController::base_delay(self).unwrap_or(Duration::ZERO)
}
fn queuing_delay(&self) -> Duration {
BbrController::queuing_delay(self).unwrap_or(Duration::ZERO)
}
fn peak_cwnd(&self) -> usize {
BbrController::current_cwnd(self)
}
fn stats(&self) -> CongestionControlStats {
let s = BbrController::stats(self);
CongestionControlStats {
algorithm: CongestionControlAlgorithm::Bbr,
cwnd: s.cwnd,
flightsize: s.flightsize,
queuing_delay: Duration::ZERO,
base_delay: s.min_rtt.unwrap_or(Duration::ZERO),
peak_cwnd: s.cwnd,
total_losses: s.lost as usize,
total_timeouts: s.timeouts as usize,
ssthresh: s.bdp,
}
}
fn algorithm(&self) -> CongestionControlAlgorithm {
CongestionControlAlgorithm::Bbr
}
}
impl<T: TimeSource> CongestionControl for LedbatController<T> {
fn on_send(&self, bytes: usize) {
LedbatController::on_send(self, bytes)
}
fn on_ack(&self, rtt_sample: Duration, bytes_acked: usize) {
LedbatController::on_ack(self, rtt_sample, bytes_acked)
}
fn on_ack_without_rtt(&self, bytes_acked: usize) {
LedbatController::on_ack_without_rtt(self, bytes_acked)
}
fn on_loss(&self) {
LedbatController::on_loss(self)
}
fn on_timeout(&self) {
LedbatController::on_timeout(self)
}
fn current_cwnd(&self) -> usize {
LedbatController::current_cwnd(self)
}
fn current_rate(&self, rtt: Duration) -> usize {
LedbatController::current_rate(self, rtt)
}
fn flightsize(&self) -> usize {
LedbatController::flightsize(self)
}
fn base_delay(&self) -> Duration {
LedbatController::base_delay(self)
}
fn queuing_delay(&self) -> Duration {
LedbatController::queuing_delay(self)
}
fn peak_cwnd(&self) -> usize {
LedbatController::peak_cwnd(self)
}
fn stats(&self) -> CongestionControlStats {
let s = LedbatController::stats(self);
CongestionControlStats {
algorithm: CongestionControlAlgorithm::Ledbat,
cwnd: s.cwnd,
flightsize: s.flightsize,
queuing_delay: s.queuing_delay,
base_delay: s.base_delay,
peak_cwnd: s.peak_cwnd,
total_losses: s.total_losses,
total_timeouts: s.total_timeouts,
ssthresh: s.ssthresh,
}
}
fn algorithm(&self) -> CongestionControlAlgorithm {
CongestionControlAlgorithm::Ledbat
}
}
impl<T: TimeSource> CongestionControl for FixedRateController<T> {
fn on_send(&self, bytes: usize) {
FixedRateController::on_send(self, bytes)
}
fn on_ack(&self, rtt_sample: Duration, bytes_acked: usize) {
FixedRateController::on_ack(self, rtt_sample, bytes_acked)
}
fn on_ack_without_rtt(&self, bytes_acked: usize) {
FixedRateController::on_ack_without_rtt(self, bytes_acked)
}
fn on_loss(&self) {
FixedRateController::on_loss(self)
}
fn on_timeout(&self) {
FixedRateController::on_timeout(self)
}
fn current_cwnd(&self) -> usize {
FixedRateController::current_cwnd(self)
}
fn current_rate(&self, rtt: Duration) -> usize {
FixedRateController::current_rate(self, rtt)
}
fn flightsize(&self) -> usize {
FixedRateController::flightsize(self)
}
fn base_delay(&self) -> Duration {
FixedRateController::base_delay(self)
}
fn queuing_delay(&self) -> Duration {
FixedRateController::queuing_delay(self)
}
fn peak_cwnd(&self) -> usize {
FixedRateController::peak_cwnd(self)
}
fn stats(&self) -> CongestionControlStats {
CongestionControlStats {
algorithm: CongestionControlAlgorithm::FixedRate,
cwnd: self.current_cwnd(),
flightsize: self.flightsize(),
queuing_delay: Duration::ZERO,
base_delay: Duration::ZERO,
peak_cwnd: self.current_cwnd(),
total_losses: 0,
total_timeouts: 0,
ssthresh: self.rate(),
}
}
fn algorithm(&self) -> CongestionControlAlgorithm {
CongestionControlAlgorithm::FixedRate
}
}
impl<T: TimeSource> CongestionController<T> {
pub fn bbr_stats(&self) -> Option<BbrStats> {
match self {
Self::Bbr(c) => Some(c.stats()),
Self::Ledbat(_) | Self::FixedRate(_) => None,
}
}
pub fn ledbat_stats(&self) -> Option<LedbatStats> {
match self {
Self::Bbr(_) | Self::FixedRate(_) => None,
Self::Ledbat(c) => Some(c.stats()),
}
}
pub fn as_bbr(&self) -> Option<&BbrController<T>> {
match self {
Self::Bbr(c) => Some(c),
Self::Ledbat(_) | Self::FixedRate(_) => None,
}
}
pub fn as_ledbat(&self) -> Option<&LedbatController<T>> {
match self {
Self::Bbr(_) | Self::FixedRate(_) => None,
Self::Ledbat(c) => Some(c),
}
}
pub fn as_fixed_rate(&self) -> Option<&FixedRateController<T>> {
match self {
Self::Bbr(_) | Self::Ledbat(_) => None,
Self::FixedRate(c) => Some(c),
}
}
pub fn on_send_with_token(&self, bytes: usize) -> Option<DeliveryRateToken> {
match self {
Self::Bbr(c) => Some(c.on_send(bytes)),
Self::Ledbat(c) => {
c.on_send(bytes);
None
}
Self::FixedRate(c) => {
c.on_send(bytes);
None
}
}
}
pub fn on_ack_with_token(
&self,
rtt_sample: Duration,
bytes_acked: usize,
token: Option<DeliveryRateToken>,
) {
match self {
Self::Bbr(c) => c.on_ack_with_token(rtt_sample, bytes_acked, token),
Self::Ledbat(c) => c.on_ack(rtt_sample, bytes_acked),
Self::FixedRate(c) => c.on_ack(rtt_sample, bytes_acked),
}
}
pub fn configured_rate(&self) -> usize {
match self {
Self::Bbr(_) => 0,
Self::Ledbat(_) => 0,
Self::FixedRate(c) => c.rate(),
}
}
}
#[derive(Debug, Clone)]
pub struct CongestionControlConfig {
pub algorithm: CongestionControlAlgorithm,
pub initial_cwnd: usize,
pub min_cwnd: usize,
pub max_cwnd: usize,
pub ssthresh: usize,
pub min_ssthresh: Option<usize>,
pub algorithm_config: AlgorithmConfig,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AlgorithmConfig {
Bbr {
startup_min_pacing_rate: u64,
},
Ledbat {
enable_slow_start: bool,
delay_exit_threshold: f64,
enable_periodic_slowdown: bool,
randomize_ssthresh: bool,
},
FixedRate {
rate_bytes_per_sec: usize,
},
}
impl Default for CongestionControlConfig {
fn default() -> Self {
use super::fixed_rate::DEFAULT_RATE_BYTES_PER_SEC;
Self {
algorithm: CongestionControlAlgorithm::FixedRate,
initial_cwnd: usize::MAX / 2, min_cwnd: usize::MAX / 2,
max_cwnd: usize::MAX / 2,
ssthresh: DEFAULT_RATE_BYTES_PER_SEC,
min_ssthresh: None,
algorithm_config: AlgorithmConfig::FixedRate {
rate_bytes_per_sec: DEFAULT_RATE_BYTES_PER_SEC,
},
}
}
}
impl CongestionControlConfig {
pub fn new(algorithm: CongestionControlAlgorithm) -> Self {
match algorithm {
CongestionControlAlgorithm::Bbr => Self::from_bbr_config(BbrConfig::default()),
CongestionControlAlgorithm::Ledbat => Self::from_ledbat_config(LedbatConfig::default()),
CongestionControlAlgorithm::FixedRate => Self::default(),
}
}
pub fn fixed_rate(rate_bytes_per_sec: usize) -> Self {
Self::from_fixed_rate_config(FixedRateConfig::new(rate_bytes_per_sec))
}
pub fn fixed_rate_mbps(mbps: usize) -> Self {
Self::from_fixed_rate_config(FixedRateConfig::from_mbps(mbps))
}
pub fn from_fixed_rate_config(config: FixedRateConfig) -> Self {
Self {
algorithm: CongestionControlAlgorithm::FixedRate,
initial_cwnd: usize::MAX / 2,
min_cwnd: usize::MAX / 2,
max_cwnd: usize::MAX / 2,
ssthresh: config.rate_bytes_per_sec,
min_ssthresh: None,
algorithm_config: AlgorithmConfig::FixedRate {
rate_bytes_per_sec: config.rate_bytes_per_sec,
},
}
}
pub fn from_ledbat_config(config: LedbatConfig) -> Self {
Self {
algorithm: CongestionControlAlgorithm::Ledbat,
initial_cwnd: config.initial_cwnd,
min_cwnd: config.min_cwnd,
max_cwnd: config.max_cwnd,
ssthresh: config.ssthresh,
min_ssthresh: config.min_ssthresh,
algorithm_config: AlgorithmConfig::Ledbat {
enable_slow_start: config.enable_slow_start,
delay_exit_threshold: config.delay_exit_threshold,
enable_periodic_slowdown: config.enable_periodic_slowdown,
randomize_ssthresh: config.randomize_ssthresh,
},
}
}
pub fn from_bbr_config(config: BbrConfig) -> Self {
Self {
algorithm: CongestionControlAlgorithm::Bbr,
initial_cwnd: config.initial_cwnd,
min_cwnd: config.min_cwnd,
max_cwnd: config.max_cwnd,
ssthresh: 1_000_000,
min_ssthresh: None,
algorithm_config: AlgorithmConfig::Bbr {
startup_min_pacing_rate: config.startup_min_pacing_rate,
},
}
}
pub fn with_initial_cwnd(mut self, cwnd: usize) -> Self {
self.initial_cwnd = cwnd;
self
}
pub fn with_min_cwnd(mut self, cwnd: usize) -> Self {
self.min_cwnd = cwnd;
self
}
pub fn with_max_cwnd(mut self, cwnd: usize) -> Self {
self.max_cwnd = cwnd;
self
}
pub fn with_ssthresh(mut self, ssthresh: usize) -> Self {
self.ssthresh = ssthresh;
self
}
pub fn with_min_ssthresh(mut self, min_ssthresh: Option<usize>) -> Self {
self.min_ssthresh = min_ssthresh;
self
}
pub fn with_startup_min_pacing_rate(mut self, rate: u64) -> Self {
if let AlgorithmConfig::Bbr {
ref mut startup_min_pacing_rate,
} = self.algorithm_config
{
*startup_min_pacing_rate = rate;
}
self
}
pub fn build(&self) -> CongestionController<RealTime> {
self.build_with_time_source(RealTime::new())
}
pub fn build_arc(&self) -> Arc<CongestionController<RealTime>> {
Arc::new(self.build())
}
pub fn build_with_time_source<T: TimeSource>(&self, time_source: T) -> CongestionController<T> {
self.build_inner(time_source)
}
pub fn build_arc_with_time_source<T: TimeSource>(
&self,
time_source: T,
) -> Arc<CongestionController<T>> {
Arc::new(self.build_with_time_source(time_source))
}
fn build_inner<T: TimeSource>(&self, time_source: T) -> CongestionController<T> {
match self.algorithm {
CongestionControlAlgorithm::Bbr => {
let startup_min_pacing_rate = match &self.algorithm_config {
AlgorithmConfig::Bbr {
startup_min_pacing_rate,
} => *startup_min_pacing_rate,
AlgorithmConfig::Ledbat { .. } | AlgorithmConfig::FixedRate { .. } => {
BbrConfig::default().startup_min_pacing_rate
}
};
let bbr_config = BbrConfig {
initial_cwnd: self.initial_cwnd,
min_cwnd: self.min_cwnd,
max_cwnd: self.max_cwnd,
startup_min_pacing_rate,
..Default::default()
};
CongestionController::Bbr(BbrController::new_with_time_source(
bbr_config,
time_source,
))
}
CongestionControlAlgorithm::Ledbat => {
let AlgorithmConfig::Ledbat {
enable_slow_start,
delay_exit_threshold,
enable_periodic_slowdown,
randomize_ssthresh,
} = &self.algorithm_config
else {
let ledbat_config = LedbatConfig {
initial_cwnd: self.initial_cwnd,
min_cwnd: self.min_cwnd,
max_cwnd: self.max_cwnd,
ssthresh: self.ssthresh,
min_ssthresh: self.min_ssthresh,
..Default::default()
};
return CongestionController::Ledbat(LedbatController::new_with_time_source(
ledbat_config,
time_source,
));
};
let ledbat_config = LedbatConfig {
initial_cwnd: self.initial_cwnd,
min_cwnd: self.min_cwnd,
max_cwnd: self.max_cwnd,
ssthresh: self.ssthresh,
min_ssthresh: self.min_ssthresh,
enable_slow_start: *enable_slow_start,
delay_exit_threshold: *delay_exit_threshold,
enable_periodic_slowdown: *enable_periodic_slowdown,
randomize_ssthresh: *randomize_ssthresh,
};
CongestionController::Ledbat(LedbatController::new_with_time_source(
ledbat_config,
time_source,
))
}
CongestionControlAlgorithm::FixedRate => {
let rate = match &self.algorithm_config {
AlgorithmConfig::FixedRate { rate_bytes_per_sec } => *rate_bytes_per_sec,
AlgorithmConfig::Bbr { .. } | AlgorithmConfig::Ledbat { .. } => {
super::fixed_rate::DEFAULT_RATE_BYTES_PER_SEC
}
};
let fixed_rate_config = FixedRateConfig::new(rate);
CongestionController::FixedRate(FixedRateController::new_with_time_source(
fixed_rate_config,
time_source,
))
}
}
}
pub fn as_ledbat_config(&self) -> Option<LedbatConfig> {
match &self.algorithm_config {
AlgorithmConfig::Bbr { .. } | AlgorithmConfig::FixedRate { .. } => None,
AlgorithmConfig::Ledbat {
enable_slow_start,
delay_exit_threshold,
enable_periodic_slowdown,
randomize_ssthresh,
} => Some(LedbatConfig {
initial_cwnd: self.initial_cwnd,
min_cwnd: self.min_cwnd,
max_cwnd: self.max_cwnd,
ssthresh: self.ssthresh,
min_ssthresh: self.min_ssthresh,
enable_slow_start: *enable_slow_start,
delay_exit_threshold: *delay_exit_threshold,
enable_periodic_slowdown: *enable_periodic_slowdown,
randomize_ssthresh: *randomize_ssthresh,
}),
}
}
pub fn as_bbr_config(&self) -> Option<BbrConfig> {
match &self.algorithm_config {
AlgorithmConfig::Bbr {
startup_min_pacing_rate,
} => Some(BbrConfig {
initial_cwnd: self.initial_cwnd,
min_cwnd: self.min_cwnd,
max_cwnd: self.max_cwnd,
startup_min_pacing_rate: *startup_min_pacing_rate,
..Default::default()
}),
AlgorithmConfig::Ledbat { .. } | AlgorithmConfig::FixedRate { .. } => None,
}
}
pub fn as_fixed_rate_config(&self) -> Option<FixedRateConfig> {
match &self.algorithm_config {
AlgorithmConfig::FixedRate { rate_bytes_per_sec } => {
Some(FixedRateConfig::new(*rate_bytes_per_sec))
}
AlgorithmConfig::Bbr { .. } | AlgorithmConfig::Ledbat { .. } => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::simulation::VirtualTime;
#[test]
fn test_default_config_creates_fixed_rate() {
let config = CongestionControlConfig::default();
assert_eq!(config.algorithm, CongestionControlAlgorithm::FixedRate);
}
#[test]
fn test_build_fixed_rate_controller() {
let config = CongestionControlConfig::default();
let controller = config.build();
assert_eq!(
controller.algorithm(),
CongestionControlAlgorithm::FixedRate
);
assert!(controller.current_cwnd() > 0);
assert_eq!(controller.flightsize(), 0);
}
#[test]
fn test_build_bbr_controller() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Bbr);
let controller = config.build();
assert_eq!(controller.algorithm(), CongestionControlAlgorithm::Bbr);
assert!(controller.current_cwnd() > 0);
assert_eq!(controller.flightsize(), 0);
}
#[test]
fn test_build_ledbat_controller() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Ledbat);
let controller = config.build();
assert_eq!(controller.algorithm(), CongestionControlAlgorithm::Ledbat);
assert!(controller.current_cwnd() > 0);
}
#[test]
fn test_build_arc() {
let config = CongestionControlConfig::default();
let controller = config.build_arc();
let _clone = controller.clone();
assert_eq!(
controller.algorithm(),
CongestionControlAlgorithm::FixedRate
);
}
#[test]
fn test_build_with_virtual_time() {
let config = CongestionControlConfig::default();
let time_source = VirtualTime::new();
let controller = config.build_with_time_source(time_source);
assert_eq!(
controller.algorithm(),
CongestionControlAlgorithm::FixedRate
);
}
#[test]
fn test_basic_send_ack_flow() {
let config = CongestionControlConfig::default();
let controller = config.build();
controller.on_send(1000);
assert_eq!(controller.flightsize(), 1000);
controller.on_ack(Duration::from_millis(50), 1000);
assert_eq!(controller.flightsize(), 0);
}
#[test]
fn test_stats_conversion() {
let config = CongestionControlConfig::default();
let controller = config.build();
let stats = controller.stats();
assert_eq!(stats.algorithm, CongestionControlAlgorithm::FixedRate);
assert!(stats.cwnd > 0);
}
#[test]
fn test_fixed_rate_specific_stats_access() {
let config = CongestionControlConfig::default();
let controller = config.build();
let fixed_rate = controller.as_fixed_rate();
assert!(fixed_rate.is_some());
match &controller {
CongestionController::FixedRate(fr) => {
assert_eq!(
fr.rate(),
super::super::fixed_rate::DEFAULT_RATE_BYTES_PER_SEC
);
}
CongestionController::Bbr(_) | CongestionController::Ledbat(_) => {
panic!("Expected FixedRate")
}
}
}
#[test]
fn test_bbr_specific_stats_access() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Bbr);
let controller = config.build();
let bbr_stats = controller.bbr_stats();
assert!(bbr_stats.is_some());
match &controller {
CongestionController::Bbr(bbr) => {
let stats = bbr.stats();
assert!(stats.cwnd > 0);
}
CongestionController::Ledbat(_) | CongestionController::FixedRate(_) => {
panic!("Expected BBR")
}
}
}
#[test]
fn test_ledbat_specific_stats_access() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Ledbat);
let controller = config.build();
let ledbat_stats = controller.ledbat_stats();
assert!(ledbat_stats.is_some());
match &controller {
CongestionController::Ledbat(ledbat) => {
let stats = ledbat.stats();
assert!(stats.cwnd > 0);
assert_eq!(stats.periodic_slowdowns, 0);
}
CongestionController::Bbr(_) | CongestionController::FixedRate(_) => {
panic!("Expected LEDBAT")
}
}
}
#[test]
fn test_as_bbr_accessor() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Bbr);
let controller = config.build();
let bbr = controller.as_bbr();
assert!(bbr.is_some());
assert!(bbr.unwrap().current_cwnd() > 0);
}
#[test]
fn test_as_ledbat_accessor() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Ledbat);
let controller = config.build();
let ledbat = controller.as_ledbat();
assert!(ledbat.is_some());
assert!(ledbat.unwrap().current_cwnd() > 0);
}
#[test]
fn test_as_fixed_rate_accessor() {
let config = CongestionControlConfig::default();
let controller = config.build();
let fixed_rate = controller.as_fixed_rate();
assert!(fixed_rate.is_some());
assert_eq!(
fixed_rate.unwrap().rate(),
super::super::fixed_rate::DEFAULT_RATE_BYTES_PER_SEC
);
}
#[test]
fn test_fixed_rate_mbps_constructor() {
let config = CongestionControlConfig::fixed_rate_mbps(50);
let controller = config.build();
assert_eq!(
controller.algorithm(),
CongestionControlAlgorithm::FixedRate
);
let fixed_rate = controller.as_fixed_rate().unwrap();
assert_eq!(fixed_rate.rate(), 50 * 1_000_000 / 8); }
#[test]
fn test_bbr_controller_implements_trait() {
let bbr = BbrController::new(BbrConfig::default());
let controller: &dyn CongestionControl = &bbr;
assert_eq!(controller.algorithm(), CongestionControlAlgorithm::Bbr);
assert!(controller.current_cwnd() > 0);
controller.on_send(1000);
assert_eq!(controller.flightsize(), 1000);
}
#[test]
fn test_ledbat_controller_implements_trait() {
let ledbat = LedbatController::new(10_000, 2_000, 1_000_000);
let controller: &dyn CongestionControl = &ledbat;
assert_eq!(controller.algorithm(), CongestionControlAlgorithm::Ledbat);
assert_eq!(controller.current_cwnd(), 10_000);
controller.on_send(1000);
assert_eq!(controller.flightsize(), 1000);
}
#[test]
fn test_from_ledbat_config() {
let ledbat_config = LedbatConfig {
initial_cwnd: 50_000,
min_cwnd: 5_000,
max_cwnd: 500_000,
ssthresh: 100_000,
min_ssthresh: Some(25_000),
enable_slow_start: false,
delay_exit_threshold: 0.5,
enable_periodic_slowdown: false,
randomize_ssthresh: false,
};
let config = CongestionControlConfig::from_ledbat_config(ledbat_config);
let controller = config.build();
assert_eq!(controller.current_cwnd(), 50_000);
assert_eq!(controller.algorithm(), CongestionControlAlgorithm::Ledbat);
}
#[test]
fn test_from_bbr_config() {
let bbr_config = BbrConfig {
initial_cwnd: 60_000,
min_cwnd: 3_000,
max_cwnd: 2_000_000,
..Default::default()
};
let config = CongestionControlConfig::from_bbr_config(bbr_config);
let controller = config.build();
assert_eq!(controller.current_cwnd(), 60_000);
assert_eq!(controller.algorithm(), CongestionControlAlgorithm::Bbr);
}
#[test]
fn test_builder_methods() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Bbr)
.with_initial_cwnd(60_000)
.with_min_cwnd(3_000)
.with_max_cwnd(2_000_000)
.with_ssthresh(500_000)
.with_min_ssthresh(Some(50_000));
assert_eq!(config.initial_cwnd, 60_000);
assert_eq!(config.min_cwnd, 3_000);
assert_eq!(config.max_cwnd, 2_000_000);
assert_eq!(config.ssthresh, 500_000);
assert_eq!(config.min_ssthresh, Some(50_000));
let controller = config.build();
assert_eq!(controller.current_cwnd(), 60_000);
}
#[test]
fn test_algorithm_display() {
assert_eq!(format!("{}", CongestionControlAlgorithm::Bbr), "BBR");
assert_eq!(
format!("{}", CongestionControlAlgorithm::Ledbat),
"LEDBAT++"
);
}
#[test]
fn test_effective_bandwidth() {
let stats = CongestionControlStats {
algorithm: CongestionControlAlgorithm::Bbr,
cwnd: 100_000,
flightsize: 0,
queuing_delay: Duration::ZERO,
base_delay: Duration::ZERO,
peak_cwnd: 100_000,
total_losses: 0,
total_timeouts: 0,
ssthresh: 100_000,
};
let bandwidth = stats.effective_bandwidth(Duration::from_millis(100));
assert_eq!(bandwidth, 1_000_000);
let bandwidth_zero = stats.effective_bandwidth(Duration::ZERO);
assert_eq!(bandwidth_zero, 0);
}
#[test]
fn test_effective_bandwidth_overflow_protection() {
let stats = CongestionControlStats {
algorithm: CongestionControlAlgorithm::Bbr,
cwnd: usize::MAX,
flightsize: 0,
queuing_delay: Duration::ZERO,
base_delay: Duration::ZERO,
peak_cwnd: usize::MAX,
total_losses: 0,
total_timeouts: 0,
ssthresh: usize::MAX,
};
let bandwidth = stats.effective_bandwidth(Duration::from_nanos(1));
assert_eq!(bandwidth, usize::MAX);
}
#[test]
fn test_on_loss_reduces_cwnd() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Ledbat);
let controller = config.build();
let initial_cwnd = controller.current_cwnd();
controller.on_loss();
let after_loss_cwnd = controller.current_cwnd();
assert!(after_loss_cwnd < initial_cwnd);
}
#[test]
fn test_on_timeout_reduces_cwnd() {
let config = CongestionControlConfig::new(CongestionControlAlgorithm::Ledbat);
let controller = config.build();
let initial_cwnd = controller.current_cwnd();
controller.on_timeout();
let after_timeout_cwnd = controller.current_cwnd();
assert!(after_timeout_cwnd < initial_cwnd);
}
#[test]
fn test_effective_bandwidth_handles_nan() {
let stats = CongestionControlStats {
algorithm: CongestionControlAlgorithm::Bbr,
cwnd: 0,
flightsize: 0,
queuing_delay: Duration::ZERO,
base_delay: Duration::ZERO,
peak_cwnd: 0,
total_losses: 0,
total_timeouts: 0,
ssthresh: 0,
};
let bandwidth = stats.effective_bandwidth(Duration::from_nanos(1));
assert_eq!(bandwidth, 0); }
#[test]
fn test_bbr_vs_ledbat_timeout_recovery() {
let time = VirtualTime::new();
let bbr_config = CongestionControlConfig::new(CongestionControlAlgorithm::Bbr);
let bbr = bbr_config.build_with_time_source(time.clone());
let ledbat_config = CongestionControlConfig::new(CongestionControlAlgorithm::Ledbat);
let ledbat = ledbat_config.build_with_time_source(time.clone());
let bbr_initial = BbrConfig::default().initial_cwnd;
for _ in 0..50 {
for _ in 0..20 {
bbr.on_send(1400);
ledbat.on_send(1400);
}
time.advance(Duration::from_millis(50));
for _ in 0..20 {
bbr.on_ack(Duration::from_millis(50), 1400);
ledbat.on_ack(Duration::from_millis(50), 1400);
}
}
let ledbat_cwnd_after_warmup = ledbat.current_cwnd();
assert!(
ledbat_cwnd_after_warmup > 100_000,
"LEDBAT cwnd ({}) should have grown significantly during warmup",
ledbat_cwnd_after_warmup
);
for _ in 0..10 {
bbr.on_timeout();
ledbat.on_timeout();
assert_eq!(
bbr.current_cwnd(),
bbr_initial,
"BBR should reset to initial_cwnd on every timeout"
);
}
assert_eq!(
bbr.current_cwnd(),
bbr_initial,
"BBR remains stuck at initial_cwnd after timeout storm"
);
assert!(
ledbat.current_cwnd() > bbr.current_cwnd(),
"LEDBAT should maintain higher cwnd than BBR after timeout storm"
);
}
}