use core::fmt;
use std::{
cmp::{self, max},
fmt::Display,
time::{Duration, Instant},
};
#[cfg(test)]
mod test;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum GcrCreationError {
ParametersOutOfRange(String),
}
impl Display for GcrCreationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ParametersOutOfRange(msg) => write!(f, "Parameters out of range: {}", msg),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum GcrRequestError {
DeniedFor(Duration),
RequestTooLarge,
ParametersOutOfRange(String),
}
impl Display for GcrRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DeniedFor(duration) => write!(f, "Request denied for {:?}", duration),
Self::RequestTooLarge => write!(f, "Request was too large to ever be allowed"),
Self::ParametersOutOfRange(msg) => write!(f, "Parameters out of range: {}", msg),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Gcr {
emission_interval: Duration,
delay_tolerance: Duration,
theoretical_arrival_time: Instant,
allow_at: Instant,
max_burst: u32,
}
impl Gcr {
pub fn new(
rate: u32,
period: Duration,
max_burst: Option<u32>,
) -> Result<Self, GcrCreationError> {
let emission_interval =
period
.checked_div(rate)
.ok_or(GcrCreationError::ParametersOutOfRange(
"Supplied rate was zero".to_string(),
))?;
let max_burst = max_burst.unwrap_or(rate);
let delay_tolerance = emission_interval.checked_mul(max_burst).ok_or(
GcrCreationError::ParametersOutOfRange("Period / rate was too large".to_string()),
)?;
let theoretical_arrival_time = Instant::now();
let allow_at = theoretical_arrival_time
.checked_sub(delay_tolerance)
.ok_or(GcrCreationError::ParametersOutOfRange(
"Period / rate was too large".to_string(),
))?;
Ok(Self {
max_burst,
emission_interval,
delay_tolerance,
theoretical_arrival_time,
allow_at,
})
}
fn capacity_at(&self, now: Instant) -> u32 {
let Some(time_since) = now.checked_duration_since(self.allow_at) else {
return 0;
};
cmp::min(
time_since.div_duration_f64(self.emission_interval) as u32,
self.max_burst,
)
}
pub fn capacity(&self) -> u32 {
self.capacity_at(Instant::now())
}
pub fn request(&mut self, n: u32) -> Result<(), GcrRequestError> {
if n > self.max_burst {
return Err(GcrRequestError::RequestTooLarge);
}
let now = Instant::now();
let required_duration =
self.emission_interval
.checked_mul(n)
.ok_or(GcrRequestError::ParametersOutOfRange(
"Period / rate was too large".to_string(),
))?;
if n > self.capacity_at(now) {
let allow_time = self.allow_at.checked_add(required_duration).ok_or(
GcrRequestError::ParametersOutOfRange("Period / rate was too large".to_string()),
)?;
let denied_for = allow_time.checked_duration_since(now);
if let Some(denied_for) = denied_for {
return Err(GcrRequestError::DeniedFor(denied_for));
}
}
self.theoretical_arrival_time = max(self.theoretical_arrival_time, now)
.checked_add(required_duration)
.ok_or(GcrRequestError::ParametersOutOfRange(
"Period / rate was too large".to_string(),
))?;
self.allow_at = self
.theoretical_arrival_time
.checked_sub(self.delay_tolerance)
.ok_or(GcrRequestError::ParametersOutOfRange(
"(Period / rate * max_burst) was too large".to_string(),
))?;
Ok(())
}
pub fn adjust(
&mut self,
rate: u32,
period: Duration,
max_burst: Option<u32>,
) -> Result<(), GcrCreationError> {
let mut new_rate = Gcr::new(rate, period, max_burst)?;
let now = Instant::now();
if let Some(time_since) = now.checked_duration_since(self.allow_at) {
new_rate.allow_at = now
.checked_sub(
new_rate
.emission_interval
.checked_mul(time_since.div_duration_f64(self.emission_interval) as u32)
.ok_or(GcrCreationError::ParametersOutOfRange(
"Period / rate was too large".to_string(),
))?,
)
.ok_or(GcrCreationError::ParametersOutOfRange(
"Period / rate was too large".to_string(),
))?;
new_rate.theoretical_arrival_time = new_rate
.allow_at
.checked_add(new_rate.delay_tolerance)
.ok_or(GcrCreationError::ParametersOutOfRange(
"Delay tolerance was too large".to_string(),
))?;
}
*self = new_rate;
Ok(())
}
}