use crate::error::PrivacyError;
use crate::types::FiveDimCoord;
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::{format, vec::Vec};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AffineTransform {
pub a: [f64; 25],
pub b: [f64; 5],
pub prob: f64,
}
impl AffineTransform {
pub fn identity() -> Self {
let mut a = [0.0f64; 25];
for i in 0..5 { a[i * 5 + i] = 1.0; }
Self { a, b: [0.0; 5], prob: 0.2 }
}
#[allow(clippy::needless_range_loop)] pub fn apply(&self, x: &[f64; 5]) -> [f64; 5] {
let mut result = [0.0f64; 5];
for i in 0..5 {
for j in 0..5 {
result[i] += self.a[i * 5 + j] * x[j];
}
result[i] += self.b[i];
}
result
}
pub fn apply_inverse_1d(&self, y: f64, dim: usize) -> f64 {
let a_diag = self.a[dim * 5 + dim];
if a_diag.abs() < 1e-12 {
0.0
} else {
(y - self.b[dim]) / a_diag
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IfsCodebook {
pub transforms: Vec<AffineTransform>,
pub dims_reduced: usize,
pub chaos_seed: [u8; 32],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompressedBlob {
pub payload: Vec<u8>,
pub stored: bool,
pub original_size: usize,
pub original_hash: Vec<u8>,
}
impl CompressedBlob {
pub fn ratio(&self) -> f64 {
if self.original_size == 0 {
return 1.0;
}
self.payload.len() as f64 / self.original_size as f64
}
}
pub struct FractalCompressor;
impl FractalCompressor {
pub fn new() -> Self {
Self
}
#[allow(clippy::needless_range_loop)] pub fn compress(
&self,
coord: &FiveDimCoord,
chaos_seed: &[u8; 32],
target_dims: usize,
) -> Result<IfsCodebook, PrivacyError> {
if target_dims == 0 || target_dims > 5 {
return Err(PrivacyError::CompressionFailed(
format!("target_dims must be 1–5, got {target_dims}")
));
}
let x = [
coord.spatial,
coord.temporal,
coord.probabilistic,
coord.quantum,
coord.chaotic,
];
let mut transforms = Vec::new();
for i in 0..target_dims {
let mut t = AffineTransform::identity();
let delta = self.chaos_delta(chaos_seed, i as u64);
for j in 0..25 {
t.a[j] *= 0.5 + delta * 0.1; }
t.b[i] = x[i] * (1.0 - 0.5 - delta * 0.1);
t.prob = 1.0 / target_dims as f64;
transforms.push(t);
}
Ok(IfsCodebook {
transforms,
dims_reduced: 5 - target_dims,
chaos_seed: *chaos_seed,
})
}
pub fn compress_bytes(&self, data: &[u8]) -> Result<CompressedBlob, PrivacyError> {
if data.is_empty() {
return Err(PrivacyError::CompressionFailed("Empty data".into()));
}
let original_hash = Sha256::digest(data).to_vec();
let deflated = miniz_oxide::deflate::compress_to_vec(data, 6);
let (payload, stored) = if deflated.len() < data.len() {
(deflated, false)
} else {
(data.to_vec(), true)
};
Ok(CompressedBlob {
payload,
stored,
original_size: data.len(),
original_hash,
})
}
pub fn decompress(&self, codebook: &IfsCodebook) -> Result<FiveDimCoord, PrivacyError> {
if codebook.transforms.is_empty() {
return Err(PrivacyError::DecompressionFailed("Empty codebook".into()));
}
let mut x = [0.0f64; 5];
for step in 0..100u64 {
let t_idx = (step as usize) % codebook.transforms.len();
x = codebook.transforms[t_idx].apply(&x);
}
Ok(FiveDimCoord {
spatial: x[0],
temporal: x[1],
probabilistic: x[2],
quantum: x[3],
chaotic: x[4],
})
}
pub fn decompress_bytes(blob: &CompressedBlob) -> Result<Vec<u8>, PrivacyError> {
let result = if blob.stored {
blob.payload.clone()
} else {
miniz_oxide::inflate::decompress_to_vec(&blob.payload)
.map_err(|e| PrivacyError::DecompressionFailed(format!("inflate failed: {e:?}")))?
};
if result.len() != blob.original_size {
return Err(PrivacyError::DecompressionFailed(format!(
"length mismatch: recovered {} bytes, expected {}",
result.len(),
blob.original_size
)));
}
if Sha256::digest(&result).to_vec() != blob.original_hash {
return Err(PrivacyError::DecompressionFailed(
"hash mismatch: data corrupted".into(),
));
}
Ok(result)
}
pub fn compress_and_verify(
&self,
data: &[u8],
) -> Result<(CompressedBlob, Vec<u8>), PrivacyError> {
let blob = self.compress_bytes(data)?;
let decompressed = Self::decompress_bytes(&blob)?;
if decompressed != data {
return Err(PrivacyError::DecompressionFailed(
"Round-trip verification failed: decompressed data does not match original".into()
));
}
Ok((blob, decompressed))
}
pub fn compression_ratio(original_dims: usize, codebook: &IfsCodebook) -> f64 {
let original_size = original_dims * 8; let compressed_size = codebook.transforms.len() * (25 + 5 + 1) * 8;
original_size as f64 / compressed_size as f64
}
fn chaos_delta(&self, chaos_seed: &[u8; 32], counter: u64) -> f64 {
let mut hasher = Sha256::new();
hasher.update(chaos_seed);
hasher.update(counter.to_le_bytes());
hasher.update(b"ifs-delta");
let h: [u8; 32] = hasher.finalize().into();
let raw = u64::from_le_bytes(h[..8].try_into().unwrap_or([0u8; 8]));
(raw as f64 / u64::MAX as f64) * 2.0 - 1.0 }
}
impl Default for FractalCompressor {
fn default() -> Self {
Self::new()
}
}
pub fn reduce_5d_to_3d(coords: &[FiveDimCoord]) -> Vec<[f64; 3]> {
if coords.is_empty() {
return Vec::new();
}
let n = coords.len() as f64;
let dims: [[f64; 5]; 1] = [[0.0; 5]]; let _ = dims;
let all_dims: Vec<[f64; 5]> = coords.iter().map(|c| [
c.spatial,
c.temporal,
c.probabilistic,
c.quantum,
c.chaotic,
]).collect();
let mut means = [0.0f64; 5];
for pt in &all_dims {
for d in 0..5 {
means[d] += pt[d];
}
}
for m in means.iter_mut() {
*m /= n;
}
let mut variances = [0.0f64; 5];
for pt in &all_dims {
for d in 0..5 {
let diff = pt[d] - means[d];
variances[d] += diff * diff;
}
}
for v in variances.iter_mut() {
*v /= n;
}
let mut dim_indices: [usize; 5] = [0, 1, 2, 3, 4];
for i in 1..5 {
let mut j = i;
while j > 0 && variances[dim_indices[j]] > variances[dim_indices[j - 1]] {
dim_indices.swap(j, j - 1);
j -= 1;
}
}
let top3 = [dim_indices[0], dim_indices[1], dim_indices[2]];
all_dims.iter().map(|pt| [pt[top3[0]], pt[top3[1]], pt[top3[2]]]).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compress_decompress() {
let compressor = FractalCompressor::new();
let seed = [0u8; 32];
let coord = FiveDimCoord {
spatial: 0.5, temporal: 1000.0, probabilistic: 1e-6,
quantum: 1.2, chaotic: 0.8,
};
let codebook = compressor.compress(&coord, &seed, 3).unwrap();
assert_eq!(codebook.dims_reduced, 2);
let decompressed = compressor.decompress(&codebook).unwrap();
assert!(decompressed.spatial.is_finite());
}
#[test]
fn test_invalid_dims() {
let compressor = FractalCompressor::new();
let seed = [0u8; 32];
let coord = FiveDimCoord::zero();
assert!(compressor.compress(&coord, &seed, 0).is_err());
assert!(compressor.compress(&coord, &seed, 6).is_err());
}
#[test]
fn test_compress_bytes_round_trip() {
let compressor = FractalCompressor::new();
let data = b"Hello, compression world! This is a test of lossless byte compression.";
let blob = compressor.compress_bytes(data).unwrap();
assert_eq!(blob.original_size, data.len());
let decompressed = FractalCompressor::decompress_bytes(&blob).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_compressible_input_actually_shrinks() {
let compressor = FractalCompressor::new();
let data = alloc::vec![b'A'; 4096];
let blob = compressor.compress_bytes(&data).unwrap();
assert!(!blob.stored, "repetitive input should compress, not fall back to stored");
assert!(
blob.payload.len() < data.len(),
"payload {} not smaller than input {}",
blob.payload.len(),
data.len()
);
assert!(blob.ratio() < 1.0);
assert_eq!(FractalCompressor::decompress_bytes(&blob).unwrap(), data);
}
#[test]
fn test_incompressible_input_falls_back_to_stored() {
let compressor = FractalCompressor::new();
let mut data = Vec::new();
for i in 0..64u32 {
data.extend_from_slice(&Sha256::digest(i.to_le_bytes()));
}
let blob = compressor.compress_bytes(&data).unwrap();
assert!(blob.stored, "high-entropy input should fall back to stored");
assert_eq!(blob.payload.len(), data.len(), "stored payload must not grow");
assert_eq!(blob.ratio(), 1.0);
assert_eq!(FractalCompressor::decompress_bytes(&blob).unwrap(), data);
}
#[test]
fn test_corrupted_payload_is_rejected() {
let compressor = FractalCompressor::new();
let data = alloc::vec![b'B'; 2048];
let mut blob = compressor.compress_bytes(&data).unwrap();
blob.original_hash[0] ^= 0xFF;
assert!(FractalCompressor::decompress_bytes(&blob).is_err());
}
#[test]
fn test_compress_and_verify() {
let compressor = FractalCompressor::new();
let data: Vec<u8> = (0..256).map(|i| i as u8).collect();
let (blob, decompressed) = compressor.compress_and_verify(&data).unwrap();
assert_eq!(decompressed, data);
assert_eq!(blob.original_size, 256);
}
#[test]
fn test_empty_input_is_an_error() {
let compressor = FractalCompressor::new();
assert!(compressor.compress_bytes(&[]).is_err());
}
#[test]
fn test_reduce_5d_to_3d() {
let coords = vec![
FiveDimCoord { spatial: 1.0, temporal: 2.0, probabilistic: 3.0, quantum: 4.0, chaotic: 5.0 },
FiveDimCoord { spatial: 2.0, temporal: 3.0, probabilistic: 4.0, quantum: 5.0, chaotic: 6.0 },
FiveDimCoord { spatial: 3.0, temporal: 4.0, probabilistic: 5.0, quantum: 6.0, chaotic: 7.0 },
];
let reduced = reduce_5d_to_3d(&coords);
assert_eq!(reduced.len(), 3);
for pt in &reduced {
assert!(pt[0].is_finite());
assert!(pt[1].is_finite());
assert!(pt[2].is_finite());
}
}
#[test]
fn test_reduce_5d_to_3d_empty() {
let reduced = reduce_5d_to_3d(&[]);
assert!(reduced.is_empty());
}
}