1use std::collections::HashMap;
26
27use rayon::prelude::*;
28use serde::{Deserialize, Serialize};
29
30use crate::checksum::{RollingChecksum, StrongHash, strong_hash};
31
32pub const MIN_BLOCK: usize = 700;
34pub const MAX_BLOCK: usize = 131_072;
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub enum Op {
40 Copy(u32),
42 Literal(Vec<u8>),
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct Delta {
49 pub block_size: u32,
51 pub ops: Vec<Op>,
53}
54
55impl Delta {
56 #[must_use]
58 pub fn literal_bytes(&self) -> usize {
59 self.ops
60 .iter()
61 .map(|op| match op {
62 Op::Literal(b) => b.len(),
63 Op::Copy(_) => 0,
64 })
65 .sum()
66 }
67
68 #[must_use]
70 pub fn copy_ops(&self) -> usize {
71 self.ops.iter().filter(|o| matches!(o, Op::Copy(_))).count()
72 }
73}
74
75#[must_use]
77#[allow(
78 clippy::cast_precision_loss,
79 clippy::cast_sign_loss,
80 clippy::cast_possible_truncation
81)]
82pub fn block_size_for(old_len: usize) -> usize {
83 let approx = (old_len as f64).sqrt().round() as usize;
84 approx.clamp(MIN_BLOCK, MAX_BLOCK)
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct BlockSig {
90 pub weak: u32,
92 pub strong: StrongHash,
94 pub len: u32,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107pub struct Signature {
108 pub block_size: u32,
110 pub blocks: Vec<BlockSig>,
112}
113
114impl Signature {
115 #[must_use]
120 pub fn compute(old: &[u8], block_override: Option<usize>) -> Self {
121 let block = block_override.map_or_else(|| block_size_for(old.len()), |b| b.max(1));
122 let block_size = u32::try_from(block).unwrap_or(u32::MAX);
123 let blocks: Vec<BlockSig> = old
124 .par_chunks(block)
125 .map(|chunk| BlockSig {
126 weak: RollingChecksum::new(chunk).value(),
127 strong: strong_hash(chunk),
128 len: u32::try_from(chunk.len()).unwrap_or(u32::MAX),
129 })
130 .collect();
131 Self { block_size, blocks }
132 }
133
134 #[must_use]
136 pub fn len(&self) -> usize {
137 self.blocks.len()
138 }
139
140 #[must_use]
142 pub fn is_empty(&self) -> bool {
143 self.blocks.is_empty()
144 }
145}
146
147struct Lookup<'a> {
150 sig: &'a Signature,
151 by_weak: HashMap<u32, Vec<u32>>,
152}
153
154impl<'a> Lookup<'a> {
155 fn new(sig: &'a Signature) -> Self {
156 let mut by_weak: HashMap<u32, Vec<u32>> = HashMap::new();
157 for (i, block) in sig.blocks.iter().enumerate() {
158 let idx = u32::try_from(i).unwrap_or(u32::MAX);
159 by_weak.entry(block.weak).or_default().push(idx);
160 }
161 Self { sig, by_weak }
162 }
163
164 fn find(&self, weak: u32, window: &[u8]) -> Option<u32> {
166 let candidates = self.by_weak.get(&weak)?;
167 let mut strong: Option<StrongHash> = None;
168 for &bi in candidates {
169 let block = &self.sig.blocks[bi as usize];
170 if block.len as usize != window.len() {
171 continue;
172 }
173 let sh = *strong.get_or_insert_with(|| strong_hash(window));
174 if block.strong == sh {
175 return Some(bi);
176 }
177 }
178 None
179 }
180}
181
182#[must_use]
187pub fn encode_with_signature(sig: &Signature, new: &[u8]) -> Delta {
188 let block = sig.block_size as usize;
189 let block_size = sig.block_size;
190
191 if sig.blocks.is_empty() {
193 let ops = if new.is_empty() {
194 Vec::new()
195 } else {
196 vec![Op::Literal(new.to_vec())]
197 };
198 return Delta { block_size, ops };
199 }
200
201 let index = Lookup::new(sig);
202 let mut ops: Vec<Op> = Vec::new();
203 let n = new.len();
204 let mut pos = 0usize; let mut lit_start = 0usize; let flush = |ops: &mut Vec<Op>, from: usize, to: usize| {
208 if to > from {
209 ops.push(Op::Literal(new[from..to].to_vec()));
210 }
211 };
212
213 if n >= block {
214 let mut rc = RollingChecksum::new(&new[..block]);
215 loop {
216 let window = &new[pos..pos + block];
217 if let Some(bi) = index.find(rc.value(), window) {
218 flush(&mut ops, lit_start, pos);
219 ops.push(Op::Copy(bi));
220 pos += block;
221 lit_start = pos;
222 if pos + block <= n {
223 rc = RollingChecksum::new(&new[pos..pos + block]);
224 continue;
225 }
226 break;
227 }
228 if pos + block < n {
229 rc.roll(new[pos], new[pos + block]);
230 pos += 1;
231 } else {
232 break;
234 }
235 }
236 }
237
238 let remaining = &new[lit_start..n];
241 if !remaining.is_empty() {
242 let weak = RollingChecksum::new(remaining).value();
243 if let Some(bi) = index.find(weak, remaining) {
244 ops.push(Op::Copy(bi));
245 } else {
246 ops.push(Op::Literal(remaining.to_vec()));
247 }
248 }
249
250 Delta { block_size, ops }
251}
252
253#[must_use]
259pub fn encode(old: &[u8], new: &[u8], block_override: Option<usize>) -> Delta {
260 encode_with_signature(&Signature::compute(old, block_override), new)
261}
262
263pub fn apply(old: &[u8], delta: &Delta) -> crate::Result<Vec<u8>> {
270 let block = delta.block_size as usize;
271 let mut out: Vec<u8> = Vec::new();
272 for op in &delta.ops {
273 match op {
274 Op::Literal(bytes) => out.extend_from_slice(bytes),
275 Op::Copy(bi) => {
276 let start = (*bi as usize)
277 .checked_mul(block)
278 .ok_or_else(|| crate::Error::DeltaApply("block offset overflow".into()))?;
279 if start >= old.len() && !(start == 0 && old.is_empty()) {
280 return Err(crate::Error::DeltaApply(format!(
281 "copy block {bi} out of range"
282 )));
283 }
284 let end = (start + block).min(old.len());
285 out.extend_from_slice(&old[start..end]);
286 }
287 }
288 }
289 Ok(out)
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 fn roundtrip(old: &[u8], new: &[u8], block: Option<usize>) -> Delta {
297 let d = encode(old, new, block);
298 let rebuilt = apply(old, &d).expect("apply");
299 assert_eq!(rebuilt, new, "roundtrip mismatch");
300 d
301 }
302
303 #[test]
304 fn empty_old_empty_new() {
305 let d = roundtrip(b"", b"", None);
306 assert!(d.ops.is_empty());
307 }
308
309 #[test]
310 fn empty_old_some_new() {
311 let d = roundtrip(b"", b"hello world", None);
312 assert_eq!(d.ops, vec![Op::Literal(b"hello world".to_vec())]);
313 }
314
315 #[test]
316 fn some_old_empty_new() {
317 let d = roundtrip(b"abcdef", b"", None);
318 assert!(d.ops.is_empty());
319 }
320
321 #[test]
322 fn identical_is_all_copy_zero_literals() {
323 let old: Vec<u8> = (0..5000u32).map(|i| (i % 251) as u8).collect();
324 let d = roundtrip(&old, &old, Some(700));
325 assert_eq!(d.literal_bytes(), 0, "expected zero literals");
326 assert!(d.copy_ops() > 0);
327 assert!(d.ops.iter().all(|o| matches!(o, Op::Copy(_))));
328 }
329
330 #[test]
331 fn completely_different_is_all_literal() {
332 let old = vec![0u8; 4096];
333 let new = vec![0xFFu8; 4096];
334 let d = roundtrip(&old, &new, Some(700));
335 assert_eq!(d.copy_ops(), 0, "expected no copies");
336 assert_eq!(d.literal_bytes(), new.len());
337 }
338
339 #[test]
340 fn new_shorter_than_old() {
341 let old: Vec<u8> = (0..3000u32).map(|i| (i % 97) as u8).collect();
342 let new = &old[..1234];
343 roundtrip(&old, new, Some(700));
344 }
345
346 #[test]
347 fn new_longer_than_old() {
348 let old: Vec<u8> = (0..3000u32).map(|i| (i % 97) as u8).collect();
349 let mut new = old.clone();
350 new.extend(std::iter::repeat_n(0xAB, 2000));
351 let d = roundtrip(&old, &new, Some(700));
352 assert!(d.copy_ops() > 0, "prefix should be copied");
353 }
354
355 #[test]
356 fn single_byte_change_middle() {
357 let old: Vec<u8> = (0..8000u32).map(|i| (i % 211) as u8).collect();
358 let mut new = old.clone();
359 new[4000] ^= 0xFF;
360 let d = roundtrip(&old, &new, Some(700));
361 assert!(d.copy_ops() > 0);
363 assert!(
364 d.literal_bytes() < 2 * 700,
365 "only the changed block should differ"
366 );
367 }
368
369 #[test]
370 fn insertion_at_front_shifts_everything() {
371 let old: Vec<u8> = (0..8000u32).map(|i| (i % 197) as u8).collect();
373 let mut new = vec![1u8, 2, 3, 4, 5];
374 new.extend_from_slice(&old);
375 let d = roundtrip(&old, &new, Some(700));
376 assert!(d.copy_ops() > 0, "shifted blocks must still be found");
377 assert!(d.literal_bytes() <= 5 + 700);
379 }
380
381 #[test]
382 fn block_size_clamps() {
383 assert_eq!(block_size_for(0), MIN_BLOCK);
384 assert_eq!(block_size_for(100), MIN_BLOCK);
385 assert_eq!(block_size_for(1_000_000), 1000);
386 assert_eq!(block_size_for(usize::MAX), MAX_BLOCK);
387 }
388
389 #[test]
390 fn apply_rejects_out_of_range_copy() {
391 let bad = Delta {
392 block_size: 4,
393 ops: vec![Op::Copy(999)],
394 };
395 assert!(apply(b"tiny", &bad).is_err());
396 }
397
398 #[test]
399 fn strong_len_is_16() {
400 assert_eq!(crate::checksum::STRONG_LEN, 16);
401 }
402}