use lru::LruCache;
use rcgen::Certificate;
use std::num::NonZeroUsize;
pub struct CertCache {
cache: LruCache<String, Certificate>,
hits: u64,
misses: u64,
}
impl CertCache {
pub fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(NonZeroUsize::new(capacity).unwrap()),
hits: 0,
misses: 0,
}
}
pub fn get(&mut self, domain: &str) -> Option<&Certificate> {
match self.cache.get(domain) {
Some(cert) => {
self.hits += 1;
Some(cert)
}
None => {
self.misses += 1;
None
}
}
}
pub fn insert(&mut self, domain: String, cert: Certificate) {
self.cache.put(domain, cert);
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
pub fn capacity(&self) -> usize {
self.cache.cap().get()
}
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn misses(&self) -> u64 {
self.misses
}
pub fn clear(&mut self) {
self.cache.clear();
self.hits = 0;
self.misses = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use rcgen::CertificateParams;
fn generate_test_cert() -> Certificate {
let params = CertificateParams::default();
params.self_signed(&rcgen::KeyPair::generate().unwrap()).unwrap()
}
#[test]
fn test_cache_insert_and_get() {
let mut cache = CertCache::new(10);
let cert = generate_test_cert();
cache.insert("example.com".to_string(), cert);
assert_eq!(cache.len(), 1);
assert!(cache.get("example.com").is_some());
assert!(cache.get("nonexistent.com").is_none());
}
#[test]
fn test_cache_hit_rate() {
let mut cache = CertCache::new(10);
let cert = generate_test_cert();
cache.insert("example.com".to_string(), cert);
cache.get("example.com");
cache.get("nonexistent1.com");
cache.get("nonexistent2.com");
assert!((cache.hit_rate() - 0.333).abs() < 0.01);
assert_eq!(cache.hits(), 1);
assert_eq!(cache.misses(), 2);
}
#[test]
fn test_cache_lru_eviction() {
let mut cache = CertCache::new(2);
let cert1 = generate_test_cert();
let cert2 = generate_test_cert();
let cert3 = generate_test_cert();
cache.insert("domain1.com".to_string(), cert1);
cache.insert("domain2.com".to_string(), cert2);
cache.insert("domain3.com".to_string(), cert3);
assert_eq!(cache.len(), 2);
assert!(cache.get("domain1.com").is_none());
assert!(cache.get("domain2.com").is_some());
assert!(cache.get("domain3.com").is_some());
}
#[test]
fn test_cache_clear() {
let mut cache = CertCache::new(10);
let cert = generate_test_cert();
cache.insert("example.com".to_string(), cert);
cache.get("example.com");
assert_eq!(cache.len(), 1);
assert_eq!(cache.hits(), 1);
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.hits(), 0);
assert_eq!(cache.misses(), 0);
}
}