use crate::event::SecurityEvent;
use hex;
use sha2::{Digest, Sha256};
#[derive(Debug, Clone)]
pub struct ChainedAuditEntry {
pub event: SecurityEvent,
pub hash: String,
pub previous_hash: Option<String>,
}
#[derive(Debug, Default)]
pub struct AuditChain {
entries: Vec<ChainedAuditEntry>,
}
impl AuditChain {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn append(&mut self, event: SecurityEvent) {
let previous_hash = self.entries.last().map(|e| e.hash.clone());
let hash = compute_entry_hash(previous_hash.as_deref(), &event);
self.entries.push(ChainedAuditEntry {
event,
hash,
previous_hash,
});
}
#[must_use]
pub fn entries(&self) -> &[ChainedAuditEntry] {
&self.entries
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn verify(&self) -> bool {
let mut prev_hash: Option<&str> = None;
for entry in &self.entries {
match (&entry.previous_hash, prev_hash) {
(None, None) => {}
(Some(a), Some(b)) if a == b => {}
_ => return false,
}
let expected = compute_entry_hash(prev_hash, &entry.event);
if expected != entry.hash {
return false;
}
prev_hash = Some(&entry.hash);
}
true
}
}
fn compute_entry_hash(previous_hash: Option<&str>, event: &SecurityEvent) -> String {
let event_json = serde_json::to_string(event).unwrap_or_default();
let mut hasher = Sha256::new();
if let Some(prev) = previous_hash {
hasher.update(prev.as_bytes());
}
hasher.update(event_json.as_bytes());
hex::encode(hasher.finalize())
}