use crate::{encode_str, fingerprint_str, UniversalError};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainLink {
pub seq: u64,
pub d: String,
pub f: String,
pub prev: Option<String>,
pub ts: u64,
}
impl ChainLink {
pub fn verify_data(&self) -> Result<String, UniversalError> {
use base64::{engine::general_purpose::STANDARD, Engine};
let bytes = STANDARD
.decode(&self.d)
.map_err(|e| UniversalError::DecodeError(e.to_string()))?;
let decoded =
String::from_utf8(bytes).map_err(|e| UniversalError::DecodeError(e.to_string()))?;
let data_fp = fingerprint_str(&decoded);
let actual_fp = match &self.prev {
Some(prev) => fingerprint_str(&format!("{}{}", data_fp, prev)),
None => data_fp,
};
if actual_fp != self.f {
return Err(UniversalError::IntegrityViolation {
expected: self.f.clone(),
actual: actual_fp,
});
}
Ok(decoded)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChainVerifyResult {
pub valid: bool,
pub total_links: usize,
pub broken_at: Option<usize>,
pub broken_reason: Option<String>,
}
impl std::fmt::Display for ChainVerifyResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.valid {
write!(f, "Valid chain ({} links)", self.total_links)
} else {
write!(
f,
"Broken at link {} of {}: {}",
self.broken_at.unwrap_or(0),
self.total_links,
self.broken_reason.as_deref().unwrap_or("unknown")
)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chain {
pub links: Vec<ChainLink>,
}
impl Chain {
#[must_use]
pub fn new(data: &str) -> Self {
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let link = ChainLink {
seq: 1,
d: encode_str(data),
f: fingerprint_str(data),
prev: None,
ts,
};
Self { links: vec![link] }
}
pub fn append(&mut self, data: &str) -> &ChainLink {
let prev_fp = self.links.last().map(|l| l.f.clone());
let seq = self.links.len() as u64 + 1;
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let combined = format!(
"{}{}",
fingerprint_str(data),
prev_fp.as_deref().unwrap_or("")
);
let chained_fp = fingerprint_str(&combined);
self.links.push(ChainLink {
seq,
d: encode_str(data),
f: chained_fp,
prev: prev_fp,
ts,
});
self.links.last().unwrap()
}
pub fn verify(&self) -> ChainVerifyResult {
if self.links.is_empty() {
return ChainVerifyResult {
valid: true,
total_links: 0,
broken_at: None,
broken_reason: None,
};
}
for (i, link) in self.links.iter().enumerate() {
if let Err(e) = link.verify_data() {
return ChainVerifyResult {
valid: false,
total_links: self.links.len(),
broken_at: Some(i + 1),
broken_reason: Some(format!("Data integrity: {}", e)),
};
}
if i > 0 {
let prev_fp = &self.links[i - 1].f;
if link.prev.as_deref() != Some(prev_fp.as_str()) {
return ChainVerifyResult {
valid: false,
total_links: self.links.len(),
broken_at: Some(i + 1),
broken_reason: Some(format!(
"Chain broken: link {} doesn't reference link {}",
i + 1,
i
)),
};
}
}
}
ChainVerifyResult {
valid: true,
total_links: self.links.len(),
broken_at: None,
broken_reason: None,
}
}
pub fn len(&self) -> usize {
self.links.len()
}
pub fn is_empty(&self) -> bool {
self.links.is_empty()
}
pub fn to_json(&self) -> Result<String, UniversalError> {
serde_json::to_string(self).map_err(|e| UniversalError::SerializationError(e.to_string()))
}
pub fn from_json(s: &str) -> Result<Self, UniversalError> {
serde_json::from_str(s).map_err(|e| UniversalError::SerializationError(e.to_string()))
}
pub fn report(&self) -> String {
let result = self.verify();
let mut out = String::new();
out.push_str("━━━━ Entrouter Universal Chain Report ━━━━\n");
out.push_str(&format!(
"Links: {} | Valid: {}\n\n",
self.links.len(),
result.valid
));
for link in &self.links {
let status = if result.broken_at == Some(link.seq as usize) {
"❌"
} else {
"✅"
};
out.push_str(&format!(
" Link {}: {} | ts: {} | fp: {}...\n",
link.seq,
status,
link.ts,
&link.f[..16]
));
}
if let Some(reason) = &result.broken_reason {
out.push_str(&format!("\n ❌ {}\n", reason));
}
out.push_str("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
out
}
pub fn diff(a: &Chain, b: &Chain) -> ChainDiff {
let min_len = a.links.len().min(b.links.len());
let mut common_length = 0;
for i in 0..min_len {
if a.links[i].f == b.links[i].f {
common_length += 1;
} else {
return ChainDiff {
common_length,
a_extra: a.links.len() - common_length,
b_extra: b.links.len() - common_length,
diverges_at: Some(i + 1), };
}
}
ChainDiff {
common_length,
a_extra: a.links.len() - common_length,
b_extra: b.links.len() - common_length,
diverges_at: None, }
}
pub fn merge(a: &Chain, b: &Chain) -> Result<Chain, UniversalError> {
let diff = Chain::diff(a, b);
if let Some(pos) = diff.diverges_at {
return Err(UniversalError::ChainMergeConflict { diverges_at: pos });
}
if a.links.len() >= b.links.len() {
Ok(a.clone())
} else {
Ok(b.clone())
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChainDiff {
pub common_length: usize,
pub a_extra: usize,
pub b_extra: usize,
pub diverges_at: Option<usize>,
}