use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use std::time::{Duration, Instant};
const TTL: Duration = Duration::from_secs(600);
const MAX_ENTRIES: usize = 100_000;
static SEEN: LazyLock<Mutex<HashMap<String, Instant>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub(super) fn check_and_record(actor: &str, id: &str) -> bool {
let key = format!("{actor}|{id}");
let now = Instant::now();
let mut seen = SEEN.lock().unwrap_or_else(|p| p.into_inner());
if let Some(&recorded) = seen.get(&key)
&& now.duration_since(recorded) < TTL
{
return false; }
if seen.len() >= MAX_ENTRIES {
seen.retain(|_, &mut t| now.duration_since(t) < TTL);
}
seen.insert(key, now);
true
}
#[cfg(test)]
pub(super) fn reset_for_test() {
SEEN.lock().unwrap_or_else(|p| p.into_inner()).clear();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_submission_is_fresh_replay_is_not() {
reset_for_test();
assert!(check_and_record("did:web:a", "id-1"), "first is fresh");
assert!(!check_and_record("did:web:a", "id-1"), "replay is caught");
assert!(check_and_record("did:web:a", "id-2"));
assert!(check_and_record("did:web:b", "id-1"));
}
}