use thiserror::Error;
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
pub enum AdmissionBudgetError {
#[error("invalid admission budget configuration")]
InvalidPolicy,
#[error("fresh verification budget is exhausted")]
Exhausted,
#[error("admission clock moved backwards")]
ClockRollback,
}
#[derive(Debug)]
pub struct SessionAdmissionBudget {
burst: u32,
available: u32,
refill_interval_ns: u64,
refill_at_ns: u64,
observed_at_ns: u64,
}
impl SessionAdmissionBudget {
pub fn new(
burst: u32,
refill_interval_ns: u64,
now_ns: u64,
) -> Result<Self, AdmissionBudgetError> {
if burst == 0 || refill_interval_ns == 0 {
return Err(AdmissionBudgetError::InvalidPolicy);
}
Ok(Self {
burst,
available: 0,
refill_interval_ns,
refill_at_ns: now_ns,
observed_at_ns: now_ns,
})
}
pub(crate) fn consume(&mut self, now_ns: u64) -> Result<(), AdmissionBudgetError> {
if now_ns < self.observed_at_ns {
return Err(AdmissionBudgetError::ClockRollback);
}
self.observed_at_ns = now_ns;
let elapsed = now_ns - self.refill_at_ns;
let refill = elapsed / self.refill_interval_ns;
self.available = u64::from(self.available)
.saturating_add(refill)
.min(u64::from(self.burst)) as u32;
self.refill_at_ns = now_ns - elapsed % self.refill_interval_ns;
if self.available == 0 {
return Err(AdmissionBudgetError::Exhausted);
}
self.available -= 1;
Ok(())
}
}