use std::collections::HashSet;
use std::path::Path;
use crate::Error;
const MIN_ID_LEN: usize = 64;
#[derive(Debug, Clone, Default)]
pub struct RevocationList(HashSet<String>);
impl RevocationList {
pub fn parse(text: &str) -> Result<Self, Error> {
text.lines()
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.map(|line| {
if line.len() < MIN_ID_LEN || line.len() % 2 != 0 || !line.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(Error::invalid(
"revocation identifier",
"expected at least 32 hex-encoded bytes",
));
}
Ok(line.to_ascii_lowercase())
})
.collect::<Result<_, _>>()
.map(Self)
}
pub fn load(path: &Path) -> std::io::Result<Self> {
let text = std::fs::read_to_string(path)?;
Self::parse(&text).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
}
pub(crate) fn find<'a>(&self, ids: &'a [String]) -> Option<&'a String> {
ids.iter().find(|id| self.0.contains(*id))
}
}
pub(crate) fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_identifiers() {
let (a, b) = ("ab".repeat(64), "cd".repeat(64));
let list = RevocationList::parse(&format!("# revoked\n\n{}\n {b} \n", a.to_uppercase())).unwrap();
let ids = ["ef".repeat(64), a];
assert_eq!(list.find(&ids), Some(&ids[1]));
assert_eq!(list.find(&ids[..1]), None);
}
#[test]
fn rejects_malformed_identifiers() {
for bad in ["abcd".to_owned(), "a".repeat(129), "z".repeat(128)] {
assert!(RevocationList::parse(&bad).is_err(), "{bad}");
}
}
}