use core::time::Duration;
use std::collections::HashMap;
use std::time::Instant;
use crate::primitives::Csprng;
pub type FreshnessToken = [u8; 32];
pub trait FreshnessStore {
fn issue(&mut self) -> FreshnessToken;
fn consume(&mut self, token: &[u8]) -> bool;
fn cleanup(&mut self);
}
#[cfg(feature = "std-primitives")]
pub struct InMemoryFreshness<R: Csprng = crate::primitives::OsCsprng> {
issued: HashMap<FreshnessToken, Instant>,
default_ttl: Duration,
_rng: core::marker::PhantomData<R>,
}
#[cfg(not(feature = "std-primitives"))]
pub struct InMemoryFreshness<R: Csprng> {
issued: HashMap<FreshnessToken, Instant>,
default_ttl: Duration,
_rng: core::marker::PhantomData<R>,
}
#[cfg(feature = "std-primitives")]
impl Default for InMemoryFreshness<crate::primitives::OsCsprng> {
fn default() -> Self {
Self::new(Duration::from_secs(300))
}
}
impl<R: Csprng> InMemoryFreshness<R> {
pub fn new(default_ttl: Duration) -> Self {
Self {
issued: HashMap::new(),
default_ttl,
_rng: core::marker::PhantomData,
}
}
pub fn live_count(&self) -> usize {
let now = Instant::now();
self.issued
.values()
.filter(|t| now.duration_since(**t) < self.default_ttl)
.count()
}
}
impl<R: Csprng> FreshnessStore for InMemoryFreshness<R> {
fn issue(&mut self) -> FreshnessToken {
let token = R::random_32();
self.issued.insert(token, Instant::now());
token
}
fn consume(&mut self, token: &[u8]) -> bool {
if token.len() != 32 {
return false;
}
let mut key = [0u8; 32];
key.copy_from_slice(token);
match self.issued.remove(&key) {
Some(issued_at) => Instant::now().duration_since(issued_at) < self.default_ttl,
None => false,
}
}
fn cleanup(&mut self) {
let now = Instant::now();
let ttl = self.default_ttl;
self.issued
.retain(|_, issued_at| now.duration_since(*issued_at) < ttl);
}
}
#[cfg(all(test, feature = "std-primitives"))]
mod tests {
use super::*;
#[test]
fn issue_and_consume() {
let mut pool = InMemoryFreshness::default();
let r = pool.issue();
assert!(pool.consume(&r));
assert!(!pool.consume(&r), "double-consume must fail");
}
#[test]
fn unknown_token_rejected() {
let mut pool = InMemoryFreshness::default();
assert!(!pool.consume(&[0u8; 32]));
}
#[test]
fn distinct_tokens() {
let mut pool = InMemoryFreshness::default();
let a = pool.issue();
let b = pool.issue();
assert_ne!(a, b);
}
}