delta_kit/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! `delta-kit` — a binary-safe delta codec.
3//!
4//! `compute_delta` produces a compact delta between a base and a target
5//! byte string; `apply_delta` reconstructs the target from the base and
6//! the delta. The wire format is byte-compatible with the
7//! `suture-protocol` crate this codec was extracted from.
8//!
9//! # Strategies
10//!
11//! `compute_delta` picks the smallest applicable strategy:
12//!
13//! 1. **Binary (XOR + Zstd)** — if either input looks binary (contains a
14//! zero byte in the first 8 KiB). Emits opcode `0x03` when the XOR
15//! stream compresses smaller than the target; requires the `zstd`
16//! feature.
17//! 2. **Rabin rolling hash** — when both inputs are at least
18//! [`BLOCK_SIZE`] (4 KiB). Splits the base into fixed blocks, indexes
19//! them by a Rabin rolling hash (base 257 over the Mersenne prime
20//! 2^61 − 1) plus an FNV-1a-style strong hash, then scans the target
21//! with a rolling window emitting `Copy`/`Insert` instructions
22//! (opcode `0x02`). Emits only if smaller than the target.
23//! 3. **Prefix/suffix trim** — small inputs. Finds the common prefix and
24//! suffix and stores only the changed middle (opcode `0x01`).
25//! 4. **Full content** — fallback storing the whole target (opcode
26//! `0x00`).
27//!
28//! # Wire format
29//!
30//! All integers are little-endian. The first byte selects the encoding:
31//!
32//! ```text
33//! 0x00 — full content
34//! [1..] entire target
35//!
36//! 0x01 — prefix/suffix patch
37//! [1..9) u64 prefix_len (bytes reused from the base head)
38//! [9..17) u64 suffix_len (bytes reused from the base tail)
39//! [17..25) u64 target_len
40//! [25..] changed middle bytes of the target
41//!
42//! 0x02 — rolling-hash instruction stream
43//! [1..9) u64 target_len
44//! [9..13) u32 instruction_count
45//! then instruction_count records:
46//! 0x01 Copy [1..9) u64 base_offset
47//! [9..13) u32 length (13 bytes total)
48//! 0x02 Insert [1..5) u32 length
49//! [5..5+n) bytes (5+n bytes total)
50//!
51//! 0x03 — binary XOR + Zstd (requires the `zstd` feature)
52//! [1..9) u64 target_len
53//! [9..25) blake3(base)[..16]
54//! [25..41) blake3(target)[..16]
55//! [41..] Zstd frame over xor(base, target)
56//! ```
57//!
58//! # Hardened decoding
59//!
60//! The origin implementation silently repaired malformed deltas
61//! (identity-decoding truncated headers, skipping out-of-range copies,
62//! returning an empty vector on checksum mismatch). This codec returns
63//! [`DeltaError`] for those cases instead. Well-formed deltas — including
64//! everything `compute_delta` produces — decode identically, and the
65//! opcode stream is byte-for-byte the same. Consumers that must preserve
66//! the origin's observable lenient behavior can delegate to
67//! [`apply_delta_lenient`] instead.
68//!
69//! # Example
70//!
71//! ```
72//! let base = b"Hello, World!";
73//! let target = b"Hello, Rust!";
74//! let (_base_copy, delta) = delta_kit::compute_delta(base, target);
75//! assert_eq!(
76//! delta_kit::apply_delta(base, &delta).expect("compute_delta output always applies"),
77//! target.to_vec()
78//! );
79//! ```
80
81#![forbid(unsafe_code)]
82#![deny(missing_docs)]
83#![cfg_attr(not(feature = "std"), no_std)]
84
85extern crate alloc;
86
87mod delta;
88
89#[cfg(feature = "zstd")]
90pub use delta::compute_binary_delta;
91pub use delta::BLOCK_SIZE;
92pub use delta::{apply_delta, apply_delta_lenient, compute_delta};
93pub use delta::{DeltaError, MismatchSide};