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