1use crate::object::MkitError;
25
26pub const STREAM_VERSION: u8 = 0x01;
28pub const OP_COPY: u8 = 0x80;
30pub const MAX_INSERT_LEN: usize = 127;
32pub const HEADER_LEN: usize = 1 + 4 + 4;
34
35const BLOCK_SIZE: usize = 16;
38
39pub(crate) const CAP_MULTIPLIER: usize = 256;
48
49#[inline]
64pub(crate) fn compute_cap_hint(result_len: usize, _base_len: usize, stream_len: usize) -> usize {
65 result_len.min(stream_len.saturating_mul(CAP_MULTIPLIER))
66}
67
68pub fn encode(base: &[u8], result: &[u8]) -> Result<Vec<u8>, MkitError> {
89 use std::collections::HashMap;
90
91 check_length_bounds(base.len(), result.len())?;
92
93 let mut out = Vec::with_capacity(HEADER_LEN + result.len());
94 write_header(&mut out, base.len(), result.len());
95
96 let num_blocks = base.len() / BLOCK_SIZE;
100 let mut index: HashMap<u64, u32> = HashMap::with_capacity(num_blocks);
101 for i in 0..num_blocks {
102 let pos = i * BLOCK_SIZE;
103 if let Ok(pos_u32) = u32::try_from(pos) {
104 let block = &base[pos..pos + BLOCK_SIZE];
105 let h = block_hash(block);
106 index.entry(h).or_insert(pos_u32);
107 } else {
108 break; }
110 }
111
112 let mut insert_buf: Vec<u8> = Vec::with_capacity(MAX_INSERT_LEN);
113 let mut ti = 0usize;
114 while ti < result.len() {
115 let mut matched = false;
116 if ti + BLOCK_SIZE <= result.len() {
117 let target_block = &result[ti..ti + BLOCK_SIZE];
118 let h = block_hash(target_block);
119 if let Some(&base_pos) = index.get(&h) {
120 let base_pos_usize = base_pos as usize;
121 if &base[base_pos_usize..base_pos_usize + BLOCK_SIZE] == target_block {
122 flush_insert(&mut out, &mut insert_buf);
123
124 let mut match_len = BLOCK_SIZE;
126 while base_pos_usize + match_len < base.len()
127 && ti + match_len < result.len()
128 && base[base_pos_usize + match_len] == result[ti + match_len]
129 && match_len < u16::MAX as usize
130 {
131 match_len += 1;
132 }
133 emit_copy(
135 &mut out,
136 base_pos,
137 u16::try_from(match_len).expect("<= u16::MAX"),
138 );
139 ti += match_len;
140 matched = true;
141 }
142 }
143 }
144 if !matched {
145 insert_buf.push(result[ti]);
146 ti += 1;
147 if insert_buf.len() == MAX_INSERT_LEN {
148 flush_insert(&mut out, &mut insert_buf);
149 }
150 }
151 }
152 flush_insert(&mut out, &mut insert_buf);
153 Ok(out)
154}
155
156pub(crate) fn check_length_bounds(base_len: usize, result_len: usize) -> Result<(), MkitError> {
160 if u32::try_from(base_len).is_err() {
161 return Err(MkitError::DeltaLengthOverflow {
162 field: "base_len",
163 len: base_len,
164 });
165 }
166 if u32::try_from(result_len).is_err() {
167 return Err(MkitError::DeltaLengthOverflow {
168 field: "result_len",
169 len: result_len,
170 });
171 }
172 Ok(())
173}
174
175pub fn decode(base: &[u8], stream: &[u8]) -> Result<Vec<u8>, MkitError> {
193 if stream.len() < HEADER_LEN {
194 return Err(MkitError::UnexpectedEof);
195 }
196 if stream[0] != STREAM_VERSION {
197 return Err(MkitError::UnsupportedObjectVersion);
198 }
199 let base_len = u32::from_le_bytes(stream[1..5].try_into().expect("4 bytes")) as usize;
200 let result_len = u32::from_le_bytes(stream[5..9].try_into().expect("4 bytes")) as usize;
201 if base_len != base.len() {
202 return Err(MkitError::TrailingData);
203 }
204
205 let cap_hint = compute_cap_hint(result_len, base.len(), stream.len());
212 let mut out: Vec<u8> = Vec::with_capacity(cap_hint);
213 let mut pos = HEADER_LEN;
214 while pos < stream.len() {
215 let op = stream[pos];
216 pos += 1;
217 if op & 0x80 != 0 {
218 if op & 0x7F != 0 {
220 return Err(MkitError::TrailingData);
221 }
222 if pos + 6 > stream.len() {
223 return Err(MkitError::UnexpectedEof);
224 }
225 let offset =
226 u32::from_le_bytes(stream[pos..pos + 4].try_into().expect("4 bytes")) as usize;
227 pos += 4;
228 let length =
229 u16::from_le_bytes(stream[pos..pos + 2].try_into().expect("2 bytes")) as usize;
230 pos += 2;
231 if length == 0 {
232 return Err(MkitError::TrailingData);
233 }
234 let end = offset.checked_add(length).ok_or(MkitError::TrailingData)?;
238 if end > base.len() {
239 return Err(MkitError::TrailingData);
240 }
241 if out.len().checked_add(length).is_none_or(|v| v > result_len) {
243 return Err(MkitError::TrailingData);
244 }
245 out.extend_from_slice(&base[offset..end]);
246 } else if op > 0 {
247 let length = op as usize;
249 if pos + length > stream.len() {
250 return Err(MkitError::UnexpectedEof);
251 }
252 if out.len().checked_add(length).is_none_or(|v| v > result_len) {
253 return Err(MkitError::TrailingData);
254 }
255 out.extend_from_slice(&stream[pos..pos + length]);
256 pos += length;
257 } else {
258 return Err(MkitError::TrailingData);
260 }
261 }
262 if out.len() != result_len {
263 return Err(MkitError::TrailingData);
264 }
265 Ok(out)
266}
267
268fn write_header(out: &mut Vec<u8>, base_len: usize, result_len: usize) {
271 let bl: u32 = u32::try_from(base_len).expect("base_len <= u32::MAX (checked)");
276 let rl: u32 = u32::try_from(result_len).expect("result_len <= u32::MAX (checked)");
277 out.push(STREAM_VERSION);
278 out.extend_from_slice(&bl.to_le_bytes());
279 out.extend_from_slice(&rl.to_le_bytes());
280}
281
282fn emit_copy(out: &mut Vec<u8>, offset: u32, length: u16) {
283 out.push(OP_COPY);
284 out.extend_from_slice(&offset.to_le_bytes());
285 out.extend_from_slice(&length.to_le_bytes());
286}
287
288fn flush_insert(out: &mut Vec<u8>, buf: &mut Vec<u8>) {
289 if buf.is_empty() {
290 return;
291 }
292 debug_assert!(buf.len() <= MAX_INSERT_LEN);
293 out.push(u8::try_from(buf.len()).expect("<= 127"));
294 out.extend_from_slice(buf);
295 buf.clear();
296}
297
298fn block_hash(block: &[u8]) -> u64 {
299 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
301 for &b in block {
302 h ^= u64::from(b);
303 h = h.wrapping_mul(0x0000_0001_0000_01b3);
304 }
305 h
306}
307
308#[cfg(test)]
313mod tests {
314 use super::*;
315
316 fn header(base_len: u32, result_len: u32) -> [u8; HEADER_LEN] {
317 let mut h = [0u8; HEADER_LEN];
318 h[0] = STREAM_VERSION;
319 h[1..5].copy_from_slice(&base_len.to_le_bytes());
320 h[5..9].copy_from_slice(&result_len.to_le_bytes());
321 h
322 }
323
324 #[test]
325 fn identity_roundtrip() {
326 let data = b"0123456789abcdef".repeat(4); let stream = encode(&data, &data).unwrap();
328 let restored = decode(&data, &stream).unwrap();
329 assert_eq!(restored, data);
330 }
331
332 #[test]
333 fn pure_insert_roundtrip() {
334 let base = b"aaa";
335 let target = b"zzz";
336 let stream = encode(base, target).unwrap();
337 assert_eq!(stream[HEADER_LEN] & 0x80, 0);
339 assert_eq!(stream[HEADER_LEN], 3);
340 let restored = decode(base, &stream).unwrap();
341 assert_eq!(restored, target);
342 }
343
344 #[test]
345 fn pure_copy_full_base() {
346 let base: Vec<u8> = (0..16u8).cycle().take(128).collect();
347 let target = &base[..64];
348 let mut stream = header(
350 u32::try_from(base.len()).unwrap(),
351 u32::try_from(target.len()).unwrap(),
352 )
353 .to_vec();
354 stream.push(OP_COPY);
355 stream.extend_from_slice(&0u32.to_le_bytes());
356 stream.extend_from_slice(&64u16.to_le_bytes());
357 assert_eq!(stream.len(), HEADER_LEN + 7);
358 let restored = decode(&base, &stream).unwrap();
359 assert_eq!(restored, target);
360 }
361
362 #[test]
363 fn near_duplicate_yields_smaller_delta() {
364 let v1 = include_str!("delta.rs"); let mut v2 = String::from(v1);
366 v2.push_str("\n// trailing edit\n");
367 let stream = encode(v1.as_bytes(), v2.as_bytes()).unwrap();
368 let restored = decode(v1.as_bytes(), &stream).unwrap();
369 assert_eq!(restored, v2.as_bytes());
370 assert!(stream.len() < v2.len(), "delta should be smaller than v2");
371 }
372
373 #[test]
374 fn rejects_zero_opcode() {
375 let mut stream = header(0, 0).to_vec();
376 stream.push(0x00);
377 let err = decode(&[], &stream).unwrap_err();
378 assert!(matches!(err, MkitError::TrailingData));
379 }
380
381 #[test]
382 fn rejects_unknown_version() {
383 let mut bytes = header(0, 0);
384 bytes[0] = 0x02;
385 let err = decode(&[], &bytes).unwrap_err();
386 assert!(matches!(err, MkitError::UnsupportedObjectVersion));
387 }
388
389 #[test]
390 fn rejects_truncated_header() {
391 let bytes = [0x01u8, 0x00, 0x00];
392 let err = decode(&[], &bytes).unwrap_err();
393 assert!(matches!(err, MkitError::UnexpectedEof));
394 }
395
396 #[test]
397 fn rejects_truncated_copy() {
398 let mut stream = header(16, 16).to_vec();
399 stream.push(OP_COPY);
400 stream.extend_from_slice(&0u32.to_le_bytes()); let err = decode(&[0u8; 16], &stream).unwrap_err();
402 assert!(matches!(err, MkitError::UnexpectedEof));
403 }
404
405 #[test]
406 fn rejects_truncated_insert() {
407 let mut stream = header(0, 10).to_vec();
408 stream.push(10); stream.extend_from_slice(b"abc"); let err = decode(&[], &stream).unwrap_err();
411 assert!(matches!(err, MkitError::UnexpectedEof));
412 }
413
414 #[test]
415 fn rejects_copy_past_base_end() {
416 let base = b"short"; let mut stream = header(u32::try_from(base.len()).unwrap(), 100).to_vec();
418 stream.push(OP_COPY);
419 stream.extend_from_slice(&0u32.to_le_bytes());
420 stream.extend_from_slice(&100u16.to_le_bytes());
421 let err = decode(base, &stream).unwrap_err();
422 assert!(matches!(err, MkitError::TrailingData));
423 }
424
425 #[test]
426 fn rejects_copy_with_zero_length() {
427 let base = [0u8; 16];
428 let mut stream = header(16, 16).to_vec();
429 stream.push(OP_COPY);
430 stream.extend_from_slice(&0u32.to_le_bytes());
431 stream.extend_from_slice(&0u16.to_le_bytes());
432 let err = decode(&base, &stream).unwrap_err();
433 assert!(matches!(err, MkitError::TrailingData));
434 }
435
436 #[test]
437 fn rejects_base_len_mismatch() {
438 let stream = header(16, 0).to_vec();
439 let err = decode(&[0u8; 8], &stream).unwrap_err();
440 assert!(matches!(err, MkitError::TrailingData));
441 }
442
443 #[test]
444 fn rejects_result_len_mismatch_at_end() {
445 let mut stream = header(0, 3).to_vec();
447 stream.push(5);
448 stream.extend_from_slice(b"hello");
449 let err = decode(&[], &stream).unwrap_err();
450 assert!(matches!(err, MkitError::TrailingData));
451 }
452
453 #[test]
454 fn rejects_huge_result_len_without_preallocating() {
455 let stream = header(0, u32::MAX);
461 let err = decode(&[], &stream).unwrap_err();
462 assert!(matches!(err, MkitError::TrailingData));
463 }
464
465 #[test]
466 fn rejects_copy_with_reserved_low_bits() {
467 let base = [0u8; 16];
468 let mut stream = header(16, 4).to_vec();
469 stream.push(OP_COPY | 0x01); stream.extend_from_slice(&0u32.to_le_bytes());
471 stream.extend_from_slice(&4u16.to_le_bytes());
472 let err = decode(&base, &stream).unwrap_err();
473 assert!(matches!(err, MkitError::TrailingData));
474 }
475
476 #[test]
477 fn empty_base_pure_insert() {
478 let target = b"all new content here!";
479 let stream = encode(b"", target).unwrap();
480 let restored = decode(b"", &stream).unwrap();
481 assert_eq!(restored, target);
482 }
483
484 #[test]
485 fn cap_hint_does_not_scale_with_base_len() {
486 let huge_base = 1usize << 30; let tiny_stream = 9usize; let declared_result = u32::MAX as usize;
494 let cap = super::compute_cap_hint(declared_result, huge_base, tiny_stream);
495 assert!(
496 cap <= tiny_stream.saturating_mul(CAP_MULTIPLIER),
497 "cap_hint {cap} must be bounded by stream.len() * CAP_MULTIPLIER, \
498 not by base.len()",
499 );
500 assert!(
501 cap < 1024 * 1024,
502 "cap_hint {cap} must stay well below 1 MiB for a 9-byte stream",
503 );
504 }
505
506 #[test]
512 fn check_length_bounds_rejects_over_u32() {
513 let over = (u32::MAX as usize).saturating_add(1);
515 assert!(matches!(
516 check_length_bounds(over, 0),
517 Err(MkitError::DeltaLengthOverflow { .. })
518 ));
519 assert!(matches!(
521 check_length_bounds(0, over),
522 Err(MkitError::DeltaLengthOverflow { .. })
523 ));
524 assert!(check_length_bounds(u32::MAX as usize, u32::MAX as usize).is_ok());
526 assert!(check_length_bounds(1, 1).is_ok());
528 }
529}