use std::collections::HashMap;
use std::sync::RwLock;
use std::time::{Duration, Instant};
const TSP_REACH_TTL: Duration = Duration::from_secs(300);
pub struct TspReachability {
seen: RwLock<HashMap<String, Instant>>,
ttl: Duration,
}
impl Default for TspReachability {
fn default() -> Self {
Self::new()
}
}
impl TspReachability {
pub fn new() -> Self {
Self::with_ttl(TSP_REACH_TTL)
}
pub fn with_ttl(ttl: Duration) -> Self {
Self {
seen: RwLock::new(HashMap::new()),
ttl,
}
}
pub fn record(&self, did: &str) {
if let Ok(mut seen) = self.seen.write() {
seen.insert(did.to_string(), Instant::now());
}
}
pub fn fresh(&self, did: &str) -> bool {
self.seen
.read()
.ok()
.and_then(|seen| seen.get(did).map(|t| t.elapsed() < self.ttl))
.unwrap_or(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn records_and_reads_back_fresh() {
let r = TspReachability::new();
assert!(!r.fresh("did:key:zDevice"));
r.record("did:key:zDevice");
assert!(r.fresh("did:key:zDevice"));
assert!(!r.fresh("did:key:zOther"));
}
#[test]
fn entry_expires_after_ttl() {
let r = TspReachability::with_ttl(Duration::from_millis(20));
r.record("did:key:zDevice");
assert!(r.fresh("did:key:zDevice"), "fresh right after record");
std::thread::sleep(Duration::from_millis(40));
assert!(
!r.fresh("did:key:zDevice"),
"must go stale once the TTL elapses so push falls back to DIDComm"
);
}
#[test]
fn record_refreshes_the_window() {
let r = TspReachability::with_ttl(Duration::from_millis(40));
r.record("did:key:zDevice");
std::thread::sleep(Duration::from_millis(25));
r.record("did:key:zDevice");
std::thread::sleep(Duration::from_millis(25));
assert!(
r.fresh("did:key:zDevice"),
"re-record within the window keeps it fresh past the original expiry"
);
}
}