use vstd::prelude::*;
#[allow(unused_imports)]
use crate::connectives::{counter, cursor};
verus! {
pub struct RateLimit {
pub budget: crate::primitives::budget::Budget,
pub window_duration: u64,
pub max_clock: u64,
pub window_start: u64,
pub clock: u64,
}
impl RateLimit {
pub open spec fn type_invariant(&self) -> bool {
&&& self.budget.capacity >= 1
&&& self.budget.safety_invariant()
&&& self.budget.reserved == 0
&&& self.budget.pending_eviction == 0
}
pub open spec fn window_count_bound(&self) -> bool {
self.budget.allocated <= self.budget.capacity
}
pub open spec fn window_start_not_future(&self) -> bool {
cursor::cursor_admitted(self.window_start as nat, self.clock as nat)
}
pub open spec fn window_expired(&self) -> bool {
self.clock as int - self.window_start as int >= self.window_duration as int
}
pub fn new(max_per_window: u64, window_duration: u64, max_clock: u64) -> (r: RateLimit)
requires
max_per_window >= 1, ensures
r.budget.capacity == max_per_window,
r.window_duration == window_duration,
r.max_clock == max_clock,
r.budget.allocated == 0,
r.window_start == 0,
r.clock == 0,
r.type_invariant(),
r.window_count_bound(),
r.window_start_not_future(),
{
RateLimit {
budget: crate::primitives::budget::Budget::new(max_per_window),
window_duration,
max_clock,
window_start: 0,
clock: 0,
}
}
pub fn try_acquire(&mut self) -> (acquired: bool)
requires
old(self).type_invariant(),
old(self).window_start_not_future(),
ensures
final(self).budget.capacity == old(self).budget.capacity,
final(self).window_duration == old(self).window_duration,
final(self).max_clock == old(self).max_clock,
final(self).clock == old(self).clock, counter::stutter(old(self).clock as int, final(self).clock as int),
cursor::cursor_admitted(
old(self).window_start as nat,
final(self).window_start as nat,
),
acquired == (old(self).window_expired()
|| old(self).budget.allocated < old(self).budget.capacity),
old(self).window_expired() ==> {
&&& final(self).window_start == old(self).clock
&&& final(self).budget.allocated == 1
},
(!old(self).window_expired()
&& old(self).budget.allocated < old(self).budget.capacity) ==> {
&&& final(self).window_start == old(self).window_start
&&& final(self).budget.allocated == old(self).budget.allocated + 1
},
(!old(self).window_expired()
&& old(self).budget.allocated >= old(self).budget.capacity) ==> {
&&& final(self).window_start == old(self).window_start
&&& final(self).budget.allocated == old(self).budget.allocated
},
final(self).type_invariant(),
final(self).window_count_bound(),
final(self).window_start_not_future(),
{
let elapsed = self.clock - self.window_start;
if elapsed >= self.window_duration {
self.window_start = self.clock;
let allocated = self.budget.allocated;
self.budget.release(allocated);
let _accepted = self.budget.try_allocate(1);
assert(_accepted);
true
} else {
self.budget.try_allocate(1)
}
}
pub fn tick(&mut self)
requires
old(self).type_invariant(),
old(self).window_start_not_future(),
old(self).clock < old(self).max_clock, ensures
final(self).budget.capacity == old(self).budget.capacity,
final(self).window_duration == old(self).window_duration,
final(self).max_clock == old(self).max_clock,
final(self).budget.allocated == old(self).budget.allocated,
final(self).window_start == old(self).window_start,
final(self).clock == old(self).clock + 1,
counter::increment(old(self).clock as int, final(self).clock as int),
final(self).type_invariant(),
final(self).window_count_bound(),
final(self).window_start_not_future(),
{
self.clock = self.clock + 1;
}
}
}