#![cfg(kani)]
use throttle_kit::gcra_decide;
#[kani::proof]
fn kani_gcra_decide_invariants_for_arbitrary_inputs() {
let now_ms: u64 = kani::any();
let tac_ms: u64 = kani::any();
let emission_interval_ms: u64 = kani::any();
let burst: u64 = kani::any();
let (allowed, retry_after_ms, remaining) =
gcra_decide(now_ms, tac_ms, emission_interval_ms, burst);
kani::assert(
allowed == (retry_after_ms == 0),
"allowed ⟺ retry_after == 0",
);
kani::assert(remaining <= burst, "remaining ≤ burst");
if allowed {
kani::assert(remaining >= 1, "allowed ⟹ remaining ≥ 1");
} else {
kani::assert(remaining == 0, "denied ⟹ remaining == 0");
kani::assert(retry_after_ms >= 1, "denied ⟹ retry_after ≥ 1");
}
}
#[kani::proof]
fn kani_gcra_decide_matches_textbook_gcra() {
let now: u64 = kani::any();
let tac: u64 = kani::any();
let emission: u64 = kani::any();
let burst: u64 = kani::any();
kani::assume(now <= 16);
kani::assume(tac <= 16);
kani::assume(emission >= 1 && emission <= 4);
kani::assume(burst <= 3);
let now_i = now as i64;
let new_tat = (tac as i64).max(now_i) + emission as i64;
let allow_at = new_tat - emission as i64 * burst as i64;
let ref_allowed = now_i >= allow_at;
let (allowed, retry_after_ms, remaining) = gcra_decide(now, tac, emission, burst);
kani::assert(allowed == ref_allowed, "agrees with textbook GCRA decision");
if allowed {
let ref_remaining = ((new_tat - now_i) / emission as i64) as u64;
kani::assert(remaining == ref_remaining.min(burst), "remaining matches");
kani::assert(retry_after_ms == 0, "no retry on allow");
} else {
kani::assert(
retry_after_ms == (allow_at - now_i) as u64,
"retry_after matches",
);
kani::assert(remaining == 0, "no remaining on deny");
}
}