1use crate::{encode_str, fingerprint_str, UniversalError};
30use serde::{Deserialize, Serialize};
31use std::time::{SystemTime, UNIX_EPOCH};
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ChainLink {
36 pub seq: u64,
38 pub d: String,
40 pub f: String,
42 pub prev: Option<String>,
44 pub ts: u64,
46}
47
48impl ChainLink {
49 pub fn verify_data(&self) -> Result<String, UniversalError> {
51 use base64::{engine::general_purpose::STANDARD, Engine};
52 let bytes = STANDARD
53 .decode(&self.d)
54 .map_err(|e| UniversalError::DecodeError(e.to_string()))?;
55 let decoded =
56 String::from_utf8(bytes).map_err(|e| UniversalError::DecodeError(e.to_string()))?;
57 let data_fp = fingerprint_str(&decoded);
58 let actual_fp = match &self.prev {
60 Some(prev) => fingerprint_str(&format!("{}{}", data_fp, prev)),
61 None => data_fp,
62 };
63 if actual_fp != self.f {
64 return Err(UniversalError::IntegrityViolation {
65 expected: self.f.clone(),
66 actual: actual_fp,
67 });
68 }
69 Ok(decoded)
70 }
71}
72
73#[derive(Debug, Clone, PartialEq)]
75pub struct ChainVerifyResult {
76 pub valid: bool,
78 pub total_links: usize,
80 pub broken_at: Option<usize>,
82 pub broken_reason: Option<String>,
84}
85
86impl std::fmt::Display for ChainVerifyResult {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 if self.valid {
89 write!(f, "Valid chain ({} links)", self.total_links)
90 } else {
91 write!(
92 f,
93 "Broken at link {} of {}: {}",
94 self.broken_at.unwrap_or(0),
95 self.total_links,
96 self.broken_reason.as_deref().unwrap_or("unknown")
97 )
98 }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct Chain {
106 pub links: Vec<ChainLink>,
107}
108
109impl Chain {
110 #[must_use]
112 pub fn new(data: &str) -> Self {
113 let ts = SystemTime::now()
114 .duration_since(UNIX_EPOCH)
115 .unwrap_or_default()
116 .as_secs();
117
118 let link = ChainLink {
119 seq: 1,
120 d: encode_str(data),
121 f: fingerprint_str(data),
122 prev: None,
123 ts,
124 };
125
126 Self { links: vec![link] }
127 }
128
129 pub fn append(&mut self, data: &str) -> &ChainLink {
131 let prev_fp = self.links.last().map(|l| l.f.clone());
132 let seq = self.links.len() as u64 + 1;
133 let ts = SystemTime::now()
134 .duration_since(UNIX_EPOCH)
135 .unwrap_or_default()
136 .as_secs();
137
138 let combined = format!(
141 "{}{}",
142 fingerprint_str(data),
143 prev_fp.as_deref().unwrap_or("")
144 );
145 let chained_fp = fingerprint_str(&combined);
146
147 self.links.push(ChainLink {
148 seq,
149 d: encode_str(data),
150 f: chained_fp,
151 prev: prev_fp,
152 ts,
153 });
154
155 self.links.last().unwrap()
156 }
157
158 pub fn verify(&self) -> ChainVerifyResult {
161 if self.links.is_empty() {
162 return ChainVerifyResult {
163 valid: true,
164 total_links: 0,
165 broken_at: None,
166 broken_reason: None,
167 };
168 }
169
170 for (i, link) in self.links.iter().enumerate() {
171 if let Err(e) = link.verify_data() {
173 return ChainVerifyResult {
174 valid: false,
175 total_links: self.links.len(),
176 broken_at: Some(i + 1),
177 broken_reason: Some(format!("Data integrity: {}", e)),
178 };
179 }
180
181 if i > 0 {
183 let prev_fp = &self.links[i - 1].f;
184 if link.prev.as_deref() != Some(prev_fp.as_str()) {
185 return ChainVerifyResult {
186 valid: false,
187 total_links: self.links.len(),
188 broken_at: Some(i + 1),
189 broken_reason: Some(format!(
190 "Chain broken: link {} doesn't reference link {}",
191 i + 1,
192 i
193 )),
194 };
195 }
196 }
197 }
198
199 ChainVerifyResult {
200 valid: true,
201 total_links: self.links.len(),
202 broken_at: None,
203 broken_reason: None,
204 }
205 }
206
207 pub fn len(&self) -> usize {
209 self.links.len()
210 }
211
212 pub fn is_empty(&self) -> bool {
214 self.links.is_empty()
215 }
216
217 pub fn to_json(&self) -> Result<String, UniversalError> {
219 serde_json::to_string(self).map_err(|e| UniversalError::SerializationError(e.to_string()))
220 }
221
222 pub fn from_json(s: &str) -> Result<Self, UniversalError> {
224 serde_json::from_str(s).map_err(|e| UniversalError::SerializationError(e.to_string()))
225 }
226
227 pub fn report(&self) -> String {
229 let result = self.verify();
230 let mut out = String::new();
231 out.push_str("━━━━ Entrouter Universal Chain Report ━━━━\n");
232 out.push_str(&format!(
233 "Links: {} | Valid: {}\n\n",
234 self.links.len(),
235 result.valid
236 ));
237 for link in &self.links {
238 let status = if result.broken_at == Some(link.seq as usize) {
239 "❌"
240 } else {
241 "✅"
242 };
243 out.push_str(&format!(
244 " Link {}: {} | ts: {} | fp: {}...\n",
245 link.seq,
246 status,
247 link.ts,
248 &link.f[..16]
249 ));
250 }
251 if let Some(reason) = &result.broken_reason {
252 out.push_str(&format!("\n ❌ {}\n", reason));
253 }
254 out.push_str("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
255 out
256 }
257
258 pub fn diff(a: &Chain, b: &Chain) -> ChainDiff {
260 let min_len = a.links.len().min(b.links.len());
261 let mut common_length = 0;
262
263 for i in 0..min_len {
264 if a.links[i].f == b.links[i].f {
265 common_length += 1;
266 } else {
267 return ChainDiff {
268 common_length,
269 a_extra: a.links.len() - common_length,
270 b_extra: b.links.len() - common_length,
271 diverges_at: Some(i + 1), };
273 }
274 }
275
276 ChainDiff {
277 common_length,
278 a_extra: a.links.len() - common_length,
279 b_extra: b.links.len() - common_length,
280 diverges_at: None, }
282 }
283
284 pub fn merge(a: &Chain, b: &Chain) -> Result<Chain, UniversalError> {
287 let diff = Chain::diff(a, b);
288 if let Some(pos) = diff.diverges_at {
289 return Err(UniversalError::ChainMergeConflict { diverges_at: pos });
290 }
291 if a.links.len() >= b.links.len() {
293 Ok(a.clone())
294 } else {
295 Ok(b.clone())
296 }
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub struct ChainDiff {
303 pub common_length: usize,
305 pub a_extra: usize,
307 pub b_extra: usize,
309 pub diverges_at: Option<usize>,
311}