1use std::collections::HashMap;
4use std::collections::VecDeque;
5use std::sync::Mutex;
6
7pub type NowFn = Box<dyn Fn() -> u64 + Send + Sync>;
9
10pub struct ReplayCache {
11 map: Mutex<Inner>,
12 max_entries: usize,
13 now: NowFn,
14}
15
16struct Inner {
17 expirations: HashMap<String, u64>,
18 order: VecDeque<String>,
19}
20
21fn default_now_ms() -> u64 {
22 std::time::SystemTime::now()
23 .duration_since(std::time::UNIX_EPOCH)
24 .map(|d| d.as_millis() as u64)
25 .unwrap_or(0)
26}
27
28impl Default for ReplayCache {
29 fn default() -> Self {
30 Self::new(100_000, Box::new(default_now_ms))
31 }
32}
33
34impl ReplayCache {
35 pub fn new(max_entries: usize, now: NowFn) -> Self {
36 Self {
37 map: Mutex::new(Inner {
38 expirations: HashMap::new(),
39 order: VecDeque::new(),
40 }),
41 max_entries,
42 now,
43 }
44 }
45
46 pub fn mark_used(&self, key: &str, expires_at_ms: u64) {
47 let mut inner = self.map.lock().unwrap();
48 if inner.expirations.len() >= self.max_entries {
49 if let Some(oldest) = inner.order.pop_front() {
50 inner.expirations.remove(&oldest);
51 }
52 }
53 if inner
54 .expirations
55 .insert(key.to_string(), expires_at_ms)
56 .is_none()
57 {
58 inner.order.push_back(key.to_string());
59 }
60 }
61
62 pub fn is_used(&self, key: &str) -> bool {
63 let mut inner = self.map.lock().unwrap();
64 let Some(exp) = inner.expirations.get(key).copied() else {
65 return false;
66 };
67 if exp <= (self.now)() {
68 inner.expirations.remove(key);
69 return false;
71 }
72 true
73 }
74}