use base64::{engine::general_purpose::STANDARD, Engine};
use sha2::{Digest, Sha256};
use thiserror::Error;
pub mod chain;
pub mod envelope;
pub mod guardian;
pub mod signed_envelope;
pub mod universal_struct;
pub mod verify;
#[cfg(feature = "compression")]
pub mod compress;
pub use chain::Chain;
pub use chain::ChainDiff;
pub use envelope::Envelope;
pub use guardian::Guardian;
pub use signed_envelope::SignedEnvelope;
pub use universal_struct::UniversalStruct;
pub use verify::VerifyResult;
#[derive(Debug, Clone, PartialEq, Error)]
#[non_exhaustive]
pub enum UniversalError {
#[error("Integrity violation: data was mutated in transit. Expected {expected}, got {actual}")]
IntegrityViolation { expected: String, actual: String },
#[error("Decode error: {0}")]
DecodeError(String),
#[error("Envelope malformed: {0}")]
MalformedEnvelope(String),
#[error("Expired: envelope expired at {expired_at}, current time {now}")]
Expired { expired_at: u64, now: u64 },
#[error("Compress error: {0}")]
CompressError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Chain merge conflict: chains diverge at link {diverges_at}")]
ChainMergeConflict { diverges_at: usize },
}
#[must_use]
pub fn encode(input: &[u8]) -> String {
STANDARD.encode(input)
}
pub fn decode(input: &str) -> Result<Vec<u8>, UniversalError> {
STANDARD
.decode(input)
.map_err(|e| UniversalError::DecodeError(e.to_string()))
}
#[must_use]
pub fn encode_str(input: &str) -> String {
encode(input.as_bytes())
}
pub fn decode_str(input: &str) -> Result<String, UniversalError> {
let bytes = decode(input)?;
String::from_utf8(bytes).map_err(|e| UniversalError::DecodeError(e.to_string()))
}
#[must_use]
pub fn fingerprint(input: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(input);
hex::encode(hasher.finalize())
}
#[must_use]
pub fn fingerprint_str(input: &str) -> String {
fingerprint(input.as_bytes())
}
pub fn verify(encoded: &str, original_fingerprint: &str) -> Result<VerifyResult, UniversalError> {
let decoded = decode(encoded)?;
let actual_fingerprint = fingerprint(&decoded);
if actual_fingerprint == original_fingerprint {
Ok(VerifyResult {
intact: true,
decoded,
fingerprint: actual_fingerprint,
})
} else {
Err(UniversalError::IntegrityViolation {
expected: original_fingerprint.to_string(),
actual: actual_fingerprint,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn round_trip_special_chars() {
let original = r#"hello "world" it's \fine\ with 日本語 and 🔥"#;
assert_eq!(original, decode_str(&encode_str(original)).unwrap());
}
#[test]
fn envelope_standard() {
let data = r#"{"token":"abc\"def","user":"john's"}"#;
let env = Envelope::wrap(data);
assert_eq!(data, env.unwrap_verified().unwrap());
}
#[test]
fn envelope_url_safe() {
let data = "race_token: abc\"123\"\nspecial chars & stuff";
let env = Envelope::wrap_url_safe(data);
assert!(env
.d
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_'));
assert_eq!(data, env.unwrap_verified().unwrap());
}
#[cfg(feature = "compression")]
#[test]
fn envelope_compressed() {
let data = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".repeat(100);
let env = Envelope::wrap_compressed(&data).unwrap();
assert!(env.d.len() < data.len());
assert_eq!(data, env.unwrap_verified().unwrap());
}
#[test]
fn envelope_ttl_valid() {
let env = Envelope::wrap_with_ttl("fresh data", 60);
assert!(!env.is_expired());
assert_eq!("fresh data", env.unwrap_verified().unwrap());
}
#[test]
fn envelope_ttl_expired() {
let env = Envelope::wrap_with_ttl("stale data", 0);
sleep(Duration::from_millis(10));
assert!(env.is_expired());
assert!(env.unwrap_verified().is_err());
}
#[test]
fn envelope_detects_mutation() {
let env = Envelope::wrap("original");
let mut json = env.to_json().unwrap();
let idx = json.find('"').unwrap() + 5;
json.replace_range(idx..idx + 1, "X");
let tampered = Envelope::from_json(&json);
let result = tampered.and_then(|e| e.unwrap_verified());
assert!(result.is_err());
}
#[test]
fn chain_builds_and_verifies() {
let mut chain = Chain::new("genesis: race started");
chain.append("link 2: user_a joined");
chain.append("link 3: user_b joined");
chain.append("link 4: winner = user_a");
let result = chain.verify();
assert!(result.valid);
assert_eq!(result.total_links, 4);
}
#[test]
fn chain_detects_tampering() {
let mut chain = Chain::new("genesis");
chain.append("link 2");
chain.append("link 3");
let mut tampered = chain.clone();
tampered.links[1].d = encode_str("TAMPERED");
let result = tampered.verify();
assert!(!result.valid);
assert_eq!(result.broken_at, Some(2));
}
#[test]
fn chain_serialises_round_trip() {
let mut chain = Chain::new("start");
chain.append("middle");
chain.append("end");
let json = chain.to_json().unwrap();
let restored = Chain::from_json(&json).unwrap();
assert!(restored.verify().valid);
}
#[test]
fn struct_wraps_all_fields() {
let wrapped = UniversalStruct::wrap_fields(&[
("token", "000001739850123456-abc\"def"),
("user_id", "john's account"),
("amount", "99.99"),
]);
let result = wrapped.verify_all();
assert!(result.all_intact);
assert_eq!(wrapped.get("token").unwrap(), "000001739850123456-abc\"def");
assert_eq!(wrapped.get("user_id").unwrap(), "john's account");
assert_eq!(wrapped.get("amount").unwrap(), "99.99");
}
#[test]
fn struct_detects_field_mutation() {
let mut wrapped = UniversalStruct::wrap_fields(&[
("token", "abc123"),
("user_id", "john"),
("amount", "99.99"),
]);
wrapped.fields[2].d = encode_str("999999.99");
let result = wrapped.verify_all();
assert!(!result.all_intact);
assert!(result.violations.contains(&"amount".to_string()));
assert!(result.fields[0].intact);
assert!(result.fields[1].intact);
assert!(!result.fields[2].intact);
}
#[test]
fn struct_to_map() {
let wrapped = UniversalStruct::wrap_fields(&[("a", "hello"), ("b", "world")]);
let map = wrapped.to_map().unwrap();
assert_eq!(map["a"], "hello");
assert_eq!(map["b"], "world");
}
#[test]
fn struct_serialises_round_trip() {
let wrapped =
UniversalStruct::wrap_fields(&[("token", r#"abc"def\ghi"#), ("user", "john")]);
let json = wrapped.to_json().unwrap();
let restored = UniversalStruct::from_json(&json).unwrap();
restored.assert_intact();
assert_eq!(restored.get("token").unwrap(), r#"abc"def\ghi"#);
}
#[test]
fn guardian_clean_pipeline() {
let mut g = Guardian::new("clean data 🔥");
let encoded = g.encoded().to_string();
g.checkpoint("http", &encoded);
g.checkpoint("redis", &encoded);
g.checkpoint("postgres", &encoded);
g.assert_intact();
}
#[test]
fn guardian_finds_violation() {
let mut g = Guardian::new("original");
let clean = g.encoded().to_string();
g.checkpoint("http", &clean);
g.checkpoint("redis", &encode_str("mangled"));
assert_eq!(g.first_violation().unwrap().layer, "redis");
}
}