use std::collections::VecDeque;
use std::sync::Mutex;
const MAX: usize = 16;
#[derive(Default)]
pub struct Capabilities(Mutex<VecDeque<String>>);
impl Capabilities {
pub fn mint(&self) -> anyhow::Result<String> {
let cap = random_hex()?;
let mut live = self.0.lock().unwrap_or_else(|e| e.into_inner());
if live.len() >= MAX {
live.pop_front();
}
live.push_back(cap.clone());
Ok(cap)
}
pub fn verify(&self, given: &str) -> bool {
let live = self.0.lock().unwrap_or_else(|e| e.into_inner());
live.iter()
.fold(false, |found, cap| constant_eq(given, cap) | found)
}
#[cfg(test)]
fn len(&self) -> usize {
self.0.lock().unwrap().len()
}
}
fn random_hex() -> anyhow::Result<String> {
let mut buf = [0u8; 32];
getrandom::fill(&mut buf)
.map_err(|e| anyhow::anyhow!("reading random bytes for the capability: {e}"))?;
Ok(buf.iter().map(|b| format!("{b:02x}")).collect())
}
fn constant_eq(a: &str, b: &str) -> bool {
a.len() == b.len()
&& a.bytes()
.zip(b.bytes())
.fold(0u8, |acc, (x, y)| acc | (x ^ y))
== 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_minted_capability_verifies_and_a_made_up_one_does_not() {
let caps = Capabilities::default();
let cap = caps.mint().unwrap();
assert!(caps.verify(&cap));
assert!(!caps.verify(&"0".repeat(64)));
assert!(!caps.verify(""));
}
#[test]
fn a_capability_is_thirty_two_bytes_of_hex_and_never_repeats() {
let caps = Capabilities::default();
let a = caps.mint().unwrap();
let b = caps.mint().unwrap();
assert_eq!(a.len(), 64);
assert!(a.chars().all(|c| c.is_ascii_hexdigit()));
assert_ne!(a, b);
}
#[test]
fn several_windows_are_live_at_once_and_the_oldest_falls_out_past_the_cap() {
let caps = Capabilities::default();
let first = caps.mint().unwrap();
let rest: Vec<_> = (0..MAX).map(|_| caps.mint().unwrap()).collect();
assert_eq!(caps.len(), MAX);
assert!(!caps.verify(&first), "the oldest was evicted");
assert!(rest.iter().all(|c| caps.verify(c)), "the rest still hold");
}
}