use std::time::{Duration, SystemTime};
use log::{error, warn};
use sieve_cache::SyncSieveCache;
use crate::dns_key::DNSKey;
#[derive(Clone)]
pub struct CachedResponse {
pub data: Vec<u8>,
pub cached_at: SystemTime,
pub expires_at: SystemTime,
}
impl CachedResponse {
pub fn new(data: Vec<u8>, ttl: Duration) -> Self {
let cached_at = SystemTime::now();
let expires_at = cached_at.checked_add(ttl).unwrap_or(cached_at);
Self {
data,
cached_at,
expires_at,
}
}
pub fn is_expired(&self) -> bool {
SystemTime::now().duration_since(self.expires_at).is_ok()
}
}
pub type SyncDnsCache = SyncSieveCache<DNSKey, CachedResponse>;
pub fn create_dns_cache(capacity: usize) -> SyncDnsCache {
if let Ok(cache) = SyncSieveCache::<DNSKey, CachedResponse>::new(capacity) {
return cache;
}
error!("Failed to create DNS cache with requested capacity of {capacity} entries");
let smaller_capacity = std::cmp::max(100, capacity / 2);
warn!("Falling back to smaller DNS cache capacity: {smaller_capacity} entries");
if let Ok(cache) = SyncSieveCache::<DNSKey, CachedResponse>::new(smaller_capacity) {
return cache;
}
error!("Failed to create DNS cache with reduced capacity of {smaller_capacity} entries");
let minimal_capacity = 10; warn!("Falling back to minimal DNS cache capacity: {minimal_capacity} entries");
match SyncSieveCache::<DNSKey, CachedResponse>::new(minimal_capacity) {
Ok(cache) => cache,
Err(e) => {
error!(
"Critical error: Failed to allocate even minimal DNS cache with {minimal_capacity} entries: {e}"
);
warn!("Running with a non-functional cache - performance will be degraded");
SyncSieveCache::<DNSKey, CachedResponse>::new(1)
.unwrap_or_else(|e2| {
panic!("Fatal error: Cannot allocate even a trivial cache with 1 entry. System is likely out of memory: {e2}")
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, SystemTime};
#[test]
fn test_large_ttl_conversion() {
let data = vec![1, 2, 3];
let ttl = Duration::from_secs(365 * 24 * 60 * 60);
let cached = CachedResponse::new(data, ttl);
assert!(cached.expires_at > SystemTime::now());
}
#[test]
fn test_cache_expiry_check() {
let data = vec![1, 2, 3];
let cached = CachedResponse::new(data, Duration::from_secs(0));
std::thread::sleep(Duration::from_millis(1));
assert!(cached.is_expired());
}
#[test]
fn test_unrepresentable_expiry_does_not_panic() {
let result = std::panic::catch_unwind(|| {
CachedResponse::new(Vec::new(), Duration::from_secs(u64::MAX))
});
assert!(result.is_ok());
assert!(result.unwrap().is_expired());
}
}