1use std::sync::Arc;
4
5use async_trait::async_trait;
6use time::OffsetDateTime;
7use tokio::sync::Mutex;
8
9use crate::{
10 event::{Event, EventReceipt, StoredEvent},
11 AuditError, AuditLog, AuditResult, GENESIS_HEAD,
12};
13
14#[derive(Default)]
15pub struct MemoryAuditLog {
16 inner: Arc<Mutex<Inner>>,
17}
18
19#[derive(Default)]
20struct Inner {
21 events: Vec<StoredEvent>,
22}
23
24impl MemoryAuditLog {
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub async fn snapshot(&self) -> Vec<StoredEvent> {
30 self.inner.lock().await.events.clone()
31 }
32}
33
34#[async_trait]
35impl AuditLog for MemoryAuditLog {
36 async fn append(&self, event: Event) -> AuditResult<EventReceipt> {
37 let mut guard = self.inner.lock().await;
38 let position = guard.events.len() as u64;
39 let prev_hash = guard
40 .events
41 .last()
42 .map(|e| e.this_hash.clone())
43 .unwrap_or_else(|| GENESIS_HEAD.to_string());
44
45 let timestamp = OffsetDateTime::now_utc();
46 let this_hash = event.compute_hash(timestamp, &prev_hash)?;
47 let stored = StoredEvent {
48 position,
49 event_id: format!("evt_{}", uuid::Uuid::new_v4().simple()),
50 timestamp,
51 prev_hash,
52 this_hash,
53 event,
54 };
55
56 let receipt = EventReceipt::from(&stored);
57 guard.events.push(stored);
58 Ok(receipt)
59 }
60
61 async fn current_head(&self) -> AuditResult<String> {
62 let guard = self.inner.lock().await;
63 Ok(guard
64 .events
65 .last()
66 .map(|e| e.this_hash.clone())
67 .unwrap_or_else(|| GENESIS_HEAD.to_string()))
68 }
69
70 async fn verify_chain(&self) -> AuditResult<()> {
71 let guard = self.inner.lock().await;
72 let mut expected_prev = GENESIS_HEAD.to_string();
73 for (i, ev) in guard.events.iter().enumerate() {
74 if ev.prev_hash != expected_prev {
75 return Err(AuditError::ChainBroken {
76 position: i as u64,
77 expected: expected_prev,
78 found: ev.prev_hash.clone(),
79 });
80 }
81 ev.verify_hash()?;
82 expected_prev = ev.this_hash.clone();
83 }
84 Ok(())
85 }
86
87 async fn len(&self) -> AuditResult<u64> {
88 Ok(self.inner.lock().await.events.len() as u64)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::event::EventKind;
96
97 #[tokio::test]
98 async fn empty_log_head_is_genesis() {
99 let log = MemoryAuditLog::new();
100 assert_eq!(log.current_head().await.unwrap(), GENESIS_HEAD);
101 assert_eq!(log.len().await.unwrap(), 0);
102 }
103
104 #[tokio::test]
105 async fn append_advances_head() {
106 let log = MemoryAuditLog::new();
107 let r = log
108 .append(Event::new(
109 EventKind::AgentRegistered,
110 "spize:acme/alice:a4f8b2",
111 "spize:acme/alice:a4f8b2",
112 serde_json::json!({"fingerprint": "a4f8b2"}),
113 ))
114 .await
115 .unwrap();
116 assert_eq!(r.position, 0);
117 assert_ne!(r.chain_head, GENESIS_HEAD);
118 assert_eq!(log.current_head().await.unwrap(), r.chain_head);
119 }
120
121 #[tokio::test]
122 async fn chain_verifies_after_many_appends() {
123 let log = MemoryAuditLog::new();
124 for i in 0..20 {
125 log.append(Event::new(
126 EventKind::TransferInitiated,
127 "spize:acme/alice:a4f8b2",
128 format!("tx_{}", i),
129 serde_json::json!({"seq": i}),
130 ))
131 .await
132 .unwrap();
133 }
134 log.verify_chain().await.unwrap();
135 assert_eq!(log.len().await.unwrap(), 20);
136 }
137
138 #[tokio::test]
139 async fn tampering_breaks_chain() {
140 let log = MemoryAuditLog::new();
141 for i in 0..3 {
142 log.append(Event::new(
143 EventKind::TransferInitiated,
144 "",
145 format!("tx_{}", i),
146 serde_json::json!({"seq": i}),
147 ))
148 .await
149 .unwrap();
150 }
151 {
153 let mut g = log.inner.lock().await;
154 g.events[1].event.subject = "tx_evil".into();
155 }
156 let err = log.verify_chain().await.unwrap_err();
157 assert!(matches!(err, AuditError::HashMismatch { position: 1, .. }));
158 }
159
160 #[tokio::test]
161 async fn broken_link_detected() {
162 let log = MemoryAuditLog::new();
163 for i in 0..3 {
164 log.append(Event::new(
165 EventKind::TransferInitiated,
166 "",
167 format!("tx_{}", i),
168 serde_json::json!({"seq": i}),
169 ))
170 .await
171 .unwrap();
172 }
173 {
174 let mut g = log.inner.lock().await;
175 g.events[2].prev_hash = "f".repeat(64);
176 }
177 let err = log.verify_chain().await.unwrap_err();
178 assert!(matches!(err, AuditError::ChainBroken { position: 2, .. }));
179 }
180}