const ECN_ALPHA_GAIN: f64 = 1.0 / 16.0;
const ECN_THRESH: f64 = 0.5;
pub(super) const ECN_FACTOR: f64 = 0.33;
#[derive(Clone, Debug)]
pub(crate) struct State {
ce_count_in_round: u64,
round_start_delivered_bytes: u64,
alpha: f64,
ce_too_high: bool,
}
impl Default for State {
fn default() -> Self {
Self {
ce_count_in_round: 0,
round_start_delivered_bytes: 0,
alpha: 1.0,
ce_too_high: false,
}
}
}
impl State {
#[inline]
pub(super) fn on_round_start(&mut self, delivered_bytes: u64, max_datagram_size: u16) {
let delivered_bytes_in_round = delivered_bytes - self.round_start_delivered_bytes;
if delivered_bytes_in_round > 0 {
let ce_ratio = ce_ratio(
self.ce_count_in_round,
delivered_bytes_in_round,
max_datagram_size,
);
self.alpha = calculate_alpha(self.alpha, ce_ratio);
self.ce_too_high = is_ce_too_high(ce_ratio);
}
self.round_start_delivered_bytes = delivered_bytes;
self.ce_count_in_round = 0;
}
#[inline]
pub(super) fn on_explicit_congestion(&mut self, ce_count: u64) {
self.ce_count_in_round += ce_count;
}
#[inline]
pub(super) fn is_ce_too_high_in_round(&self) -> bool {
self.ce_too_high
}
#[inline]
pub(super) fn alpha(&self) -> f64 {
self.alpha
}
}
#[inline]
pub(super) fn ce_ratio(ecn_ce_count: u64, delivered_bytes: u64, max_datagram_size: u16) -> f64 {
let ecn_ce_bytes = ecn_ce_count.saturating_mul(max_datagram_size as u64) as f64;
ecn_ce_bytes / delivered_bytes as f64
}
#[inline]
pub(super) fn is_ce_too_high(ce_ratio: f64) -> bool {
ce_ratio > ECN_THRESH
}
#[inline]
fn calculate_alpha(alpha: f64, ce_ratio: f64) -> f64 {
((1.0 - ECN_ALPHA_GAIN) * alpha + ECN_ALPHA_GAIN * ce_ratio).min(1.0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_delta, path::MINIMUM_MAX_DATAGRAM_SIZE, recovery::bbr::ecn};
#[test]
fn on_round_start() {
let mut state = ecn::State::default();
assert_delta!(1.0, state.alpha(), 0.0001);
let delivered_bytes = 1000;
state.on_round_start(delivered_bytes, MINIMUM_MAX_DATAGRAM_SIZE);
assert_delta!(1.0 - ECN_ALPHA_GAIN, state.alpha(), 0.0001);
assert!(!state.is_ce_too_high_in_round());
assert_eq!(delivered_bytes, state.round_start_delivered_bytes);
state.on_explicit_congestion(10);
assert_eq!(10, state.ce_count_in_round);
let alpha = state.alpha();
let prev_delivered = delivered_bytes;
let delivered_bytes = prev_delivered + 20 * MINIMUM_MAX_DATAGRAM_SIZE as u64;
state.on_round_start(delivered_bytes, MINIMUM_MAX_DATAGRAM_SIZE);
assert_delta!(
(1.0 - ECN_ALPHA_GAIN) * alpha + ECN_ALPHA_GAIN * 0.5,
state.alpha(),
0.0001
);
assert!(!state.is_ce_too_high_in_round());
assert_eq!(delivered_bytes, state.round_start_delivered_bytes);
assert_eq!(0, state.ce_count_in_round);
state.on_explicit_congestion(11);
assert_eq!(11, state.ce_count_in_round);
let alpha = state.alpha();
let prev_delivered = delivered_bytes;
let delivered_bytes = prev_delivered + 20 * MINIMUM_MAX_DATAGRAM_SIZE as u64;
state.on_round_start(delivered_bytes, MINIMUM_MAX_DATAGRAM_SIZE);
assert_delta!(
(1.0 - ECN_ALPHA_GAIN) * alpha + ECN_ALPHA_GAIN * 11.0 / 20.0,
state.alpha(),
0.0001
);
assert!(state.is_ce_too_high_in_round());
assert_eq!(delivered_bytes, state.round_start_delivered_bytes);
assert_eq!(0, state.ce_count_in_round);
}
}