znippy_common/precompressed.rs
1//! Deciding whether a payload is **already compressed**, and so should be stored
2//! raw instead of handed to the codec.
3//!
4//! ## Where the probe lives
5//!
6//! In [`ldeflate::precompressed`], not here. This module is a **re-export** plus
7//! the one leg that cannot live there: the path/extension fallback.
8//!
9//! The direction is forced, not chosen. `znippy-common` already depends on
10//! `znippy-zoomies` (`Cargo.toml:96`, plus `ljar`/`lbzip2`/`lgz`), so an
11//! `ldeflate -> znippy-common` edge would close a cycle; and `znippy-common`
12//! drags `arrow`, `blake3` and an `openzl-sys` whose `build.rs` fetches a tarball
13//! over the network — for 88 lines of `std`-only byte matching. The dependency
14//! arrow already pointed at zoomies, which makes `ldeflate` the home: it is the
15//! leaf, and it is the *compressor*, the only thing that ever needs to ask the
16//! question.
17//!
18//! What is left here is genuinely znippy's: [`SkipPolicy::skip_by_path`] consults
19//! [`crate::index::is_probably_compressed`], an extension table that `ldeflate`
20//! has no business knowing about — it is handed `&[u8]`, never a `Path`.
21//! [`SkipPolicy`] is therefore a thin wrapper that adds the path leg and
22//! **delegates** the byte leg to `ldeflate::precompressed::SkipPolicy::skip`.
23//! There is one magic table, one `looks_compressed`, one `is_zlib_stream`, and
24//! one hint-to-verdict mapping for bytes — in `ldeflate`.
25//!
26//! ## The resolution order
27//!
28//! | # | mechanism | cost | how it can be wrong |
29//! |---|---|---|---|
30//! | 1 | an explicit caller [`ContentHint`] | free | only if the caller lies |
31//! | 2 | the extension table ([`crate::index::is_probably_compressed`]) | one `str` compare | the name can lie |
32//! | 3 | a [magic-byte probe](looks_compressed) over the first [`SNIFF_PREFIX_LEN`] bytes | ≤ 16 byte compares | a container whose magic is not at offset 0 |
33//! | 4 | default | — | — |
34//!
35//! **The hint is the primary mechanism, not the fallback.** A caller sealing a
36//! git packfile *knows* what it is holding; no amount of inspection beats being
37//! told. It is deterministic by construction, costs nothing, and carries
38//! knowledge the bytes themselves do not.
39//!
40//! ## Why there is no entropy estimate
41//!
42//! An entropy estimate was considered and **rejected**. It is a heuristic with a
43//! tunable threshold, it costs a scan proportional to the data, and it misjudges
44//! in both directions — high-entropy-but-compressible (a table of hashes with a
45//! compressible envelope) and low-entropy-but-already-compressed (a short zstd
46//! frame) both exist. A wrong guess wastes CPU or wastes bytes and reports
47//! neither. Every probe in `ldeflate` is exact: a fixed byte pattern at a fixed
48//! offset, no threshold, no sample size to tune.
49//!
50//! ## What a probe does *not* claim
51//!
52//! [`looks_compressed`] reads only the head of a buffer. For a file sliced into
53//! chunks, only chunk 0 carries the magic. The big-file pass in `znippy-compress`
54//! therefore takes chunk 0's verdict and carries it over the whole file — but
55//! **excluding** [`is_zlib_stream`], which is a two-byte test accepting 1 in 2048
56//! two-byte heads (measured exhaustively over all 65 536, not sampled) and so is
57//! not a claim to make about 8 GiB. See `slot_packer::carries_file_wide`. The
58//! extension table covers the whole file at once and so has no such gap, which is
59//! why `.pack` / `.idx` belong there too.
60//!
61//! ## The git case, precisely
62//!
63//! Two different things are called "a git object" and they need opposite
64//! answers:
65//!
66//! * a **loose object file** in `.git/objects/ab/cdef…` is a zlib stream, has no
67//! extension at all, and must be stored raw. [`is_zlib_stream`] catches it.
68//! * a `znippy-plugin-git` archive entry is the object's **canonical form**
69//! (`"<type> <size>\0<content>"`) — inflated bytes that compress perfectly
70//! well and *must* be compressed. It begins `commit `/`tree `/`blob `/`tag `,
71//! matches no probe there, and is correctly left alone.
72//!
73//! A rule of the shape "an oid-keyed entry is already compressed" would get the
74//! second case backwards and silently store compressible data raw. Probing the
75//! bytes at offset 0 gets both right. Both halves are asserted against a real
76//! encoder in `ldeflate::precompressed`'s own tests.
77
78use std::path::Path;
79
80/// The probe itself, re-exported so `znippy_common::precompressed::*` keeps
81/// naming exactly what it always named. These are `ldeflate`'s items, not copies
82/// of them — breaking one there breaks every znippy caller, which is the point
83/// of the move.
84pub use ldeflate::precompressed::{ContentHint, SNIFF_PREFIX_LEN, is_zlib_stream, looks_compressed};
85
86/// A batch-level compression policy: one [`ContentHint`] covering everything in
87/// a run, plus the fallbacks for when the hint says nothing.
88///
89/// Batch-level because that is how the knowledge actually arrives — a caller
90/// sealing a pack knows it for the whole pack, not entry by entry.
91///
92/// The two decision points are deliberately split, because the compress path has
93/// two moments with different information available:
94///
95/// * [`skip_by_path`](Self::skip_by_path) runs during enumeration, before a
96/// single byte is read;
97/// * [`skip_by_bytes`](Self::skip_by_bytes) refines it inside the compressor,
98/// once a chunk's real bytes are in hand, and is
99/// `ldeflate::precompressed::SkipPolicy::skip` under a name that says which of
100/// the two moments it belongs to.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
102pub struct SkipPolicy(ldeflate::precompressed::SkipPolicy);
103
104impl SkipPolicy {
105 /// No claim: decide per entry from its name, then from its bytes.
106 pub const fn resolve() -> Self {
107 Self(ldeflate::precompressed::SkipPolicy::resolve())
108 }
109
110 /// Everything in this batch is already compressed — store it all raw.
111 pub const fn already_compressed() -> Self {
112 Self(ldeflate::precompressed::SkipPolicy::already_compressed())
113 }
114
115 /// Compress everything, whatever it looks like. The meaning of `--no-skip`.
116 pub const fn compress_everything() -> Self {
117 Self(ldeflate::precompressed::SkipPolicy::compress_everything())
118 }
119
120 /// Bridge from the historical `no_skip: bool`, which is precisely the
121 /// two-valued subset of [`ContentHint`] that the CLI already exposed.
122 pub const fn from_no_skip(no_skip: bool) -> Self {
123 if no_skip { Self::compress_everything() } else { Self::resolve() }
124 }
125
126 pub const fn from_hint(hint: ContentHint) -> Self {
127 Self(ldeflate::precompressed::SkipPolicy::from_hint(hint))
128 }
129
130 pub const fn hint(&self) -> ContentHint {
131 self.0.hint()
132 }
133
134 /// The same policy as `ldeflate` sees it — for a caller handing bytes
135 /// straight to `ldeflate::compress_objects` rather than to znippy's packer,
136 /// so the two cannot disagree about what a hint means.
137 pub const fn as_ldeflate(&self) -> ldeflate::precompressed::SkipPolicy {
138 self.0
139 }
140
141 /// The decision available before any bytes are read: hint, else the
142 /// extension table.
143 ///
144 /// This is the leg `ldeflate` cannot have — it is handed `&[u8]`, never a
145 /// `Path`.
146 ///
147 /// `true` means "store raw". A `false` here is **not** final — the caller
148 /// should still offer the bytes to [`skip_by_bytes`](Self::skip_by_bytes)
149 /// once it has them.
150 pub fn skip_by_path(&self, path: &Path) -> bool {
151 match self.0.hint() {
152 ContentHint::AlreadyCompressed => true,
153 ContentHint::Compressible => false,
154 ContentHint::Unknown => crate::index::is_probably_compressed(path),
155 }
156 }
157
158 /// The refinement once real bytes are in hand: a magic-byte probe.
159 ///
160 /// Only meaningful when [`skip_by_path`](Self::skip_by_path) returned
161 /// `false`; calling it otherwise is harmless but pointless. Honours the hint
162 /// in both directions, so a `Compressible` batch is never vetoed by a probe.
163 pub fn skip_by_bytes(&self, prefix: &[u8]) -> bool {
164 self.0.skip(prefix)
165 }
166}
167
168impl From<ldeflate::precompressed::SkipPolicy> for SkipPolicy {
169 fn from(p: ldeflate::precompressed::SkipPolicy) -> Self {
170 Self(p)
171 }
172}
173
174impl From<SkipPolicy> for ldeflate::precompressed::SkipPolicy {
175 fn from(p: SkipPolicy) -> Self {
176 p.0
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 // The probe's own behaviour — the magic table, the bzip2/WebP special cases,
185 // the zlib header against streams from a real encoder, canonical git object
186 // bytes — is asserted in `ldeflate::precompressed`, which is where the code
187 // now is. Repeating it here would be the twin this change removed.
188 //
189 // What is asserted here is what this crate adds: the path leg, the bridge
190 // from `no_skip`, and — first, because everything else assumes it — that the
191 // re-export really does reach `ldeflate` rather than a leftover copy.
192
193 /// The dedup itself. If `znippy-common` ever grows a second magic table
194 /// again, these calls stop agreeing.
195 #[test]
196 fn the_probe_is_ldeflates_and_not_a_second_copy() {
197 for bytes in [
198 b"PACK\x00\x00\x00\x02".as_slice(),
199 b"\x1f\x8b\x08\x00".as_slice(),
200 b"\x28\xb5\x2f\xfd\x00\x58".as_slice(),
201 b"RIFF\x24\x00\x00\x00WEBPVP8 ".as_slice(),
202 b"\x78\x9c".as_slice(),
203 b"SELECT * FROM njord WHERE id = 42;".as_slice(),
204 b"x = 1;\n".as_slice(),
205 b"".as_slice(),
206 ] {
207 assert_eq!(
208 looks_compressed(bytes),
209 ldeflate::precompressed::looks_compressed(bytes),
210 "the re-export must BE ldeflate's probe: {bytes:?}"
211 );
212 assert_eq!(
213 is_zlib_stream(bytes),
214 ldeflate::precompressed::is_zlib_stream(bytes),
215 "the re-export must BE ldeflate's zlib probe: {bytes:?}"
216 );
217 // And the policy's byte leg must be the same decision, not a
218 // parallel one.
219 assert_eq!(
220 SkipPolicy::resolve().skip_by_bytes(bytes),
221 ldeflate::precompressed::SkipPolicy::resolve().skip(bytes),
222 "skip_by_bytes must delegate: {bytes:?}"
223 );
224 }
225 assert_eq!(SNIFF_PREFIX_LEN, ldeflate::precompressed::SNIFF_PREFIX_LEN);
226 }
227
228 /// The wrapper must not lose the hint on the way in or out — the failure
229 /// that would make `--no-skip` silently stop meaning anything.
230 #[test]
231 fn the_hint_survives_the_wrapper_in_both_directions() {
232 for (p, want) in [
233 (SkipPolicy::resolve(), ContentHint::Unknown),
234 (SkipPolicy::already_compressed(), ContentHint::AlreadyCompressed),
235 (SkipPolicy::compress_everything(), ContentHint::Compressible),
236 ] {
237 assert_eq!(p.hint(), want);
238 assert_eq!(p.as_ldeflate().hint(), want, "the ldeflate view must carry the same hint");
239 assert_eq!(SkipPolicy::from(p.as_ldeflate()), p, "round-trip through ldeflate");
240 assert_eq!(ldeflate::precompressed::SkipPolicy::from(p), p.as_ldeflate());
241 assert_eq!(SkipPolicy::from_hint(want), p, "from_hint must agree with the constructor");
242 }
243 assert_eq!(SkipPolicy::default(), SkipPolicy::resolve(), "the probe is the default");
244 }
245
246 // ── the hint, both directions ────────────────────────────────────────────
247
248 #[test]
249 fn an_already_compressed_hint_is_honoured_without_inspection() {
250 let p = SkipPolicy::already_compressed();
251 // Plain compressible bytes under a plain name: only the hint can decide
252 // this, and it must.
253 assert!(p.skip_by_path(Path::new("objects/00ff")), "hint must win on the path");
254 assert!(p.skip_by_bytes(b"SELECT * FROM njord;"), "hint must win on the bytes");
255 }
256
257 #[test]
258 fn an_unhinted_batch_of_compressible_data_is_still_compressed() {
259 // The other direction: a hint that swallows everything is as broken as
260 // one that is ignored.
261 let p = SkipPolicy::resolve();
262 assert!(!p.skip_by_path(Path::new("dbdump/njord.dump")));
263 assert!(!p.skip_by_bytes(b"SELECT * FROM njord WHERE id = 42;"));
264 }
265
266 #[test]
267 fn a_compressible_hint_overrules_both_fallbacks() {
268 let p = SkipPolicy::compress_everything();
269 assert!(!p.skip_by_path(Path::new("photo.png")), "--no-skip must beat the extension table");
270 assert!(!p.skip_by_bytes(b"\x89PNG\r\n\x1a\n"), "--no-skip must beat the probe");
271 }
272
273 #[test]
274 fn no_skip_maps_onto_the_hint() {
275 assert_eq!(SkipPolicy::from_no_skip(true), SkipPolicy::compress_everything());
276 assert_eq!(SkipPolicy::from_no_skip(false), SkipPolicy::resolve());
277 }
278
279 /// The whole point of a sniffer: a name that lies.
280 #[test]
281 fn an_extension_that_lies_is_caught_by_the_bytes() {
282 let p = SkipPolicy::resolve();
283 let notes = Path::new("release-notes.txt");
284 let zstd_bytes = b"\x28\xb5\x2f\xfd\x00\x58\x2d\x00\x00";
285
286 assert!(!p.skip_by_path(notes), "the name says text, so the fast path lets it through");
287 assert!(p.skip_by_bytes(zstd_bytes), "the bytes say zstd, and the bytes are the authority");
288 }
289
290 /// And the same trap in reverse: a `.pack` name over compressible bytes is
291 /// still skipped by the fast path. Documented so the asymmetry is a decision
292 /// rather than a surprise — the extension table is trusted, by design.
293 #[test]
294 fn the_extension_fast_path_covers_a_whole_file_not_just_its_head() {
295 let p = SkipPolicy::resolve();
296 assert!(p.skip_by_path(Path::new("objects/pack/pack-9f2c.pack")));
297 assert!(p.skip_by_path(Path::new("objects/pack/pack-9f2c.idx")));
298 }
299
300 /// A git loose object is the case with **no extension at all**, so only the
301 /// byte leg can reach it — and the path leg must say "not from the name",
302 /// which is what makes the byte leg necessary rather than decorative.
303 #[test]
304 fn an_oid_named_loose_object_is_reached_only_by_the_bytes() {
305 let p = SkipPolicy::resolve();
306 let oid = Path::new(".git/objects/9f/2c1e0b7a4d3f8c6e5a2b9d0c7f4e1a8b3d6c9e2f");
307 assert!(!p.skip_by_path(oid), "an oid has no extension for the table to match");
308 assert!(p.skip_by_bytes(b"\x78\x01\x4b\xca\x49"), "so the zlib header must catch it");
309 }
310}