use crate::algorithms::{CongestionControlAlgorithm, Bbr, Cubic};
use crate::constants::*;
use crate::events::CongestionEvent;
use std::time::{Duration, Instant};
pub trait NetworkMetrics {
fn on_packet_sent(&mut self, bytes: u32);
fn on_packet_acked(&mut self, bytes: u32, rtt: Duration);
fn on_packet_lost(&mut self, bytes: u32);
fn rtt(&self) -> Duration;
fn min_rtt(&self) -> Duration;
fn bandwidth(&self) -> u64;
fn loss_rate(&self) -> f64;
fn bw_ratio(&self) -> f64;
fn set_bw_ratio(&mut self, ratio: f64);
fn rtt_cv(&self) -> f64;
fn set_rtt_cv(&mut self, cv: f64);
fn has_variable_rtt(&self) -> bool;
fn has_low_bandwidth_utilization(&self) -> bool;
fn has_high_loss(&self) -> bool;
fn update(&mut self, rtt_us: Duration, loss_rate: f64, throughput: u64, cwnd: u32);
fn reset(&mut self);
}
pub trait MetricsWindow {
fn add_sample(&mut self, rtt: Duration, loss_rate: f64, bandwidth: u64);
fn avg_rtt(&self) -> Duration;
fn avg_bandwidth(&self) -> u64;
fn avg_loss_rate(&self) -> f64;
fn rtt_coefficient_of_variation(&self) -> Option<f64>;
fn is_stable(&self) -> bool;
fn clear(&mut self);
}
#[derive(Debug, Clone)]
pub struct CongestionConfig {
pub auto_switch: bool,
pub switch_cooldown: Duration,
pub metrics_window_size: usize,
pub platform_optimized: bool,
}
impl Default for CongestionConfig {
fn default() -> Self {
Self {
auto_switch: true,
switch_cooldown: perf::SWITCH_COOLDOWN,
metrics_window_size: 32,
platform_optimized: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlgorithmType {
Bbr,
Cubic,
Auto,
}
pub struct CongestionManager<M: NetworkMetrics, W: MetricsWindow> {
current_algorithm: Box<dyn CongestionControlAlgorithm>,
config: CongestionConfig,
metrics: M,
metrics_window: W,
last_switch: Instant,
packets_sent: u64,
packets_lost: u64,
bytes_sent: u64,
throughput_start: Instant,
}
impl<M: NetworkMetrics, W: MetricsWindow> CongestionManager<M, W> {
pub fn new(
initial_algorithm: Box<dyn CongestionControlAlgorithm>,
metrics: M,
metrics_window: W,
) -> Self {
Self::with_config(initial_algorithm, CongestionConfig::default(), metrics, metrics_window)
}
pub fn with_config(
initial_algorithm: Box<dyn CongestionControlAlgorithm>,
config: CongestionConfig,
metrics: M,
metrics_window: W,
) -> Self {
let now = Instant::now();
Self {
current_algorithm: initial_algorithm,
config,
metrics,
metrics_window,
last_switch: now,
packets_sent: 0,
packets_lost: 0,
bytes_sent: 0,
throughput_start: now,
}
}
pub fn with_bbr(metrics: M, metrics_window: W) -> Self {
Self::new(Box::new(Bbr::new()), metrics, metrics_window)
}
pub fn with_cubic(metrics: M, metrics_window: W) -> Self {
Self::new(Box::new(Cubic::new()), metrics, metrics_window)
}
pub fn with_auto(metrics: M, metrics_window: W) -> Self {
let mut manager = Self::new(Box::new(Bbr::new()), metrics, metrics_window);
manager.config.auto_switch = true;
manager
}
pub fn switch_algorithm(&mut self, new_algorithm: Box<dyn CongestionControlAlgorithm>) {
self.current_algorithm = new_algorithm;
self.last_switch = Instant::now();
}
pub fn switch_to(&mut self, algorithm_type: AlgorithmType) {
let new_algorithm: Box<dyn CongestionControlAlgorithm> = match algorithm_type {
AlgorithmType::Bbr => Box::new(Bbr::new()),
AlgorithmType::Cubic => Box::new(Cubic::new()),
AlgorithmType::Auto => {
if self.should_use_bbr() {
Box::new(Bbr::new())
} else {
Box::new(Cubic::new())
}
}
};
self.switch_algorithm(new_algorithm);
}
pub fn smart_switch_decision(&mut self) {
if !self.config.auto_switch {
return;
}
if Instant::now() - self.last_switch < self.config.switch_cooldown {
return;
}
let should_use_bbr = self.should_use_bbr();
let current_is_bbr = self.current_algorithm.algorithm_name() == "BBR";
if should_use_bbr && !current_is_bbr {
self.switch_algorithm(Box::new(Bbr::new()));
} else if !should_use_bbr && current_is_bbr {
self.switch_algorithm(Box::new(Cubic::new()));
}
}
fn should_use_bbr(&self) -> bool {
if self.metrics.has_high_loss() {
return true;
}
if self.metrics.has_variable_rtt() && self.metrics.has_low_bandwidth_utilization() {
return true;
}
false
}
#[inline(always)]
pub fn on_congestion_event(&mut self, event: CongestionEvent) {
match &event {
CongestionEvent::PacketAcked { bytes, rtt, .. } => {
self.packets_sent += 1;
self.bytes_sent += bytes;
self.update_metrics_with_rtt(*rtt);
},
CongestionEvent::PacketLost { bytes, .. } => {
self.packets_lost += 1;
self.bytes_sent += bytes;
self.update_loss_rate();
},
CongestionEvent::ExplicitCongestionNotification => {
}
}
self.current_algorithm.on_congestion_event(event);
self.update_metrics_window();
self.smart_switch_decision();
}
fn update_metrics_with_rtt(&mut self, rtt: Duration) {
let throughput = self.calculate_throughput();
self.metrics.update(
rtt,
self.calculate_loss_rate(),
throughput,
self.current_algorithm.congestion_window() as u32,
);
if let Some(cv) = self.metrics_window.rtt_coefficient_of_variation() {
self.metrics.set_rtt_cv(cv);
}
let pacing_rate = self.current_algorithm.pacing_rate();
if pacing_rate > 0 {
let new_ratio = ((throughput as f64) / (pacing_rate as f64)).min(1.0);
self.metrics.set_bw_ratio(new_ratio);
}
}
fn update_loss_rate(&mut self) {
let total_packets = self.packets_sent + self.packets_lost;
if total_packets > 0 {
let loss_rate = self.packets_lost as f64 / total_packets as f64;
self.metrics.update(
self.metrics.rtt(),
loss_rate,
self.calculate_throughput(),
self.current_algorithm.congestion_window() as u32,
);
}
}
fn calculate_loss_rate(&self) -> f64 {
let total_packets = self.packets_sent + self.packets_lost;
if total_packets > 0 {
self.packets_lost as f64 / total_packets as f64
} else {
0.0
}
}
fn calculate_throughput(&self) -> u64 {
let elapsed = Instant::now().duration_since(self.throughput_start);
if elapsed.as_secs_f64() > 0.0 {
((self.bytes_sent * 8) as f64 / elapsed.as_secs_f64()) as u64
} else {
0
}
}
fn update_metrics_window(&mut self) {
self.metrics_window.add_sample(
self.metrics.rtt(),
self.metrics.loss_rate(),
self.metrics.bandwidth(),
);
}
pub fn network_metrics(&self) -> &M {
&self.metrics
}
pub fn metrics_window(&self) -> &W {
&self.metrics_window
}
pub fn reset_statistics(&mut self) {
self.packets_sent = 0;
self.packets_lost = 0;
self.bytes_sent = 0;
self.throughput_start = Instant::now();
self.metrics.reset();
self.metrics_window.clear();
}
pub fn config(&self) -> &CongestionConfig {
&self.config
}
pub fn set_config(&mut self, config: CongestionConfig) {
self.config = config;
}
#[inline(always)]
pub fn congestion_window(&self) -> u64 {
self.current_algorithm.congestion_window()
}
#[inline(always)]
pub fn can_send(&self, bytes_in_flight: u64) -> bool {
self.current_algorithm.can_send(bytes_in_flight)
}
#[inline(always)]
pub fn pacing_rate(&self) -> u64 {
self.current_algorithm.pacing_rate()
}
pub fn algorithm_name(&self) -> &'static str {
self.current_algorithm.algorithm_name()
}
pub fn ssthresh(&self) -> u64 {
self.current_algorithm.ssthresh()
}
pub fn process_ack_batch(&mut self, acks: &[(u64, u64, Duration)]) {
for &(_, bytes, rtt) in acks {
self.packets_sent += 1;
self.bytes_sent += bytes;
}
if let Some(&(_, _, rtt)) = acks.last() {
self.update_metrics_with_rtt(rtt);
}
self.current_algorithm.process_ack_batch(acks);
self.update_metrics_window();
self.smart_switch_decision();
}
}