use crate::{DrivenError, Result};
#[derive(Debug, Clone)]
pub struct XorPatch {
pub block_size: u32,
pub target_len: u32,
pub block_count: u32,
pub blocks: Vec<(u32, Vec<u8>)>,
}
impl XorPatch {
pub fn empty() -> Self {
Self {
block_size: 64,
target_len: 0,
block_count: 0,
blocks: Vec::new(),
}
}
pub fn apply(&self, original: &[u8]) -> Result<Vec<u8>> {
let mut result = original.to_vec();
for (block_idx, xor_data) in &self.blocks {
let start = (*block_idx as usize) * (self.block_size as usize);
let _end = (start + self.block_size as usize).min(result.len());
if start >= result.len() {
result.resize(start + xor_data.len(), 0);
}
for (i, &xor_byte) in xor_data.iter().enumerate() {
if start + i < result.len() {
result[start + i] ^= xor_byte;
}
}
}
result.resize(self.target_len as usize, 0);
Ok(result)
}
pub fn serialize(&self) -> Vec<u8> {
let mut output = Vec::new();
output.extend_from_slice(&self.block_size.to_le_bytes());
output.extend_from_slice(&self.target_len.to_le_bytes());
output.extend_from_slice(&self.block_count.to_le_bytes());
for (idx, data) in &self.blocks {
output.extend_from_slice(&idx.to_le_bytes());
output.extend_from_slice(&(data.len() as u32).to_le_bytes());
output.extend_from_slice(data);
}
output
}
pub fn deserialize(data: &[u8]) -> Result<Self> {
if data.len() < 12 {
return Err(DrivenError::InvalidBinary("XOR patch too small".into()));
}
let block_size = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let target_len = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let block_count = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let mut blocks = Vec::with_capacity(block_count as usize);
let mut pos = 12;
for _ in 0..block_count {
if pos + 8 > data.len() {
return Err(DrivenError::InvalidBinary("Truncated XOR patch".into()));
}
let idx = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
let len =
u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]]);
pos += 8;
if pos + len as usize > data.len() {
return Err(DrivenError::InvalidBinary("Truncated block data".into()));
}
blocks.push((idx, data[pos..pos + len as usize].to_vec()));
pos += len as usize;
}
Ok(Self {
block_size,
target_len,
block_count,
blocks,
})
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
pub fn savings(&self, original_size: usize) -> f32 {
let patch_size = self.serialize().len();
if original_size == 0 {
return 0.0;
}
1.0 - (patch_size as f32 / original_size as f32)
}
}
#[derive(Debug)]
pub struct XorPatcher {
block_size: u32,
}
impl XorPatcher {
pub fn new(block_size: u32) -> Self {
Self { block_size }
}
pub fn compute(&self, old: &[u8], new: &[u8]) -> XorPatch {
let block_size = self.block_size as usize;
let max_len = old.len().max(new.len());
let num_blocks = max_len.div_ceil(block_size);
let mut blocks = Vec::new();
for block_idx in 0..num_blocks {
let start = block_idx * block_size;
let old_end = (start + block_size).min(old.len());
let new_end = (start + block_size).min(new.len());
let old_block = if start < old.len() {
&old[start..old_end]
} else {
&[]
};
let new_block = if start < new.len() {
&new[start..new_end]
} else {
&[]
};
if old_block != new_block {
let mut xor_data = vec![0u8; block_size];
for (i, &new_byte) in new_block.iter().enumerate() {
let old_byte = old_block.get(i).copied().unwrap_or(0);
xor_data[i] = old_byte ^ new_byte;
}
for i in old_block.len()..new_block.len() {
xor_data[i] = new_block[i];
}
while xor_data.last() == Some(&0) {
xor_data.pop();
}
if !xor_data.is_empty() {
blocks.push((block_idx as u32, xor_data));
}
}
}
XorPatch {
block_size: self.block_size,
target_len: new.len() as u32,
block_count: blocks.len() as u32,
blocks,
}
}
pub fn is_identical(&self, old: &[u8], new: &[u8]) -> bool {
old == new
}
}
impl Default for XorPatcher {
fn default() -> Self {
Self::new(64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xor_patch_roundtrip() {
let old = b"Hello, World! This is a test.";
let new = b"Hello, Rust! This is a test.";
let patcher = XorPatcher::new(8);
let patch = patcher.compute(old, new);
assert!(!patch.is_empty());
let result = patch.apply(old).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_identical_files() {
let data = b"Same content";
let patcher = XorPatcher::new(8);
assert!(patcher.is_identical(data, data));
let patch = patcher.compute(data, data);
assert!(patch.is_empty());
}
#[test]
fn test_savings() {
let old = vec![0u8; 1000];
let mut new = old.clone();
new[500] = 1;
let patcher = XorPatcher::new(64);
let patch = patcher.compute(&old, &new);
let savings = patch.savings(old.len());
assert!(savings > 0.9); }
}