use std::any::Any;
use std::sync::Arc;
use std::time::{Duration, Instant};
use super::{Controller, ControllerFactory};
use crate::connection::RttEstimator;
use std::cmp;
const BETA_CUBIC: f64 = 0.7;
const C: f64 = 0.4;
#[derive(Debug, Default, Clone)]
pub struct State {
k: f64,
w_max: f64,
cwnd_inc: u64,
}
impl State {
fn cubic_k(&self, max_datagram_size: u64) -> f64 {
let w_max = self.w_max / max_datagram_size as f64;
(w_max * (1.0 - BETA_CUBIC) / C).cbrt()
}
fn w_cubic(&self, t: Duration, max_datagram_size: u64) -> f64 {
let w_max = self.w_max / max_datagram_size as f64;
(C * (t.as_secs_f64() - self.k).powi(3) + w_max) * max_datagram_size as f64
}
fn w_est(&self, t: Duration, rtt: Duration, max_datagram_size: u64) -> f64 {
let w_max = self.w_max / max_datagram_size as f64;
(w_max * BETA_CUBIC
+ 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC) * t.as_secs_f64() / rtt.as_secs_f64())
* max_datagram_size as f64
}
}
#[derive(Debug, Clone)]
pub struct Cubic {
config: Arc<CubicConfig>,
window: u64,
ssthresh: u64,
recovery_start_time: Option<Instant>,
cubic_state: State,
}
impl Cubic {
pub fn new(config: Arc<CubicConfig>, _now: Instant) -> Self {
Self {
window: config.initial_window,
ssthresh: u64::MAX,
recovery_start_time: None,
config,
cubic_state: Default::default(),
}
}
}
impl Controller for Cubic {
fn on_ack(
&mut self,
now: Instant,
sent: Instant,
bytes: u64,
app_limited: bool,
rtt: &RttEstimator,
) {
if app_limited
|| self
.recovery_start_time
.map(|recovery_start_time| sent <= recovery_start_time)
.unwrap_or(false)
{
return;
}
if self.window < self.ssthresh {
self.window += bytes;
} else {
let ca_start_time;
match self.recovery_start_time {
Some(t) => ca_start_time = t,
None => {
ca_start_time = now;
self.recovery_start_time = Some(now);
self.cubic_state.w_max = self.window as f64;
self.cubic_state.k = 0.0;
}
}
let t = now - ca_start_time;
let w_cubic = self
.cubic_state
.w_cubic(t + rtt.get(), self.config.max_datagram_size);
let w_est = self
.cubic_state
.w_est(t, rtt.get(), self.config.max_datagram_size);
let mut cubic_cwnd = self.window;
if w_cubic < w_est {
cubic_cwnd = cmp::max(cubic_cwnd, w_est as u64);
} else if cubic_cwnd < w_cubic as u64 {
let cubic_inc = (w_cubic - cubic_cwnd as f64) / cubic_cwnd as f64
* self.config.max_datagram_size as f64;
cubic_cwnd += cubic_inc as u64;
}
self.cubic_state.cwnd_inc += cubic_cwnd - self.window;
if self.cubic_state.cwnd_inc as u64 >= self.config.max_datagram_size {
self.window += self.config.max_datagram_size;
self.cubic_state.cwnd_inc = 0;
}
}
}
fn on_congestion_event(
&mut self,
now: Instant,
sent: Instant,
is_persistent_congestion: bool,
_lost_bytes: u64,
) {
if self
.recovery_start_time
.map(|recovery_start_time| sent <= recovery_start_time)
.unwrap_or(false)
{
return;
}
self.recovery_start_time = Some(now);
#[allow(clippy::branches_sharing_code)]
if (self.window as f64) < self.cubic_state.w_max {
self.cubic_state.w_max = self.window as f64 * (1.0 + BETA_CUBIC) / 2.0;
} else {
self.cubic_state.w_max = self.window as f64;
}
self.ssthresh = cmp::max(
(self.cubic_state.w_max * BETA_CUBIC) as u64,
self.config.minimum_window,
);
self.window = self.ssthresh;
self.cubic_state.k = self.cubic_state.cubic_k(self.config.max_datagram_size);
self.cubic_state.cwnd_inc = (self.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as u64;
if is_persistent_congestion {
self.recovery_start_time = None;
self.cubic_state.w_max = self.window as f64;
self.ssthresh = cmp::max(
(self.window as f64 * BETA_CUBIC) as u64,
self.config.minimum_window,
);
self.cubic_state.cwnd_inc = 0;
self.window = self.config.minimum_window;
}
}
fn window(&self) -> u64 {
self.window
}
fn clone_box(&self) -> Box<dyn Controller> {
Box::new(self.clone())
}
fn initial_window(&self) -> u64 {
self.config.initial_window
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
#[derive(Debug, Clone)]
pub struct CubicConfig {
max_datagram_size: u64,
initial_window: u64,
minimum_window: u64,
}
impl CubicConfig {
pub fn max_datagram_size(&mut self, value: u64) -> &mut Self {
self.max_datagram_size = value;
self
}
pub fn initial_window(&mut self, value: u64) -> &mut Self {
self.initial_window = value;
self
}
pub fn minimum_window(&mut self, value: u64) -> &mut Self {
self.minimum_window = value;
self
}
}
impl Default for CubicConfig {
fn default() -> Self {
const MAX_DATAGRAM_SIZE: u64 = 1232;
Self {
max_datagram_size: MAX_DATAGRAM_SIZE,
initial_window: 14720.max(2 * MAX_DATAGRAM_SIZE).min(10 * MAX_DATAGRAM_SIZE),
minimum_window: 2 * MAX_DATAGRAM_SIZE,
}
}
}
impl ControllerFactory for Arc<CubicConfig> {
fn build(&self, now: Instant) -> Box<dyn Controller> {
Box::new(Cubic::new(self.clone(), now))
}
}