1const MIN_MATCH_LENGTH_LARGE: usize = 16;
17const MIN_MATCH_LENGTH_SMALL: usize = 8;
19const MAX_MATCH_CANDIDATES: usize = 1024;
21const MATCH_CHUNK_SIZE: usize = 32;
23const MAX_COPY_LENGTH: usize = 0xFF_FFFF;
25const INDEX_BLOCK_SIZE: usize = 16;
27const MAX_INDEX_BYTES: usize = 4 * 1024 * 1024;
29const DENSE_INDEX_BELOW: usize = 1024;
31
32#[derive(Clone, Copy, Debug)]
33struct IndexEntry {
34 key: u32,
35 offset: u32,
36}
37
38#[derive(Debug)]
40pub struct DeltaIndex {
41 entries: Vec<IndexEntry>,
42}
43
44#[derive(Debug)]
46pub struct DeltaEncoder;
47
48impl DeltaEncoder {
49 pub fn new() -> Self {
51 Self
52 }
53
54 pub fn encode(base: &[u8], target: &[u8]) -> Vec<u8> {
56 if base.is_empty() {
57 return Self::encode_insert(target);
58 }
59
60 let index = Self::build_index(base);
61 Self::encode_with_index(&index, base, target)
62 }
63
64 pub fn encode_with_index(index: &DeltaIndex, base: &[u8], target: &[u8]) -> Vec<u8> {
66 if base.is_empty() {
67 return Self::encode_insert(target);
68 }
69
70 let min_match = Self::min_match_for(target.len());
71 let mut delta = Vec::new();
72 let mut pos = 0;
73 let mut key = Self::target_key(target, pos);
74
75 while pos < target.len() {
76 if let Some((offset, length)) =
77 Self::find_best_match(index, base, target, pos, key, min_match)
78 {
79 Self::emit_copy(&mut delta, offset, length);
80 pos += length;
81 key = Self::target_key(target, pos);
82 } else {
83 let start = pos;
84 while pos < target.len() && pos - start < 127 {
85 pos += 1;
86 key = Self::roll_target_key(key, target, pos);
87 if Self::find_best_match(index, base, target, pos, key, min_match).is_some() {
88 break;
89 }
90 }
91
92 let len = pos - start;
93 delta.push(len as u8 - 1);
94 delta.extend_from_slice(&target[start..pos]);
95 }
96 }
97
98 delta
99 }
100
101 pub fn estimate_delta_size(base: &[u8], target: &[u8]) -> usize {
103 if base.is_empty() {
104 return target.len() + target.len().div_ceil(128);
105 }
106
107 let index = Self::build_index(base);
108 Self::estimate_delta_size_with_index(&index, base, target)
109 }
110
111 pub fn estimate_delta_size_with_index(index: &DeltaIndex, base: &[u8], target: &[u8]) -> usize {
113 if base.is_empty() {
114 return target.len() + target.len().div_ceil(128);
115 }
116
117 let min_match = Self::min_match_for(target.len());
118 let mut size = 0usize;
119 let mut pos = 0;
120 let mut key = Self::target_key(target, pos);
121
122 while pos < target.len() {
123 if let Some((offset, length)) =
124 Self::find_best_match(index, base, target, pos, key, min_match)
125 {
126 size += Self::copy_instruction_size(offset, length);
127 pos += length;
128 key = Self::target_key(target, pos);
129 } else {
130 let start = pos;
131 while pos < target.len() && pos - start < 127 {
132 pos += 1;
133 key = Self::roll_target_key(key, target, pos);
134 if Self::find_best_match(index, base, target, pos, key, min_match).is_some() {
135 break;
136 }
137 }
138 size += 1 + (pos - start);
139 }
140 }
141
142 size
143 }
144
145 pub fn build_index(base: &[u8]) -> DeltaIndex {
147 if base.len() < 4 {
148 return DeltaIndex {
149 entries: Vec::new(),
150 };
151 }
152
153 let last_offset = (base.len() - 4).min(u32::MAX as usize);
156 let max_entries = MAX_INDEX_BYTES / size_of::<IndexEntry>();
157 let stride = if base.len() < DENSE_INDEX_BELOW {
161 1
162 } else {
163 (last_offset + 1)
164 .div_ceil(max_entries)
165 .max(INDEX_BLOCK_SIZE)
166 .next_multiple_of(INDEX_BLOCK_SIZE)
167 };
168 let mut entries = Vec::with_capacity(last_offset / stride + 1);
169
170 for offset in (0..=last_offset).step_by(stride) {
171 let key = u32::from_be_bytes([
172 base[offset],
173 base[offset + 1],
174 base[offset + 2],
175 base[offset + 3],
176 ]);
177 entries.push(IndexEntry {
178 key,
179 offset: offset as u32,
180 });
181 }
182 entries.sort_unstable_by_key(|entry| (entry.key, entry.offset));
183 DeltaIndex { entries }
184 }
185
186 fn emit_copy(delta: &mut Vec<u8>, offset: usize, length: usize) {
194 let mut remaining = length;
195 let mut offset = offset;
196 while remaining > 0 {
197 let chunk = remaining.min(MAX_COPY_LENGTH);
198 Self::emit_copy_instruction(delta, offset, chunk);
199 offset += chunk;
200 remaining -= chunk;
201 }
202 }
203
204 fn emit_copy_instruction(delta: &mut Vec<u8>, offset: usize, length: usize) {
205 let mut cmd: u8 = 0x80;
206 let offset = offset as u32;
207 let length = length as u32;
208
209 cmd |= 0x01; if offset & 0xFF00 != 0 {
214 cmd |= 0x02;
215 }
216 if offset & 0xFF_0000 != 0 {
217 cmd |= 0x04;
218 }
219 if offset & 0xFF00_0000 != 0 {
220 cmd |= 0x08;
221 }
222
223 if length != 0x10000 {
226 if length & 0xFF != 0 {
227 cmd |= 0x10;
228 }
229 if length & 0xFF00 != 0 {
230 cmd |= 0x20;
231 }
232 if length & 0xFF_0000 != 0 {
233 cmd |= 0x40;
234 }
235 }
236
237 delta.push(cmd);
238
239 delta.push(offset as u8); if offset & 0xFF00 != 0 {
242 delta.push((offset >> 8) as u8);
243 }
244 if offset & 0xFF_0000 != 0 {
245 delta.push((offset >> 16) as u8);
246 }
247 if offset & 0xFF00_0000 != 0 {
248 delta.push((offset >> 24) as u8);
249 }
250
251 if length != 0x10000 {
253 if length & 0xFF != 0 {
254 delta.push(length as u8);
255 }
256 if length & 0xFF00 != 0 {
257 delta.push((length >> 8) as u8);
258 }
259 if length & 0xFF_0000 != 0 {
260 delta.push((length >> 16) as u8);
261 }
262 }
263 }
264
265 fn copy_instruction_size(offset: usize, length: usize) -> usize {
267 let mut remaining = length;
268 let mut offset = offset;
269 let mut size = 0;
270 while remaining > 0 {
271 let chunk = remaining.min(MAX_COPY_LENGTH);
272 size += Self::copy_instruction_size_one(offset, chunk);
273 offset += chunk;
274 remaining -= chunk;
275 }
276 size
277 }
278
279 fn copy_instruction_size_one(offset: usize, length: usize) -> usize {
280 let offset = offset as u32;
281 let length = length as u32;
282 let mut n = 1 + 1; if offset & 0xFF00 != 0 {
286 n += 1;
287 }
288 if offset & 0xFF_0000 != 0 {
289 n += 1;
290 }
291 if offset & 0xFF00_0000 != 0 {
292 n += 1;
293 }
294
295 if length != 0x10000 {
297 if length & 0xFF != 0 {
298 n += 1;
299 }
300 if length & 0xFF00 != 0 {
301 n += 1;
302 }
303 if length & 0xFF_0000 != 0 {
304 n += 1;
305 }
306 }
307
308 n
309 }
310
311 fn min_match_for(target_len: usize) -> usize {
313 if target_len < 1024 {
314 MIN_MATCH_LENGTH_SMALL
315 } else {
316 MIN_MATCH_LENGTH_LARGE
317 }
318 }
319
320 fn encode_insert(data: &[u8]) -> Vec<u8> {
321 let mut delta = Vec::new();
322 for chunk in data.chunks(128) {
323 delta.push((chunk.len() - 1) as u8);
324 delta.extend_from_slice(chunk);
325 }
326 delta
327 }
328
329 fn find_best_match(
330 index: &DeltaIndex,
331 base: &[u8],
332 target: &[u8],
333 pos: usize,
334 key: Option<u32>,
335 min_match: usize,
336 ) -> Option<(usize, usize)> {
337 let key = key?;
338 let found = index
339 .entries
340 .binary_search_by_key(&key, |entry| entry.key)
341 .ok()?;
342 let first = index.entries[..found].partition_point(|entry| entry.key < key);
343 let last = found + index.entries[found..].partition_point(|entry| entry.key == key);
344 let offsets = &index.entries[first..last];
345
346 let mut best_offset = 0;
347 let mut best_length = 0;
348
349 let target_remaining = target.len() - pos;
350 let recent_start = offsets.len().saturating_sub(MAX_MATCH_CANDIDATES);
351 let mut examined = 0usize;
352
353 if recent_start > 0 {
354 let offset = offsets[0].offset as usize;
355 let length = Self::match_length(base, offset, target, pos);
356 if length > best_length {
357 best_length = length;
358 best_offset = offset;
359 }
360 if length == target_remaining {
361 return Some((best_offset, best_length));
362 }
363 examined += 1;
364 }
365
366 let remaining_budget = MAX_MATCH_CANDIDATES - examined;
367 let start = offsets.len().saturating_sub(remaining_budget);
368 for entry in &offsets[start..] {
369 let offset = entry.offset as usize;
370 let length = Self::match_length(base, offset, target, pos);
371 if length > best_length {
372 best_length = length;
373 best_offset = offset;
374 }
375 if length == target_remaining {
376 break;
377 }
378 }
379
380 if best_length >= min_match {
381 Some((best_offset, best_length))
382 } else {
383 None
384 }
385 }
386
387 fn target_key(target: &[u8], pos: usize) -> Option<u32> {
388 let bytes = target.get(pos..pos.checked_add(4)?)?;
389 Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
390 }
391
392 fn roll_target_key(key: Option<u32>, target: &[u8], pos: usize) -> Option<u32> {
393 let next_byte = *target.get(pos.checked_add(3)?)?;
394 Some((key? << 8) | u32::from(next_byte))
395 }
396
397 fn match_length(base: &[u8], base_pos: usize, target: &[u8], target_pos: usize) -> usize {
398 let max_len = (base.len() - base_pos).min(target.len() - target_pos).min(
401 (u32::MAX as usize)
402 .saturating_sub(base_pos)
403 .saturating_add(1),
404 );
405 let mut len = 0;
406 while len + MATCH_CHUNK_SIZE <= max_len
407 && base[base_pos + len..base_pos + len + MATCH_CHUNK_SIZE]
408 == target[target_pos + len..target_pos + len + MATCH_CHUNK_SIZE]
409 {
410 len += MATCH_CHUNK_SIZE;
411 }
412 while len < max_len && base[base_pos + len] == target[target_pos + len] {
413 len += 1;
414 }
415 len
416 }
417}
418
419impl Default for DeltaEncoder {
420 fn default() -> Self {
421 Self::new()
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::{DeltaEncoder, IndexEntry, MAX_INDEX_BYTES};
428
429 #[test]
430 fn index_memory_is_bounded() {
431 let base = vec![0u8; 16 * 1024 * 1024];
432 let index = DeltaEncoder::build_index(&base);
433 assert!(index.entries.capacity() * size_of::<IndexEntry>() <= MAX_INDEX_BYTES);
434 }
435}