heddle_format/delta/
delta_encoder.rs1use std::collections::HashMap;
16
17const MIN_MATCH_LENGTH_LARGE: usize = 16;
19const MIN_MATCH_LENGTH_SMALL: usize = 8;
21const MAX_MATCH_CANDIDATES: usize = 1024;
23const MATCH_CHUNK_SIZE: usize = 32;
25
26#[derive(Debug)]
28pub struct DeltaEncoder;
29
30impl DeltaEncoder {
31 pub fn new() -> Self {
33 Self
34 }
35
36 pub fn encode(base: &[u8], target: &[u8]) -> Vec<u8> {
38 if base.is_empty() {
39 return Self::encode_insert(target);
40 }
41
42 let index = Self::build_index(base);
43 Self::encode_with_index(&index, base, target)
44 }
45
46 pub fn encode_with_index(
48 index: &HashMap<[u8; 4], Vec<usize>>,
49 base: &[u8],
50 target: &[u8],
51 ) -> Vec<u8> {
52 if base.is_empty() {
53 return Self::encode_insert(target);
54 }
55
56 let min_match = Self::min_match_for(target.len());
57 let mut delta = Vec::new();
58 let mut pos = 0;
59
60 while pos < target.len() {
61 if let Some((offset, length)) =
62 Self::find_best_match(index, base, target, pos, min_match)
63 {
64 Self::emit_copy(&mut delta, offset, length);
65 pos += length;
66 } else {
67 let start = pos;
68 while pos < target.len() && pos - start < 127 {
69 if Self::find_best_match(index, base, target, pos, min_match).is_some() {
70 break;
71 }
72 pos += 1;
73 }
74
75 let len = pos - start;
76 delta.push(len as u8 - 1);
77 delta.extend_from_slice(&target[start..pos]);
78 }
79 }
80
81 delta
82 }
83
84 pub fn estimate_delta_size(base: &[u8], target: &[u8]) -> usize {
86 if base.is_empty() {
87 return target.len() + target.len().div_ceil(128);
88 }
89
90 let index = Self::build_index(base);
91 Self::estimate_delta_size_with_index(&index, base, target)
92 }
93
94 pub fn estimate_delta_size_with_index(
96 index: &HashMap<[u8; 4], Vec<usize>>,
97 base: &[u8],
98 target: &[u8],
99 ) -> usize {
100 if base.is_empty() {
101 return target.len() + target.len().div_ceil(128);
102 }
103
104 let min_match = Self::min_match_for(target.len());
105 let mut size = 0usize;
106 let mut pos = 0;
107
108 while pos < target.len() {
109 if let Some((offset, length)) =
110 Self::find_best_match(index, base, target, pos, min_match)
111 {
112 size += Self::copy_instruction_size(offset, length);
113 pos += length;
114 } else {
115 let start = pos;
116 while pos < target.len() && pos - start < 127 {
117 if Self::find_best_match(index, base, target, pos, min_match).is_some() {
118 break;
119 }
120 pos += 1;
121 }
122 size += 1 + (pos - start);
123 }
124 }
125
126 size
127 }
128
129 pub fn build_index(base: &[u8]) -> HashMap<[u8; 4], Vec<usize>> {
131 let mut index: HashMap<[u8; 4], Vec<usize>> = HashMap::new();
132
133 for i in 0..base.len().saturating_sub(4) {
134 let key = [base[i], base[i + 1], base[i + 2], base[i + 3]];
135 index.entry(key).or_default().push(i);
136 }
137
138 index
139 }
140
141 fn emit_copy(delta: &mut Vec<u8>, offset: usize, length: usize) {
149 let mut cmd: u8 = 0x80;
150 let offset = offset as u32;
151 let length = length as u32;
152
153 cmd |= 0x01; if offset & 0xFF00 != 0 {
158 cmd |= 0x02;
159 }
160 if offset & 0xFF_0000 != 0 {
161 cmd |= 0x04;
162 }
163 if offset & 0xFF00_0000 != 0 {
164 cmd |= 0x08;
165 }
166
167 if length != 0x10000 {
170 if length & 0xFF != 0 {
171 cmd |= 0x10;
172 }
173 if length & 0xFF00 != 0 {
174 cmd |= 0x20;
175 }
176 if length & 0xFF_0000 != 0 {
177 cmd |= 0x40;
178 }
179 }
180
181 delta.push(cmd);
182
183 delta.push(offset as u8); if offset & 0xFF00 != 0 {
186 delta.push((offset >> 8) as u8);
187 }
188 if offset & 0xFF_0000 != 0 {
189 delta.push((offset >> 16) as u8);
190 }
191 if offset & 0xFF00_0000 != 0 {
192 delta.push((offset >> 24) as u8);
193 }
194
195 if length != 0x10000 {
197 if length & 0xFF != 0 {
198 delta.push(length as u8);
199 }
200 if length & 0xFF00 != 0 {
201 delta.push((length >> 8) as u8);
202 }
203 if length & 0xFF_0000 != 0 {
204 delta.push((length >> 16) as u8);
205 }
206 }
207 }
208
209 fn copy_instruction_size(offset: usize, length: usize) -> usize {
211 let offset = offset as u32;
212 let length = length as u32;
213 let mut n = 1 + 1; if offset & 0xFF00 != 0 {
217 n += 1;
218 }
219 if offset & 0xFF_0000 != 0 {
220 n += 1;
221 }
222 if offset & 0xFF00_0000 != 0 {
223 n += 1;
224 }
225
226 if length != 0x10000 {
228 if length & 0xFF != 0 {
229 n += 1;
230 }
231 if length & 0xFF00 != 0 {
232 n += 1;
233 }
234 if length & 0xFF_0000 != 0 {
235 n += 1;
236 }
237 }
238
239 n
240 }
241
242 fn min_match_for(target_len: usize) -> usize {
244 if target_len < 1024 {
245 MIN_MATCH_LENGTH_SMALL
246 } else {
247 MIN_MATCH_LENGTH_LARGE
248 }
249 }
250
251 fn encode_insert(data: &[u8]) -> Vec<u8> {
252 let mut delta = Vec::new();
253 for chunk in data.chunks(128) {
254 delta.push((chunk.len() - 1) as u8);
255 delta.extend_from_slice(chunk);
256 }
257 delta
258 }
259
260 fn find_best_match(
261 index: &HashMap<[u8; 4], Vec<usize>>,
262 base: &[u8],
263 target: &[u8],
264 pos: usize,
265 min_match: usize,
266 ) -> Option<(usize, usize)> {
267 if pos + 4 > target.len() {
268 return None;
269 }
270
271 let key = [
272 target[pos],
273 target[pos + 1],
274 target[pos + 2],
275 target[pos + 3],
276 ];
277 let offsets = index.get(&key)?;
278
279 let mut best_offset = 0;
280 let mut best_length = 0;
281
282 let target_remaining = target.len() - pos;
283 let recent_start = offsets.len().saturating_sub(MAX_MATCH_CANDIDATES);
284 let mut examined = 0usize;
285
286 if recent_start > 0 {
287 let offset = offsets[0];
288 let length = Self::match_length(base, offset, target, pos);
289 if length > best_length {
290 best_length = length;
291 best_offset = offset;
292 }
293 if length == target_remaining {
294 return Some((best_offset, best_length));
295 }
296 examined += 1;
297 }
298
299 let remaining_budget = MAX_MATCH_CANDIDATES - examined;
300 let start = offsets.len().saturating_sub(remaining_budget);
301 for &offset in &offsets[start..] {
302 let length = Self::match_length(base, offset, target, pos);
303 if length > best_length {
304 best_length = length;
305 best_offset = offset;
306 }
307 if length == target_remaining {
308 break;
309 }
310 }
311
312 if best_length >= min_match {
313 Some((best_offset, best_length))
314 } else {
315 None
316 }
317 }
318
319 fn match_length(base: &[u8], base_pos: usize, target: &[u8], target_pos: usize) -> usize {
320 let max_len = (base.len() - base_pos).min(target.len() - target_pos);
321 let mut len = 0;
322 while len + MATCH_CHUNK_SIZE <= max_len
323 && base[base_pos + len..base_pos + len + MATCH_CHUNK_SIZE]
324 == target[target_pos + len..target_pos + len + MATCH_CHUNK_SIZE]
325 {
326 len += MATCH_CHUNK_SIZE;
327 }
328 while len < max_len && base[base_pos + len] == target[target_pos + len] {
329 len += 1;
330 }
331 len
332 }
333}
334
335impl Default for DeltaEncoder {
336 fn default() -> Self {
337 Self::new()
338 }
339}