use crate::DCPError;
use blake3;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct XorDelta {
pub prev_hash: [u8; 32],
pub new_hash: [u8; 32],
pub patch: Vec<u8>,
pub new_len: usize,
}
impl XorDelta {
pub fn compute(prev: &[u8], new: &[u8]) -> Self {
let max_len = prev.len().max(new.len());
let mut xor = Vec::with_capacity(max_len);
for i in 0..max_len {
let prev_byte = prev.get(i).copied().unwrap_or(0);
let new_byte = new.get(i).copied().unwrap_or(0);
xor.push(prev_byte ^ new_byte);
}
Self {
prev_hash: *blake3::hash(prev).as_bytes(),
new_hash: *blake3::hash(new).as_bytes(),
patch: rle_compress(&xor),
new_len: new.len(),
}
}
pub fn apply(&self, state: &[u8]) -> Result<Vec<u8>, DCPError> {
let current_hash = *blake3::hash(state).as_bytes();
if current_hash != self.prev_hash {
return Err(DCPError::HashMismatch);
}
let xor = rle_decompress(&self.patch);
let mut new_state = Vec::with_capacity(self.new_len);
for i in 0..self.new_len {
let state_byte = state.get(i).copied().unwrap_or(0);
let xor_byte = xor.get(i).copied().unwrap_or(0);
new_state.push(state_byte ^ xor_byte);
}
let result_hash = *blake3::hash(&new_state).as_bytes();
if result_hash != self.new_hash {
return Err(DCPError::HashMismatch);
}
Ok(new_state)
}
pub fn patch_size(&self) -> usize {
self.patch.len()
}
pub fn is_sparse(&self, full_state_size: usize) -> bool {
self.patch.len() < full_state_size
}
pub fn verify_prev_hash(&self, state: &[u8]) -> bool {
let current_hash = *blake3::hash(state).as_bytes();
current_hash == self.prev_hash
}
}
fn rle_compress(data: &[u8]) -> Vec<u8> {
if data.is_empty() {
return Vec::new();
}
let mut result = Vec::new();
let mut i = 0;
while i < data.len() {
let value = data[i];
let mut count = 1u8;
while i + (count as usize) < data.len()
&& data[i + (count as usize)] == value
&& count < 255
{
count += 1;
}
result.push(count);
result.push(value);
i += count as usize;
}
result
}
fn rle_decompress(data: &[u8]) -> Vec<u8> {
let mut result = Vec::new();
let mut i = 0;
while i + 1 < data.len() {
let count = data[i] as usize;
let value = data[i + 1];
for _ in 0..count {
result.push(value);
}
i += 2;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rle_compress_decompress() {
let data = vec![0, 0, 0, 1, 1, 2, 2, 2, 2, 0];
let compressed = rle_compress(&data);
let decompressed = rle_decompress(&compressed);
assert_eq!(decompressed, data);
}
#[test]
fn test_rle_empty() {
let data: Vec<u8> = vec![];
let compressed = rle_compress(&data);
let decompressed = rle_decompress(&compressed);
assert!(compressed.is_empty());
assert!(decompressed.is_empty());
}
#[test]
fn test_rle_single_byte() {
let data = vec![42];
let compressed = rle_compress(&data);
let decompressed = rle_decompress(&compressed);
assert_eq!(decompressed, data);
}
#[test]
fn test_rle_long_run() {
let data = vec![0u8; 300];
let compressed = rle_compress(&data);
let decompressed = rle_decompress(&compressed);
assert_eq!(decompressed, data);
assert_eq!(compressed.len(), 4);
}
#[test]
fn test_delta_basic() {
let prev = b"hello world";
let new = b"hello rust!";
let delta = XorDelta::compute(prev, new);
let result = delta.apply(prev).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_delta_sparse_change() {
let mut prev = vec![0u8; 1000];
let mut new = prev.clone();
new[500] = 1;
let delta = XorDelta::compute(&prev, &new);
assert!(delta.is_sparse(new.len()));
let result = delta.apply(&prev).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_delta_hash_mismatch() {
let prev = b"hello";
let new = b"world";
let delta = XorDelta::compute(prev, new);
let wrong_state = b"wrong";
let result = delta.apply(wrong_state);
assert_eq!(result, Err(DCPError::HashMismatch));
}
#[test]
fn test_delta_different_lengths() {
let prev = b"short";
let new = b"much longer string";
let delta = XorDelta::compute(prev, new);
let result = delta.apply(prev).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_delta_shrinking() {
let prev = b"much longer string";
let new = b"short";
let delta = XorDelta::compute(prev, new);
let result = delta.apply(prev).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_delta_empty_states() {
let prev: &[u8] = b"";
let new = b"new data";
let delta = XorDelta::compute(prev, new);
let result = delta.apply(prev).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_delta_to_empty() {
let prev = b"old data";
let new: &[u8] = b"";
let delta = XorDelta::compute(prev, new);
let result = delta.apply(prev).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_verify_prev_hash() {
let prev = b"hello";
let new = b"world";
let delta = XorDelta::compute(prev, new);
assert!(delta.verify_prev_hash(prev));
assert!(!delta.verify_prev_hash(new));
}
}