use alloc::vec::Vec;
use crate::error::{Error, Result};
pub mod builder;
pub mod applier;
pub use builder::DeltaBuilder;
pub use applier::DeltaApplier;
pub const DELTA_MAGIC: &[u8] = b"SDF\x01";
pub const DELTA_VERSION: u8 = 1;
pub const MAX_HUNK_SIZE: usize = 65536;
#[derive(Clone, Debug, PartialEq)]
pub struct Delta {
pub source_size: u64,
pub target_size: u64,
pub ops: Vec<Op>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Op {
Copy {
src_offset: u64,
length: u64,
},
Insert {
data: Vec<u8>,
},
}
impl Delta {
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let mut result = Vec::new();
result.extend_from_slice(DELTA_MAGIC);
result.push(DELTA_VERSION);
result.extend_from_slice(&self.source_size.to_le_bytes());
result.extend_from_slice(&self.target_size.to_le_bytes());
result.extend_from_slice(&(self.ops.len() as u32).to_le_bytes());
for op in &self.ops {
match op {
Op::Copy { src_offset, length } => {
result.push(0x01); result.extend_from_slice(&src_offset.to_le_bytes());
result.extend_from_slice(&length.to_le_bytes());
}
Op::Insert { data } => {
if data.len() > MAX_HUNK_SIZE {
return Err(Error::delta(format!(
"insert hunk too large: {}", data.len()
)));
}
result.push(0x02); result.extend_from_slice(&(data.len() as u32).to_le_bytes());
result.extend_from_slice(data);
}
}
}
Ok(result)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 30 {
return Err(Error::delta("delta too small for header"));
}
if &bytes[0..4] != DELTA_MAGIC {
return Err(Error::delta("invalid delta magic"));
}
if bytes[4] != DELTA_VERSION {
return Err(Error::delta(format!(
"unsupported delta version: {}", bytes[4]
)));
}
let source_size = u64::from_le_bytes([
bytes[5], bytes[6], bytes[7], bytes[8],
bytes[9], bytes[10], bytes[11], bytes[12],
]);
let target_size = u64::from_le_bytes([
bytes[13], bytes[14], bytes[15], bytes[16],
bytes[17], bytes[18], bytes[19], bytes[20],
]);
let op_count = u32::from_le_bytes([bytes[21], bytes[22], bytes[23], bytes[24]]) as usize;
let mut ops = Vec::with_capacity(op_count);
let mut pos = 25;
for _ in 0..op_count {
if pos >= bytes.len() {
return Err(Error::delta("truncated delta ops"));
}
let opcode = bytes[pos];
pos += 1;
match opcode {
0x01 => { if pos + 16 > bytes.len() {
return Err(Error::delta("truncated copy op"));
}
let src_offset = u64::from_le_bytes([
bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3],
bytes[pos+4], bytes[pos+5], bytes[pos+6], bytes[pos+7],
]);
let length = u64::from_le_bytes([
bytes[pos+8], bytes[pos+9], bytes[pos+10], bytes[pos+11],
bytes[pos+12], bytes[pos+13], bytes[pos+14], bytes[pos+15],
]);
pos += 16;
ops.push(Op::Copy { src_offset, length });
}
0x02 => { if pos + 4 > bytes.len() {
return Err(Error::delta("truncated insert header"));
}
let len = u32::from_le_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
pos += 4;
if pos + len > bytes.len() {
return Err(Error::delta("truncated insert data"));
}
if len > MAX_HUNK_SIZE {
return Err(Error::delta("insert hunk exceeds maximum"));
}
ops.push(Op::Insert {
data: bytes[pos..pos+len].to_vec(),
});
pos += len;
}
_ => return Err(Error::delta(format!("unknown opcode: {}", opcode))),
}
}
Ok(Self {
source_size,
target_size,
ops,
})
}
pub fn patch_size(&self) -> usize {
self.ops.iter().map(|op| match op {
Op::Copy { .. } => 17, Op::Insert { data } => 5 + data.len(), }).sum()
}
}
pub fn create_simple_delta(old: &[u8], new: &[u8]) -> Vec<u8> {
DeltaBuilder::new()
.build(old, new)
.and_then(|d| d.to_bytes())
.unwrap_or_else(|_| {
let mut result = DELTA_MAGIC.to_vec();
result.push(DELTA_VERSION);
result.extend_from_slice(&(old.len() as u64).to_le_bytes());
result.extend_from_slice(&(new.len() as u64).to_le_bytes());
result.extend_from_slice(&1u32.to_le_bytes()); result.push(0x02); result.extend_from_slice(&(new.len() as u32).to_le_bytes());
result.extend_from_slice(new);
result
})
}
pub fn apply_delta(source: &[u8], delta: &Delta) -> Result<Vec<u8>> {
if source.len() as u64 != delta.source_size {
return Err(Error::delta(format!(
"source size mismatch: expected {}, got {}",
delta.source_size, source.len()
)));
}
let mut result = Vec::with_capacity(delta.target_size as usize);
for op in &delta.ops {
match op {
Op::Copy { src_offset, length } => {
let start = *src_offset as usize;
let end = start + *length as usize;
if end > source.len() {
return Err(Error::delta("copy extends past source end"));
}
result.extend_from_slice(&source[start..end]);
}
Op::Insert { data } => {
result.extend_from_slice(data);
}
}
}
if result.len() as u64 != delta.target_size {
return Err(Error::delta(format!(
"result size mismatch: expected {}, got {}",
delta.target_size, result.len()
)));
}
Ok(result)
}