1use crate::error::{Error, Result};
7
8pub(crate) const WINDOW: usize = 16;
10pub(crate) const MIN_COPY: usize = 4;
12pub(crate) const COPY_CHUNK: usize = 0x10000;
14
15pub(crate) fn write_delta_varint(out: &mut Vec<u8>, mut n: usize) {
16 loop {
17 let mut b = (n & 0x7f) as u8;
18 n >>= 7;
19 if n != 0 {
20 b |= 0x80;
21 }
22 out.push(b);
23 if n == 0 {
24 break;
25 }
26 }
27}
28
29pub(crate) fn push_copy(out: &mut Vec<u8>, mut offset: usize, mut size: usize) -> Result<()> {
31 if size == 0 {
32 return Ok(());
33 }
34 while size > 0 {
35 let chunk = size.min(COPY_CHUNK);
36 let moff = offset;
37 let msize = chunk;
38
39 let mut op = 0x80u8;
40 if moff & 0x0000_00ff != 0 {
41 op |= 0x01;
42 }
43 if moff & 0x0000_ff00 != 0 {
44 op |= 0x02;
45 }
46 if moff & 0x00ff_0000 != 0 {
47 op |= 0x04;
48 }
49 if moff & 0xff00_0000 != 0 {
50 op |= 0x08;
51 }
52 if msize != COPY_CHUNK {
53 if msize & 0x00ff != 0 {
54 op |= 0x10;
55 }
56 if msize & 0xff00 != 0 {
57 op |= 0x20;
58 }
59 }
60
61 out.push(op);
62 if op & 0x01 != 0 {
63 out.push(moff as u8);
64 }
65 if op & 0x02 != 0 {
66 out.push((moff >> 8) as u8);
67 }
68 if op & 0x04 != 0 {
69 out.push((moff >> 16) as u8);
70 }
71 if op & 0x08 != 0 {
72 out.push((moff >> 24) as u8);
73 }
74 if msize != COPY_CHUNK {
75 if op & 0x10 != 0 {
76 out.push(msize as u8);
77 }
78 if op & 0x20 != 0 {
79 out.push((msize >> 8) as u8);
80 }
81 }
82
83 offset = offset.saturating_add(chunk);
84 size -= chunk;
85 }
86 Ok(())
87}
88
89pub(crate) fn push_insert(out: &mut Vec<u8>, mut data: &[u8]) {
90 while !data.is_empty() {
91 let n = data.len().min(127);
92 out.push(n as u8);
93 out.extend_from_slice(&data[..n]);
94 data = &data[n..];
95 }
96}
97
98pub struct DeltaIndex {
100 inner: crate::pack_roll_delta::BaseMatchCatalog,
101}
102
103impl DeltaIndex {
104 #[must_use]
106 pub fn new(base: &[u8]) -> Self {
107 Self {
108 inner: crate::pack_roll_delta::BaseMatchCatalog::build(base),
109 }
110 }
111
112 #[must_use]
114 pub(crate) fn from_shared(base: std::sync::Arc<[u8]>) -> Self {
115 Self {
116 inner: crate::pack_roll_delta::BaseMatchCatalog::build_shared(base),
117 }
118 }
119
120 #[must_use]
122 pub fn index_stride(&self) -> usize {
123 WINDOW
124 }
125
126 pub fn encode(&self, target: &[u8], max_size: usize) -> Option<Vec<u8>> {
128 self.inner.encode(target, max_size)
129 }
130}
131
132pub fn encode_delta(base: &[u8], target: &[u8], max_size: usize) -> Option<Vec<u8>> {
134 DeltaIndex::new(base).encode(target, max_size)
135}
136
137pub fn encode_prefix_extension_delta(base: &[u8], target: &[u8]) -> Result<Vec<u8>> {
139 if !target.starts_with(base) || target.len() <= base.len() {
140 return Err(Error::CorruptObject(
141 "encode_prefix_extension_delta: target must strictly extend base".into(),
142 ));
143 }
144 let mut out = Vec::new();
145 write_delta_varint(&mut out, base.len());
146 write_delta_varint(&mut out, target.len());
147 push_copy(&mut out, 0, base.len())?;
148 push_insert(&mut out, &target[base.len()..]);
149 Ok(out)
150}
151
152pub fn encode_lcp_delta(base: &[u8], target: &[u8]) -> Result<Vec<u8>> {
154 encode_delta(base, target, 0)
155 .ok_or_else(|| Error::CorruptObject("encode_lcp_delta: delta encoding failed".into()))
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::unpack_objects::apply_delta;
162
163 fn assert_roundtrip(base: &[u8], target: &[u8]) {
164 let delta = encode_delta(base, target, 0).expect("encode");
165 let got = apply_delta(base, &delta).expect("apply");
166 assert_eq!(
167 got,
168 target,
169 "base_len={} target_len={}",
170 base.len(),
171 target.len()
172 );
173 }
174
175 #[test]
176 fn empty_target_produces_valid_delta() {
177 let base = b"non-empty base";
178 let delta = DeltaIndex::new(base).encode(b"", 0).expect("encode");
179 assert_eq!(apply_delta(base, &delta).expect("apply"), b"" as &[u8]);
180 }
181
182 #[test]
183 fn empty_target_respects_max_size_before_return() {
184 let base = b"non-empty base";
185 assert!(DeltaIndex::new(base).encode(b"", 1).is_none());
186 }
187
188 #[test]
189 fn empty_base_and_target_roundtrip() {
190 assert_roundtrip(b"", b"");
191 }
192
193 #[test]
194 fn roundtrip_prefix_delta() {
195 let base = b"hello world".repeat(100);
196 let mut target = base.clone();
197 target.extend_from_slice(b"\nextra suffix\n");
198 assert_roundtrip(&base, &target);
199 }
200
201 #[test]
202 fn roundtrip_lcp_delta() {
203 assert_roundtrip(b"padding\n9", b"padding\n10");
204 }
205
206 #[test]
207 fn roundtrip_mid_file_insertion() {
208 let mut lines: Vec<String> = (1..=200)
209 .map(|i| format!("The quick brown fox jumps over the lazy dog line {i}\n"))
210 .collect();
211 let base = lines.join("");
212 lines.insert(100, "inserted: extra line for delta test\n".to_owned());
213 let target = lines.join("");
214 let delta = encode_delta(base.as_bytes(), target.as_bytes(), 0).expect("encode");
215 assert!(
216 delta.len() * 100 <= target.len() * 105,
217 "delta {} bytes vs target {} (want <=5%)",
218 delta.len(),
219 target.len()
220 );
221 assert_roundtrip(base.as_bytes(), target.as_bytes());
222 }
223
224 #[test]
225 fn roundtrip_mid_file_line_replace() {
226 let mut lines: Vec<String> = (1..=200)
227 .map(|i| format!("The quick brown fox jumps over the lazy dog line {i}\n"))
228 .collect();
229 let base = lines.join("");
230 lines[100] = "edit: replaced line content for delta test\n".to_owned();
231 let target = lines.join("");
232 let delta = encode_delta(base.as_bytes(), target.as_bytes(), 0).expect("encode");
233 assert!(
234 delta.len() * 100 <= target.len() * 105,
235 "delta {} bytes vs target {} (want <=5%)",
236 delta.len(),
237 target.len()
238 );
239 assert_roundtrip(base.as_bytes(), target.as_bytes());
240 }
241
242 #[test]
243 fn roundtrip_random_edits() {
244 let mut seed = 0x767u64;
245 for _ in 0..250 {
246 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
247 let base_len = (seed % 8000 + 32) as usize;
248 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
249 let target_len = (seed % 8000) as usize;
250 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
251
252 let mut base = vec![0u8; base_len];
253 for b in &mut base {
254 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
255 *b = (seed >> 32) as u8;
256 }
257
258 let mut target = if target_len == 0 {
259 Vec::new()
260 } else {
261 vec![0u8; target_len]
262 };
263 let shared = base_len.min(target_len);
264 target[..shared].copy_from_slice(&base[..shared]);
265
266 let op = (seed % 5) as u8;
267 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
268 match op {
269 0 if target_len > shared => {
270 for t in &mut target[shared..] {
271 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
272 *t = (seed >> 40) as u8;
273 }
274 }
275 1 if target_len > 0 && base_len > 0 => {
276 let i = (seed as usize) % target_len;
277 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
278 target[i] = (seed >> 33) as u8;
279 }
280 2 if target_len > 4 => {
281 let i = (seed as usize) % (target_len - 1);
282 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
283 target.remove(i);
284 }
285 3 if target_len > 0 => {
286 let i = (seed as usize) % (target_len + 1);
287 seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
288 target.insert(i, (seed >> 34) as u8);
289 }
290 _ => {}
291 }
292
293 assert_roundtrip(&base, &target);
294 }
295 }
296
297 #[test]
298 fn real_world_source_edit() {
299 let base = include_str!("delta_encode.rs");
300 let mut target = base.to_owned();
301 target.insert_str(base.len() / 3, "\npub fn new_api() {}\n");
302 assert_roundtrip(base.as_bytes(), target.as_bytes());
303 }
304
305 #[test]
306 fn max_size_rejects_large_delta() {
307 let base = vec![0u8; 10_000];
308 let target = vec![1u8; 10_000];
309 assert!(encode_delta(&base, &target, 50).is_none());
310 }
311
312 #[test]
313 fn prefix_extension_requires_strict_suffix() {
314 let base = b"same";
315 let err = encode_prefix_extension_delta(base, base).unwrap_err();
316 assert!(matches!(err, Error::CorruptObject(_)));
317 }
318
319 #[test]
320 fn prefix_extension_splits_large_copy_runs() {
321 let base = vec![0xABu8; 70_000];
322 let mut target = base.clone();
323 target.push(b'!');
324 let delta = encode_prefix_extension_delta(&base, &target).unwrap();
325 let got = apply_delta(&base, &delta).unwrap();
326 assert_eq!(got, target);
327 }
328
329 #[test]
330 fn delta_index_reuse_across_targets() {
331 let base = b"abcdef".repeat(500);
332 let index = DeltaIndex::new(&base);
333 for n in 1..20 {
334 let mut target = base.clone();
335 target.extend_from_slice(&b"X".repeat(n));
336 let d1 = index.encode(&target, 0).unwrap();
337 let d2 = encode_delta(&base, &target, 0).unwrap();
338 assert_eq!(d1, d2);
339 assert_eq!(apply_delta(&base, &d1).unwrap(), target);
340 }
341 }
342
343 #[test]
344 fn index_stride_matches_git_window() {
345 let index = DeltaIndex::new(b"0123456789abcdef");
346 assert_eq!(index.index_stride(), WINDOW);
347 }
348
349 #[test]
350 fn large_base_roundtrip_extension() {
351 let base = vec![0u8; 512 * 1024];
352 let mut target = base.clone();
353 target.push(1);
354 assert_roundtrip(&base, &target);
355 }
356
357 #[test]
358 fn single_byte_mid_edit_encodes_small() {
359 let base = vec![b'a'; 50_000];
360 let mut target = base.clone();
361 target[25_000] = b'X';
362 let delta = encode_delta(&base, &target, 0).expect("delta");
363 assert!(delta.len() < 200, "delta {} bytes", delta.len());
364 }
365
366 #[test]
367 fn repetitive_lines_one_byte_edit_encodes_small() {
368 let lines: Vec<String> = (1..=500)
369 .map(|i| format!("line {i:05} lorem ipsum dolor sit amet"))
370 .collect();
371 let base = lines.join("\n");
372 let mut target = base.as_bytes().to_vec();
373 target[9000] ^= 0x01;
374 let delta = encode_delta(base.as_bytes(), &target, 0).expect("delta");
375 assert!(
376 delta.len() < 200,
377 "delta {} base {}",
378 delta.len(),
379 base.len()
380 );
381 }
382
383 #[test]
384 fn issue_1011_consecutive_blob_pair_from_repo_fixture() {
385 let base = include_bytes!("../tests/fixtures/issue_1011_base.bin");
386 let target = include_bytes!("../tests/fixtures/issue_1011_target.bin");
387 let delta = encode_delta(base, target, 0).expect("encode");
388 assert_eq!(apply_delta(base, &delta).expect("apply"), target.as_slice());
389 assert!(
390 delta.len() < target.len() / 10,
391 "delta {} vs target {} (expect Git-scale compression)",
392 delta.len(),
393 target.len()
394 );
395 }
396
397 #[test]
398 fn multi_line_edit_step_encodes_small() {
399 let line = " some moderately long line of text to make blob bigger abcdefghijklmnop\n";
400 let mut body = String::new();
401 while body.len() < 90 * 1024 {
402 let n = body.lines().count() + 1;
403 body.push_str(&format!("{n}{line}"));
404 }
405 let base = body.as_bytes();
406 let mut lines: Vec<String> = body.lines().map(str::to_owned).collect();
407 let stride = lines.len() / 16;
408 for k in 0..15 {
409 let idx = (3 + k * stride).min(lines.len().saturating_sub(1));
410 lines[idx] = format!("edit 1-{k}");
411 }
412 let target = lines.join("\n");
413 let delta = encode_delta(base, target.as_bytes(), 0).expect("encode");
414 assert!(
415 delta.len() < target.len() / 10,
416 "delta {} vs target {}",
417 delta.len(),
418 target.len()
419 );
420 }
421
422 #[test]
423 fn issue_1011_single_line_edit_in_large_blob() {
424 let lines: Vec<String> = (1..=3000)
425 .map(|i| {
426 format!(
427 "{i} some moderately long line of text to make blob bigger abcdefghijklmnop"
428 )
429 })
430 .collect();
431 let base = lines.join("\n");
432 let mut target = base.as_bytes().to_vec();
433 target[base.len() / 3] ^= 0x01;
435 let target = target;
436 let max = target.len() / 2 - 20;
437 let delta_unlim = encode_delta(base.as_bytes(), &target, 0).expect("unlim");
438 let delta_max = encode_delta(base.as_bytes(), &target, max);
439 assert!(
440 delta_max.is_some(),
441 "encode_delta None with max_size {max} (unlim len {})",
442 delta_unlim.len()
443 );
444 let delta = delta_max.unwrap();
445 assert!(
446 delta.len() < target.len() / 10,
447 "delta {} vs target {}",
448 delta.len(),
449 target.len()
450 );
451 }
452}