use super::overuse::Usage;
use super::state::RateControlState;
use std::time::{Duration, Instant};
pub const DEFAULT_DECREASE_FACTOR: f64 = 0.85;
pub const DEFAULT_INCREASE_FACTOR: f64 = 1.08;
pub const DEFAULT_RATE_CONTROL_INTERVAL: Duration = Duration::from_millis(200);
#[derive(Debug, Clone)]
pub struct RateController {
state: RateControlState,
target: f64,
min: f64,
max: f64,
decrease_factor: f64,
increase_factor: f64,
interval: Duration,
last_decrease_rate: Option<f64>,
last_change: Option<Instant>,
}
impl RateController {
pub fn new(initial: f64, min: f64, max: f64) -> Self {
Self {
state: RateControlState::default(),
target: initial.clamp(min, max),
min,
max,
decrease_factor: DEFAULT_DECREASE_FACTOR,
increase_factor: DEFAULT_INCREASE_FACTOR,
interval: DEFAULT_RATE_CONTROL_INTERVAL,
last_decrease_rate: None,
last_change: None,
}
}
pub fn with_decrease_factor(mut self, decrease_factor: f64) -> Self {
self.decrease_factor = decrease_factor;
self
}
pub fn target(&self) -> f64 {
self.target
}
pub fn state(&self) -> RateControlState {
self.state
}
pub fn update(&mut self, now: Instant, usage: Usage, received: Option<f64>) -> f64 {
self.state = self.state.next(usage);
if let Some(last) = self.last_change
&& now.saturating_duration_since(last) < self.interval
&& self.state != RateControlState::Decrease
{
return self.target;
}
match self.state {
RateControlState::Hold => {}
RateControlState::Decrease => {
let Some(received) = received else {
return self.target;
};
self.target = (received * self.decrease_factor).clamp(self.min, self.max);
self.last_decrease_rate = Some(received);
self.last_change = Some(now);
}
RateControlState::Increase => {
let elapsed = self
.last_change
.map_or(self.interval, |last| now.saturating_duration_since(last));
let near_the_ceiling = self
.last_decrease_rate
.is_some_and(|ceiling| self.target > ceiling * 0.85);
self.target = if near_the_ceiling {
(self.target + 12_000.0).clamp(self.min, self.max)
} else {
let growth = self.increase_factor.powf(elapsed.as_secs_f64().min(1.0));
(self.target * growth).clamp(self.min, self.max)
};
self.last_change = Some(now);
}
}
self.target
}
}
#[cfg(test)]
mod tests {
use super::*;
const MIN: f64 = 100_000.0;
const MAX: f64 = 10_000_000.0;
fn controller() -> RateController {
RateController::new(1_000_000.0, MIN, MAX)
}
#[test]
fn overuse_backs_off_from_the_received_rate() {
let epoch = Instant::now();
let mut controller = RateController::new(2_000_000.0, MIN, MAX);
let target = controller.update(epoch, Usage::Over, Some(600_000.0));
assert!(
(target - 510_000.0).abs() < 1.0,
"expected 0.85 × 600 kb/s, got {target}"
);
}
#[test]
fn a_quiet_path_increases() {
let epoch = Instant::now();
let mut controller = controller();
let mut at = epoch;
let before = controller.target();
for _ in 0..10 {
at += DEFAULT_RATE_CONTROL_INTERVAL;
controller.update(at, Usage::Normal, Some(1_000_000.0));
}
assert!(
controller.target() > before,
"a healthy path should be probed for more: {before} → {}",
controller.target()
);
}
#[test]
fn it_climbs_carefully_near_a_known_ceiling() {
let epoch = Instant::now();
let mut controller = controller();
let mut at = epoch;
controller.update(at, Usage::Over, Some(1_000_000.0));
let after_backoff = controller.target();
let mut steps = 0;
while controller.target() < after_backoff * 1.5 && steps < 200 {
at += DEFAULT_RATE_CONTROL_INTERVAL;
controller.update(at, Usage::Normal, Some(1_000_000.0));
steps += 1;
}
assert!(
steps > 5,
"approaching a known ceiling should take several steps, took {steps}"
);
}
#[test]
fn it_holds_when_the_received_rate_is_unknown() {
let epoch = Instant::now();
let mut controller = controller();
let before = controller.target();
let target = controller.update(epoch, Usage::Over, None);
assert_eq!(before, target, "no rate to back off from means no change");
}
#[test]
fn the_target_stays_within_configured_bounds() {
let epoch = Instant::now();
let mut controller = RateController::new(MIN, MIN, 500_000.0);
let mut at = epoch;
for _ in 0..500 {
at += DEFAULT_RATE_CONTROL_INTERVAL;
controller.update(at, Usage::Normal, Some(10_000_000.0));
}
assert!(
controller.target() <= 500_000.0,
"ceiling breached: {}",
controller.target()
);
for _ in 0..50 {
at += DEFAULT_RATE_CONTROL_INTERVAL;
controller.update(at, Usage::Over, Some(1.0));
}
assert!(
controller.target() >= MIN,
"floor breached: {}",
controller.target()
);
}
#[test]
fn increases_are_paced_but_a_decrease_is_not() {
let epoch = Instant::now();
let mut controller = controller();
controller.update(epoch, Usage::Normal, Some(1_000_000.0));
let after_first = controller.target();
controller.update(
epoch + Duration::from_millis(1),
Usage::Normal,
Some(1_000_000.0),
);
assert_eq!(
after_first,
controller.target(),
"a second increase in the same interval must be ignored"
);
let before_backoff = controller.target();
controller.update(
epoch + Duration::from_millis(2),
Usage::Over,
Some(500_000.0),
);
assert!(
controller.target() < before_backoff,
"a decrease must not be delayed by the pacing interval"
);
}
}