1pub mod log_entry;
11pub mod chain;
12pub mod merkle_service;
13pub mod publication;
14pub mod error;
15
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use tokio::sync::RwLock;
19
20pub struct ImmutableLog {
22 chain: Arc<RwLock<chain::LogChain>>,
23 merkle: Arc<RwLock<merkle_service::MerkleService>>,
24}
25
26impl ImmutableLog {
27 pub fn new() -> Self {
29 ImmutableLog {
30 chain: Arc::new(RwLock::new(chain::LogChain::new())),
31 merkle: Arc::new(RwLock::new(merkle_service::MerkleService::new())),
32 }
33 }
34
35 pub async fn append(&self, entry: log_entry::LogEntry) -> Result<log_entry::LogEntry, error::LogError> {
37 let mut chain = self.chain.write().await;
39 let entry = chain.append(entry).await?;
40
41 let mut merkle = self.merkle.write().await;
43 merkle.add_entry(entry.clone()).await?;
44
45 Ok(entry)
46 }
47
48 pub async fn verify(&self) -> Result<bool, error::LogError> {
50 let chain = self.chain.read().await;
51 Ok(chain.verify())
52 }
53
54 pub async fn entry_count(&self) -> usize {
56 let chain = self.chain.read().await;
57 chain.len()
58 }
59
60 pub async fn current_hash(&self) -> String {
62 let chain = self.chain.read().await;
63 chain.current_hash().to_string()
64 }
65
66 pub async fn get_hourly_root(&self) -> Option<merkle_service::HourlyRoot> {
68 let merkle = self.merkle.read().await;
69 merkle.get_current_root()
70 }
71
72 pub async fn hourly_roots_snapshot(&self) -> Vec<merkle_service::HourlyRoot> {
74 let merkle = self.merkle.read().await;
75 let mut roots = merkle.get_published_roots().to_vec();
76 if let Some(current) = merkle.get_current_root() {
77 let exists = roots
78 .iter()
79 .any(|r| r.hour == current.hour && r.root_hash == current.root_hash);
80 if !exists {
81 roots.push(current);
82 }
83 }
84 roots
85 }
86
87 pub async fn get_chain_proof(&self, entry_id: &str) -> Option<chain::ChainProof> {
89 let chain = self.chain.read().await;
90 let entry = chain.get_entry(entry_id)?.clone();
91 let mut proof = chain.generate_proof(entry_id)?;
92 drop(chain);
93
94 let merkle = self.merkle.read().await;
95 let merkle_proof = merkle.generate_proof(entry_id, &entry);
96 proof.attach_merkle_proof(merkle_proof);
97 Some(proof)
98 }
99}
100
101impl Default for ImmutableLog {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct LogConfig {
110 pub hash_algorithm: String,
112 pub hourly_publication: bool,
114 pub daily_publication: bool,
116 pub tsa_url: Option<String>,
118 pub blockchain_enabled: bool,
120}
121
122impl Default for LogConfig {
123 fn default() -> Self {
124 LogConfig {
125 hash_algorithm: "SHA256".to_string(),
126 hourly_publication: true,
127 daily_publication: true,
128 tsa_url: None,
129 blockchain_enabled: true,
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_default_config() {
140 let config = LogConfig::default();
141 assert_eq!(config.hash_algorithm, "SHA256");
142 assert!(config.hourly_publication);
143 }
144
145 #[tokio::test]
146 async fn test_append_entry() {
147 let log = ImmutableLog::new();
148
149 let entry = log_entry::LogEntry::new(
150 log_entry::EventType::AccountQuery,
151 "agent-001".to_string(),
152 "org-001".to_string(),
153 );
154
155 let result = log.append(entry).await;
156 assert!(result.is_ok());
157 }
158
159 #[tokio::test]
160 async fn test_chain_stats() {
161 let log = ImmutableLog::new();
162 assert_eq!(log.entry_count().await, 0);
163 assert_eq!(log.current_hash().await.len(), 64);
164 }
165
166 #[tokio::test]
167 async fn test_hourly_roots_snapshot_includes_current_root() {
168 let log = ImmutableLog::new();
169 let entry = log_entry::LogEntry::new(
170 log_entry::EventType::AccountQuery,
171 "agent-001".to_string(),
172 "org-001".to_string(),
173 );
174 log.append(entry).await.expect("append");
175
176 let roots = log.hourly_roots_snapshot().await;
177 assert_eq!(roots.len(), 1);
178 assert_eq!(roots[0].entry_count, 1);
179 }
180}