use std::collections::HashSet;
use std::sync::RwLock;
pub struct RevocationRegistry {
revoked: RwLock<HashSet<String>>,
}
impl RevocationRegistry {
pub fn new() -> Self {
Self {
revoked: RwLock::new(HashSet::new()),
}
}
pub fn revoke(&self, delegation_id: &str) {
if let Ok(mut set) = self.revoked.write() {
set.insert(delegation_id.to_string());
}
}
pub fn is_revoked(&self, delegation_id: &str) -> bool {
match self.revoked.read() {
Ok(set) => set.contains(delegation_id),
Err(_) => {
true
}
}
}
pub fn revoke_cascade(&self, ids: &[String]) {
if let Ok(mut set) = self.revoked.write() {
for id in ids {
set.insert(id.clone());
}
}
}
}
impl Default for RevocationRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_registry_is_empty() {
let reg = RevocationRegistry::new();
assert!(!reg.is_revoked("any-id"));
}
#[test]
fn revoke_and_check() {
let reg = RevocationRegistry::new();
reg.revoke("del-1");
assert!(reg.is_revoked("del-1"));
assert!(!reg.is_revoked("del-2"));
}
#[test]
fn revoke_cascade_marks_all() {
let reg = RevocationRegistry::new();
let ids = vec!["d1".to_string(), "d2".to_string(), "d3".to_string()];
reg.revoke_cascade(&ids);
assert!(reg.is_revoked("d1"));
assert!(reg.is_revoked("d2"));
assert!(reg.is_revoked("d3"));
assert!(!reg.is_revoked("d4"));
}
#[test]
fn revoke_is_idempotent() {
let reg = RevocationRegistry::new();
reg.revoke("del-1");
reg.revoke("del-1");
assert!(reg.is_revoked("del-1"));
}
#[test]
fn default_creates_empty_registry() {
let reg = RevocationRegistry::default();
assert!(!reg.is_revoked("anything"));
}
#[test]
fn concurrent_access() {
use std::sync::Arc;
use std::thread;
let reg = Arc::new(RevocationRegistry::new());
let mut handles = vec![];
for i in 0..10 {
let reg_clone = Arc::clone(®);
let handle = thread::spawn(move || {
let id = format!("del-{i}");
reg_clone.revoke(&id);
});
handles.push(handle);
}
for h in handles {
let _ = h.join();
}
for i in 0..10 {
let id = format!("del-{i}");
assert!(reg.is_revoked(&id));
}
}
}