structured_zstd/encoding/mod.rs
1//! Zstandard encoder — frame compression, streaming, dictionary support.
2//!
3//! Five entry points cover the common use cases:
4//!
5//! * [`compress`] — one-shot helper that builds a self-contained
6//! Zstandard frame from a `Read` source to a `Write` sink. The
7//! input is consumed incrementally from `Read`, so input buffering
8//! stays bounded; however, the compressed output is buffered in
9//! memory until the frame is complete so the Frame Content Size
10//! field can be filled in the header — peak memory is
11//! `O(compressed_size)` (worst-case `O(input_size)` for
12//! incompressible payloads, plus a small frame overhead). The
13//! savings vs [`compress_to_vec`] come from not materialising the
14//! input alongside the output.
15//! * [`compress_to_vec`] — same one-shot path as [`compress`] but
16//! the input is eagerly drained into an internal `Vec` first
17//! (`read_to_end`) so the encoder can be handed a `&[u8]` and a
18//! precise source-size hint. Peak memory is therefore ≈
19//! `input_size + output_size`; prefer [`compress`] or
20//! [`StreamingEncoder`] when the input is large or unbounded.
21//! * [`StreamingEncoder`] — implements [`crate::io::Write`], which
22//! re-exports [`std::io::Write`] under the `std` feature and falls
23//! back to a `no_std`-friendly trait otherwise. Accepts bytes
24//! incrementally and flushes compressed output as blocks fill.
25//! Requires `set_pledged_content_size` before the first write if
26//! the Frame Content Size field is to be populated.
27//! * [`CompressionContext`] — the reusable state behind
28//! [`StreamingEncoder`], with the output passed to each call: compresses
29//! frame after frame with one set of settings, one attached dictionary and
30//! one set of match-finder allocations, as upstream's `ZSTD_CCtx` does.
31//! * [`FrameCompressor`] — lower-level builder that owns the matcher and
32//! the per-frame configuration; the streaming and one-shot helpers are
33//! thin wrappers over it. Reach for it when you need to swap in a custom
34//! [`Matcher`] implementation or share the matcher across frames.
35//!
36//! Compression intensity is selected via [`CompressionLevel`], which
37//! provides both named presets (`Fastest`, `Default`, `Better`, `Best`) and
38//! numeric levels (`from_level(n)`) that mirror C zstd's level numbering
39//! (negative for ultra-fast, `0` = default, `1..=22` for the standard
40//! range).
41//!
42//! All produced frames are valid RFC 8878 Zstandard streams and decode
43//! through both this crate's [`crate::decoding`] module and upstream C zstd.
44//!
45//! For memory budgeting, [`estimated_compression_workspace_bytes`] reports
46//! the approximate steady-state heap footprint of a one-shot compression at
47//! a given level (window + match-finder tables + block staging).
48
49pub(crate) mod block_header;
50pub(crate) mod blocks;
51pub(crate) mod cparams;
52pub(crate) mod dict_attach;
53pub(crate) mod fastpath;
54pub(crate) mod frame_header;
55pub(crate) mod incompressible;
56pub(crate) mod match_generator;
57pub(crate) mod util;
58
59// `#111` encoder architecture rewrite. `cost_model`, `opt`,
60// `strategy`, `dfast`, `row`, and `simple` host the relocated
61// cost-model types, the optimal-parser plain-data types, the
62// const-generic [`strategy::Strategy`] trait + per-level [`strategy::
63// StrategyTag`] dispatcher, and the Dfast / Row / Simple matchers
64// respectively. `match_table::helpers` hosts the shared match-finder
65// primitives. The rewrite plan is tracked in
66// <https://github.com/structured-world/structured-zstd/issues/111>;
67// per-phase boundaries are `perf/post-pr-110-baseline` (start),
68// `perf/post-pr-121-baseline` (post-Phase-2).
69pub(crate) mod bt;
70pub(crate) mod cost_model;
71pub(crate) mod dfast;
72pub(crate) mod hc;
73#[cfg(test)]
74pub(crate) mod lazy_parse;
75// LDM hashes each `min_match_length` window with XXH64 (upstream zstd
76// `zstd_ldm.c:315`), so the `ldm` feature implies `hash` and the
77// `twox-hash` dependency it pulls in. `BtMatcher::ldm_producer` and the
78// `cfg(feature = "ldm")` blocks inside `BtMatcher::prepare_ldm_candidates` /
79// `BtMatcher::reset` carry the same gate; the call site in
80// `hc::optimal::HcMatchGenerator::start_matching_optimal` invokes
81// `prepare_ldm_candidates` unconditionally because the gating is internal to
82// the method body (without the feature it shrinks to an
83// `ldm_sequences.clear()` stub).
84pub(crate) mod block_input;
85#[cfg(feature = "ldm")]
86pub(crate) mod ldm;
87pub(crate) mod match_table;
88pub(crate) mod opt;
89pub(crate) mod row;
90pub mod sequences;
91pub(crate) mod simple;
92pub(crate) mod strategy;
93pub(crate) mod workspace;
94
95pub(crate) mod frame_compressor;
96#[cfg(feature = "lsm")]
97pub mod frame_emit_info;
98mod levels;
99pub(crate) mod parameters;
100#[cfg(feature = "bench-internals")]
101pub mod sequence_capture;
102mod streaming_encoder;
103pub use frame_compressor::{DictionaryHold, EncoderDictionary, FrameCompressor};
104#[cfg(feature = "lsm")]
105pub use frame_emit_info::{BlockType, FrameBlock, FrameEmitInfo};
106pub use levels::config::{
107 estimated_bt_strategy_extra_bytes, estimated_compression_workspace_bytes,
108 estimated_compression_workspace_bytes_for_parameters,
109 estimated_compression_workspace_bytes_for_run,
110 estimated_compression_workspace_bytes_for_source,
111};
112pub use match_generator::MatchGeneratorDriver;
113pub use parameters::{
114 Bounds, CParameter, CompressionParameters, CompressionParametersBuilder, LevelParameters,
115 LiteralCompressionMode, ParameterError, RowMatchFinderMode, Strategy,
116};
117pub use streaming_encoder::{CompressionContext, StreamingEncoder};
118pub use workspace::HistoryBuf;
119
120/// Name of the kernel tier the match finders run for this process (`scalar`,
121/// `sse2`, `sse4.2`, `avx2`, `neon`, `simd128`), chosen by run-time CPU
122/// detection under any [`set_cpu_ceiling`](crate::set_cpu_ceiling).
123///
124/// # Examples
125/// ```
126/// let name = structured_zstd::encoding::active_match_kernel_name();
127/// assert!(!name.is_empty());
128/// ```
129pub fn active_match_kernel_name() -> &'static str {
130 fastpath::select_kernel().name()
131}
132
133use crate::io::{Read, Write};
134use alloc::vec::Vec;
135
136/// Convenience function to compress some source into a target without reusing any resources of the compressor
137/// ```rust
138/// use structured_zstd::encoding::{compress, CompressionLevel};
139/// let data: &[u8] = &[0,0,0,0,0,0,0,0,0,0,0,0];
140/// let mut target = Vec::new();
141/// compress(data, &mut target, CompressionLevel::Fastest);
142/// ```
143pub fn compress<R: Read, W: Write>(source: R, target: W, level: CompressionLevel) {
144 let mut frame_enc = FrameCompressor::new(level);
145 frame_enc.set_source(source);
146 frame_enc.set_drain(target);
147 frame_enc.compress();
148}
149
150/// Convenience function to compress some source into a Vec without reusing any resources of the compressor.
151///
152/// This helper eagerly buffers the full input (`Read`) before compression so it
153/// can provide a source-size hint to the one-shot encoder path. Peak memory can
154/// therefore be roughly `input_size + output_size`. For very large payloads or
155/// tighter memory budgets, prefer streaming APIs such as [`StreamingEncoder`].
156///
157/// **This is NOT a streaming API.** The source is fully buffered
158/// into a `Vec<u8>` before any compression work begins, so peak input
159/// memory is bounded by `source.len()` (not "constant regardless of
160/// payload size" as a stream-shaped encoder would offer). If the
161/// source is large enough that holding it in memory is not acceptable,
162/// use [`StreamingEncoder`] which consumes chunks incrementally
163/// without the up-front Vec build.
164///
165/// This helper drives `read_to_end` to materialize the full source
166/// into a `Vec<u8>` before forwarding the slice to
167/// [`compress_slice_to_vec`]. For a `Read` whose size is unknown ahead
168/// of time, `read_to_end` grows that input `Vec` via power-of-two
169/// doubling: peak input allocation can be up to 2× the final source
170/// length transiently. The live working set on this entry point is
171/// roughly `input.capacity()` plus the block-accumulation buffer and
172/// per-block scratch carried by [`compress_slice_to_vec`], plus the
173/// exactly-sized output `Vec`. [`StreamingEncoder`] avoids the input
174/// materialization step entirely and is the right entry point when
175/// the source is large or unbounded.
176///
177/// ```rust
178/// use structured_zstd::encoding::{compress_to_vec, CompressionLevel};
179/// let data: &[u8] = &[0,0,0,0,0,0,0,0,0,0,0,0];
180/// let compressed = compress_to_vec(data, CompressionLevel::Fastest);
181/// ```
182pub fn compress_to_vec<R: Read>(source: R, level: CompressionLevel) -> Vec<u8> {
183 let mut source = source;
184 let mut input = Vec::new();
185 source.read_to_end(&mut input).unwrap();
186 compress_slice_to_vec(input.as_slice(), level)
187}
188
189/// Compress a contiguous byte slice into a fresh `Vec<u8>` without the
190/// input-buffering step that [`compress_to_vec`] performs to adapt a
191/// `Read` source.
192///
193/// One-shot wrapper over
194/// [`FrameCompressor::compress_independent_frame`]: the input is read by
195/// reference (the eligible Fast path scans it in place, no per-block
196/// history copy), and the returned `Vec` is allocated exactly once at the
197/// final frame size after compression. Peak transient memory is the
198/// block-accumulation buffer (grown via amortized doubling, ≈ 2× current
199/// compressed size at the last realloc) plus the exactly-sized output. The
200/// worst-case compressed-size bound is never pinned upfront, so a highly
201/// compressible 100 MiB input does not charge ~100 MiB of worst-case
202/// expansion against peak.
203///
204/// To compress many slices, construct one [`FrameCompressor`] and call
205/// [`compress_independent_frame_into`](FrameCompressor::compress_independent_frame_into)
206/// in a loop instead, which reuses the matcher tables, scratch, and output
207/// buffer across frames (this function allocates and primes from scratch
208/// each call).
209///
210/// # Panics
211///
212/// Panics on encoder error (matches the failure surface of
213/// [`compress_to_vec`], which this function backs). Out-of-memory during
214/// the output / per-block scratch allocations is handled by the global
215/// allocator's abort policy. The slice/Vec entry points mirror the upstream zstd
216/// `ZSTD_compress` shape (no error return on the bulk path).
217///
218/// ```rust
219/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
220/// let data: &[u8] = &[0,0,0,0,0,0,0,0,0,0,0,0];
221/// let compressed = compress_slice_to_vec(data, CompressionLevel::Fastest);
222/// ```
223pub fn compress_slice_to_vec(source: &[u8], level: CompressionLevel) -> Vec<u8> {
224 // Bare `FrameCompressor` resolves all three type params to their
225 // defaults (`&'static [u8]` reader, `Vec<u8>` drain, MatchGeneratorDriver);
226 // neither the reader nor the drain is used by the in-place
227 // `compress_independent_frame` path.
228 let mut enc: FrameCompressor = FrameCompressor::new(level);
229 enc.compress_independent_frame(source)
230}
231
232/// Worst-case compressed-frame size for an input of `src_size` bytes.
233///
234/// A destination buffer of this size is always large enough to hold the
235/// output of [`compress_slice_to_vec`] (or any single-frame compression) for
236/// an input of `src_size` bytes, so a caller sizing a fixed buffer once (the
237/// shape the C `ZSTD_compress` entry point needs) never has to grow it.
238///
239/// Mirrors the upstream `ZSTD_COMPRESSBOUND` formula exactly:
240/// `src_size + (src_size >> 8) + margin`, where `margin` is
241/// `(128 KiB - src_size) >> 11` for inputs below 128 KiB and `0` otherwise.
242/// The margin guarantees `bound(a) + bound(b) <= bound(a + b)` for blocks of
243/// at least 128 KiB, which keeps multi-frame concatenation sizing sound.
244///
245/// Saturates at [`usize::MAX`] if the formula would overflow on a
246/// pathologically large `src_size` — no allocation that large can exist, so
247/// the saturated value is the correct "cannot fit" sentinel rather than a
248/// masked wrap.
249///
250/// ```rust
251/// use structured_zstd::encoding::{compress_bound, compress_slice_to_vec, CompressionLevel};
252/// let data = [7u8; 4096];
253/// assert!(compress_slice_to_vec(&data, CompressionLevel::Default).len() <= compress_bound(data.len()));
254/// ```
255pub const fn compress_bound(src_size: usize) -> usize {
256 const LOWER: usize = 128 * 1024;
257 let margin = if src_size < LOWER {
258 (LOWER - src_size) >> 11
259 } else {
260 0
261 };
262 // Saturating is the correct UPPER-BOUND semantic here, not a masked bug:
263 // this is a public API over an arbitrary `usize`, and the largest meaningful
264 // bound is `usize::MAX`. A real slice is at most `isize::MAX` bytes, so the
265 // `* 1.004 + margin` cannot overflow for genuine inputs; the saturation only
266 // caps a pathological caller-supplied size at the representable ceiling.
267 src_size
268 .saturating_add(src_size >> 8)
269 .saturating_add(margin)
270}
271
272/// Compress a byte slice into a fresh `Vec<u8>` using fine-grained
273/// [`CompressionParameters`] (#27) instead of a bare
274/// [`CompressionLevel`].
275///
276/// One-shot wrapper over [`FrameCompressor::set_parameters`] +
277/// [`FrameCompressor::compress_independent_frame`]. The produced frame is
278/// a valid RFC 8878 stream regardless of the knobs chosen.
279///
280/// ```rust
281/// use structured_zstd::encoding::{
282/// compress_with_parameters, CompressionLevel, CompressionParameters, Strategy,
283/// };
284/// let data: &[u8] = b"the quick brown fox jumps over the lazy dog";
285/// let params = CompressionParameters::builder(CompressionLevel::Level(5))
286/// .strategy(Strategy::Greedy)
287/// .build()
288/// .unwrap();
289/// let compressed = compress_with_parameters(data, ¶ms);
290/// assert!(!compressed.is_empty());
291/// ```
292pub fn compress_with_parameters(source: &[u8], params: &CompressionParameters) -> Vec<u8> {
293 let mut enc: FrameCompressor = FrameCompressor::new(params.level());
294 enc.set_parameters(params);
295 enc.compress_independent_frame(source)
296}
297
298/// The compression mode used impacts the speed of compression,
299/// and resulting compression ratios. Faster compression will result
300/// in worse compression ratios, and vice versa.
301#[derive(Copy, Clone, Debug, PartialEq, Eq)]
302pub enum CompressionLevel {
303 /// This level does not compress the data at all, and simply wraps
304 /// it in a Zstandard frame.
305 Uncompressed,
306 /// This level is roughly equivalent to Zstd compression level 1
307 Fastest,
308 /// This level uses the crate's dedicated `dfast`-style matcher to
309 /// target a better speed/ratio tradeoff than [`CompressionLevel::Fastest`].
310 ///
311 /// It represents this crate's "default" compression setting and may
312 /// evolve in future versions as the implementation moves closer to
313 /// reference zstd level 3 behavior.
314 Default,
315 /// This level is roughly equivalent to Zstd level 7.
316 ///
317 /// Uses the hash-chain matcher with a lazy2 matching strategy: the encoder
318 /// evaluates up to two positions ahead before committing to a match,
319 /// trading speed for a better compression ratio than [`CompressionLevel::Default`].
320 Better,
321 /// This level is equivalent to Zstd level 13.
322 ///
323 /// Uses the lazy2 parse over the binary-tree match finder (`btlazy2`),
324 /// the first level of the deep band that strictly dominates every level
325 /// below it on ratio; compared to [`CompressionLevel::Better`] it
326 /// trades speed for the best ratio of the named presets.
327 Best,
328 /// Numeric compression level.
329 ///
330 /// Levels 1–22 correspond to the C zstd level numbering. Higher values
331 /// produce smaller output at the cost of more CPU time. Negative values
332 /// select ultra-fast modes that trade ratio for speed. Level 0 is
333 /// treated as [`DEFAULT_LEVEL`](Self::DEFAULT_LEVEL), matching C zstd
334 /// semantics.
335 ///
336 /// Named variants map to specific numeric levels:
337 /// [`Fastest`](Self::Fastest) = 1, [`Default`](Self::Default) = 3,
338 /// [`Better`](Self::Better) = 7, [`Best`](Self::Best) = 13.
339 /// [`Best`](Self::Best) remains the highest-ratio named preset, but
340 /// [`Level`](Self::Level) values above 13 can target stronger (slower)
341 /// tuning than the named hierarchy.
342 ///
343 /// Levels above 13 use progressively larger windows and deeper search.
344 /// Levels 16–17 use a `btopt`-style price parser, 18 uses `btultra`,
345 /// and 19–22 use a `btultra2`-style two-pass selection profile.
346 ///
347 /// Semver note: this variant was added after the initial enum shape and
348 /// is a breaking API change for downstream crates that exhaustively
349 /// `match` on [`CompressionLevel`] without a wildcard arm.
350 Level(i32),
351}
352
353impl CompressionLevel {
354 /// The minimum supported numeric compression level (ultra-fast mode).
355 pub const MIN_LEVEL: i32 = -131072;
356 /// The maximum supported numeric compression level.
357 pub const MAX_LEVEL: i32 = 22;
358 /// The default numeric compression level (equivalent to [`Default`](Self::Default)).
359 pub const DEFAULT_LEVEL: i32 = 3;
360
361 /// Create a compression level from a numeric value.
362 ///
363 /// Returns named variants for canonical levels (`0`/`3`, `1`, `7`, `13`)
364 /// and [`Level`](Self::Level) for all other values.
365 ///
366 /// With the default matcher backend (`MatchGeneratorDriver`), values
367 /// outside [`MIN_LEVEL`](Self::MIN_LEVEL)..=[`MAX_LEVEL`](Self::MAX_LEVEL)
368 /// are silently clamped during built-in level parameter resolution.
369 pub const fn from_level(level: i32) -> Self {
370 match level {
371 0 | Self::DEFAULT_LEVEL => Self::Default,
372 1 => Self::Fastest,
373 7 => Self::Better,
374 13 => Self::Best,
375 _ => Self::Level(level),
376 }
377 }
378}
379
380/// The sizes of a dictionary handed to [`Matcher::set_dictionary_size_hint`].
381///
382/// Upstream picks the CDict's cParams tier from the size of the serialized
383/// dictionary buffer (`ZSTD_createCDict(dictBuffer, dictSize, level)`, header
384/// and entropy tables included), while the dictionary tables and the attach
385/// cutoffs are sized from the content that is actually indexed.
386///
387/// # Examples
388/// ```
389/// use structured_zstd::encoding::DictionarySizes;
390/// let sizes = DictionarySizes::raw_content(4096);
391/// assert_eq!(sizes.serialized, sizes.content);
392/// ```
393#[derive(Clone, Copy, Debug, PartialEq, Eq)]
394pub struct DictionarySizes {
395 /// Bytes of dictionary content the matcher indexes.
396 pub content: usize,
397 /// Bytes of the serialized dictionary (the CDict cParams tier key); equal
398 /// to `content` for a raw-content dictionary.
399 pub serialized: usize,
400}
401
402impl DictionarySizes {
403 /// Sizes of a raw-content dictionary: nothing but the content is
404 /// serialized.
405 pub const fn raw_content(len: usize) -> Self {
406 Self {
407 content: len,
408 serialized: len,
409 }
410 }
411}
412
413/// Bytes below which a frame is still about its dictionary rather than about
414/// its own content (upstream `ZSTD_USE_CDICT_PARAMS_SRCSIZE_CUTOFF`).
415const DICTIONARY_DESCRIBES_FRAME_BELOW: u64 = 128 * 1024;
416/// Multiple of the dictionary's content below which the same holds however
417/// large both are (upstream `ZSTD_USE_CDICT_PARAMS_DICTSIZE_MULTIPLIER`).
418const DICTIONARY_DESCRIBES_FRAME_MULTIPLE: u64 = 6;
419
420/// Whether a dictionary of `dict_content` bytes still describes a frame over
421/// `src_size` bytes (`None` = not yet known), so that the shape it was prepared
422/// with is the frame's too.
423///
424/// A source under 128 KiB, or under six times the dictionary, is about the
425/// dictionary: the tables it was prepared with are the right ones and can be
426/// searched in place. Past that the frame is about its own content, and a shape
427/// chosen for a dictionary undersizes it. A frame of unknown size keeps the
428/// dictionary's shape, having nothing better to go on. Upstream weighs the same
429/// three things at `ZSTD_compressBegin_internal` (zstd_compress.c:5254).
430///
431/// A size of `u64::MAX` is the encoder's "unknown" sentinel, not a source of
432/// that many bytes, and counts as unknown here too.
433///
434/// This is the codec's rule, exported so that every surface in front of it
435/// (the C ABI included) asks rather than re-deciding.
436///
437/// # Examples
438/// ```
439/// use structured_zstd::encoding::dictionary_describes_frame;
440///
441/// // A few kilobytes against a 4 KiB dictionary: about the dictionary.
442/// assert!(dictionary_describes_frame(4096, Some(8192)));
443/// // A megabyte against the same: about itself.
444/// assert!(!dictionary_describes_frame(4096, Some(1 << 20)));
445/// // Unknown, so there is nothing better than the dictionary's own shape.
446/// assert!(dictionary_describes_frame(4096, None));
447/// assert!(dictionary_describes_frame(4096, Some(u64::MAX)));
448/// ```
449pub fn dictionary_describes_frame(dict_content: usize, src_size: Option<u64>) -> bool {
450 if dict_content == 0 {
451 return false;
452 }
453 let Some(src) = src_size.filter(|size| *size != crate::encoding::cparams::CONTENTSIZE_UNKNOWN)
454 else {
455 return true;
456 };
457 // Asked as a division rather than `src < dict * 6`: this is public, so the
458 // dictionary is whatever the caller names, and six times a large one does
459 // not fit the type. For integers the two are the same question, and a
460 // division of a `u64` by a constant cannot overflow.
461 src < DICTIONARY_DESCRIBES_FRAME_BELOW
462 || src / DICTIONARY_DESCRIBES_FRAME_MULTIPLE < dict_content as u64
463}
464
465/// Trait used by the encoder that users can use to extend the matching facilities with their own algorithm
466/// making their own tradeoffs between runtime, memory usage and compression ratio
467///
468/// The matcher owns the input: the encoder reads the data to be compressed
469/// straight into the matcher's [`HistoryBuf`] through
470/// [`fill_in_place`](Self::fill_in_place), where it waits, uncommitted, until
471/// the encoder has chosen where the next block ends. The encoder then claims
472/// the block with [`commit_filled`](Self::commit_filled) and either calls
473/// `start_matching` or, if the block is deemed not worth compressing,
474/// `skip_matching`. Bytes left uncommitted head the next block.
475///
476/// This is repeated until no more data is left to be compressed.
477pub trait Matcher {
478 /// The block most recently claimed by [`commit_filled`](Self::commit_filled).
479 fn get_last_space(&mut self) -> &[u8];
480 /// Read input straight into the matcher's history buffer.
481 ///
482 /// `fill` is handed the buffer, with room for `capacity` more bytes, to
483 /// append to, and returns `(appended, eof)`, which this returns. The bytes
484 /// are then readable through [`uncommitted_input`](Self::uncommitted_input)
485 /// but are NOT yet part of the match window: the encoder chooses the block
486 /// boundary (the pre-split pass needs the bytes to decide) and then calls
487 /// [`commit_filled`](Self::commit_filled). Whatever is left over stays in
488 /// the buffer and becomes the head of the next block, so a carried split
489 /// remainder costs no copy.
490 fn fill_in_place(
491 &mut self,
492 capacity: usize,
493 fill: &mut dyn FnMut(&mut HistoryBuf) -> (usize, bool),
494 ) -> (usize, bool);
495 /// Bytes read by [`fill_in_place`](Self::fill_in_place) that no block has
496 /// claimed yet.
497 fn uncommitted_input(&self) -> &[u8];
498 /// Claim `len` bytes from the head of
499 /// [`uncommitted_input`](Self::uncommitted_input) as the next block.
500 fn commit_filled(&mut self, len: usize);
501 /// Just process the data in the last committed space for future matching.
502 fn skip_matching(&mut self);
503 /// Hint-aware skip path used internally to thread a precomputed block
504 /// incompressibility verdict to matcher backends.
505 ///
506 /// Default implementation preserves backwards compatibility for external
507 /// custom matchers by delegating to [`skip_matching`](Self::skip_matching).
508 fn skip_matching_with_hint(&mut self, _incompressible_hint: Option<bool>) {
509 self.skip_matching();
510 }
511 /// Process the data in the last committed space for future matching AND
512 /// report its sequences, in order, covering the whole block (see
513 /// [`Sequence`]).
514 fn start_matching(&mut self, handle_sequence: impl FnMut(Sequence));
515 /// Reset this matcher so it can be used for the next new frame
516 fn reset(&mut self, level: CompressionLevel);
517 /// Reset for the next frame at `level`, as [`reset`](Self::reset), with
518 /// the compression context's workspace to take tables from rather than
519 /// allocating them. The context calls this in place of `reset`, once per
520 /// frame.
521 ///
522 /// The workspace type cannot be named outside this crate, so a matcher
523 /// defined elsewhere can neither override nor call this: it keeps the
524 /// default, which resets and keeps its own allocations. That is what makes
525 /// the workspace's regions sound to hand out: only the context lays it out,
526 /// and only for the matcher it is resetting.
527 #[doc(hidden)]
528 fn reset_in_workspace(
529 &mut self,
530 level: CompressionLevel,
531 _workspace: &mut workspace::Workspace,
532 ) {
533 self.reset(level);
534 }
535 /// Move whatever this matcher holds in a compression context's workspace
536 /// into allocations of its own, before it leaves that context. Only a
537 /// matcher that takes [`reset_in_workspace`](Self::reset_in_workspace)
538 /// holds anything there.
539 #[doc(hidden)]
540 fn leave_workspace(&mut self) {}
541 /// The built-in matcher, when this is it. The encoder then runs its
542 /// backends compiled for the kernel tier it chose for the block, rather
543 /// than through [`start_matching`](Self::start_matching), which leaves the
544 /// tier to the matcher. Every other matcher keeps the default.
545 #[doc(hidden)]
546 #[inline]
547 fn builtin_driver(&mut self) -> Option<&mut match_generator::MatchGeneratorDriver> {
548 None
549 }
550 /// Provide a hint about the total uncompressed size for the next frame.
551 ///
552 /// Implementations may use this to select smaller hash tables and windows
553 /// for small inputs, matching the C zstd source-size-class behavior.
554 /// Called before [`reset`](Self::reset) when the caller knows the input
555 /// size (e.g. from pledged content size or file metadata).
556 ///
557 /// The default implementation is a no-op for custom matchers and
558 /// test stubs. The built-in runtime matcher (`MatchGeneratorDriver`)
559 /// overrides this hook and applies the hint during level resolution.
560 fn set_source_size_hint(&mut self, _size: u64) {}
561 /// Hint the sizes of the dictionary that will be primed into the next
562 /// frame. The built-in runtime matcher resolves the frame's cParams from
563 /// the dictionary's CDict tier (upstream `ZSTD_createCDict`, keyed by the
564 /// serialized size) and sizes its dictionary tables from the content.
565 /// Default no-op for custom matchers and test stubs; consumed at the next
566 /// [`reset`](Self::reset).
567 fn set_dictionary_size_hint(&mut self, _sizes: DictionarySizes) {}
568 /// Drop any per-frame fine-grained parameter overrides installed via
569 /// the public parameter API, reverting to plain level-based geometry
570 /// at the next [`reset`](Self::reset). Called by
571 /// [`FrameCompressor::set_compression_level`](crate::encoding::FrameCompressor::set_compression_level)
572 /// so switching back to a bare level after a customized frame does not
573 /// keep the old overrides sticky. Default no-op for custom matchers.
574 fn clear_param_overrides(&mut self) {}
575 /// Prime matcher state with dictionary history before compressing the next frame.
576 /// Default implementation is a no-op for custom matchers that do not support this.
577 fn prime_with_dictionary(&mut self, _dict_content: &[u8], _offset_hist: [u32; 3]) {}
578 /// Whether the most recent [`reset`](Self::reset) re-borrowed a resident
579 /// attach-mode dictionary (kept the dict bytes + cached index in place).
580 /// When `true` the caller MUST skip [`Self::prime_with_dictionary`] and only
581 /// reapply the offset history via [`Self::reapply_resident_dictionary`].
582 fn dictionary_is_resident(&self) -> bool {
583 false
584 }
585 /// Reapply the dictionary's offset history to a re-borrowed frame — the cheap
586 /// tail of priming, without the dict commit / re-index. Default no-op.
587 fn reapply_resident_dictionary(&mut self, _offset_hist: [u32; 3]) {}
588 /// CDict-equivalent fast path for repeated frames sharing one dictionary.
589 /// Restore the matcher state captured by [`Self::capture_primed_dictionary`]
590 /// at the SAME level (a table copy) instead of re-running
591 /// [`Self::prime_with_dictionary`] (which re-hashes every dictionary
592 /// position). Returns `true` when a matching snapshot was restored;
593 /// `false` (the default) means the caller must prime then capture.
594 fn restore_primed_dictionary(&mut self, _level: CompressionLevel) -> bool {
595 false
596 }
597 /// Snapshot the post-prime matcher state for the given level so later
598 /// frames can [`Self::restore_primed_dictionary`] it. Default no-op.
599 fn capture_primed_dictionary(&mut self, _level: CompressionLevel) {}
600 /// Drop any captured prime snapshot (dictionary or level changed).
601 /// Default no-op.
602 fn invalidate_primed_dictionary(&mut self) {}
603 /// Seed matcher cost model with dictionary entropy tables before the next frame.
604 /// Default implementation is a no-op for custom matchers.
605 fn seed_dictionary_entropy(
606 &mut self,
607 _huff: Option<&crate::huff0::huff0_encoder::HuffmanTable>,
608 _ll: Option<&crate::fse::fse_encoder::FSETable>,
609 _ml: Option<&crate::fse::fse_encoder::FSETable>,
610 _of: Option<&crate::fse::fse_encoder::FSETable>,
611 ) {
612 }
613 /// Returns whether this matcher can consume dictionary priming state and produce
614 /// dictionary-dependent sequences. Defaults to `false` for custom matchers.
615 fn supports_dictionary_priming(&self) -> bool {
616 false
617 }
618 /// Whether a sample of `block` hashes to a match in an attached dictionary.
619 /// The raw-fast-path uses this to avoid skipping the scan on a block that
620 /// looks incompressible but compresses against the dictionary (an external
621 /// match the block's own content cannot reveal). Defaults to `false` for
622 /// custom matchers (and the no-dict case), leaving the content-only verdict.
623 fn block_samples_match_dict(&self, _block: &[u8]) -> bool {
624 false
625 }
626 /// Whether this matcher finds matches beyond what its regular tables
627 /// reach (long-distance matching). A block that looks incompressible can
628 /// then still be a repeat from far back, so the encoder searches every
629 /// block instead of writing one off on a sample. Defaults to `false`.
630 fn matches_beyond_window_tables(&self) -> bool {
631 false
632 }
633 /// Where to cut the next block, given the cut the block splitter
634 /// proposes `cut` bytes into the `window` bytes of
635 /// [`uncommitted_input`](Self::uncommitted_input) it looked at. A matcher
636 /// that already knows a match crossing the cut moves it out of the match,
637 /// at most to `window`; cutting inside one only makes the next block
638 /// start by repeating it. Defaults to `cut`.
639 fn settle_block_cut(&mut self, window: usize, cut: usize) -> usize {
640 let _ = window;
641 cut
642 }
643 /// The length the next block must take when this matcher lays the blocks
644 /// out itself (a replayed parse with block delimiters), or `None` to leave
645 /// the cut to the encoder. A block so laid out is written as one physical
646 /// block, held to the block size. Defaults to `None`.
647 fn fixed_block_len(&self) -> Option<usize> {
648 None
649 }
650 /// Heap bytes this matcher's allocations hold (tables, history, scratch),
651 /// excluding the inline struct itself. Lets a context report its true
652 /// footprint via `ZSTD_sizeof_CCtx`. Defaults to `0` for custom matchers.
653 fn heap_size(&self) -> usize {
654 0
655 }
656 /// The size of the window the decoder will need to execute all sequences produced by this matcher.
657 ///
658 /// Must return a positive (non-zero) value; returning 0 causes
659 /// [`StreamingEncoder`] to reject the first write with an invalid-input error
660 /// (`InvalidInput` with `std`, `Other` with `no_std`).
661 ///
662 /// Must remain stable for the lifetime of a frame.
663 /// It may change only after `reset()` is called for the next frame
664 /// (for example because the compression level changed).
665 fn window_size(&self) -> u64;
666}
667
668/// Sequences that a [`Matcher`] can produce, in block order.
669///
670/// A sequence names lengths, not bytes: the literals are the block's own bytes,
671/// and the encoder reads them from the block by position. The sequences of a
672/// block therefore cover it from its first byte, in order, each one's literals
673/// followed by its match, with any trailing literals last.
674#[derive(Clone, Copy, PartialEq, Eq, Debug)]
675pub enum Sequence {
676 /// `literal_len` literal bytes, then `match_len` bytes copied from `offset`
677 /// bytes back in the decoded data.
678 Triple {
679 literal_len: usize,
680 offset: usize,
681 match_len: usize,
682 },
683 /// The block's trailing literals, after its last match. Reported last.
684 Literals { len: usize },
685}
686
687#[cfg(test)]
688mod compress_bound_tests;
689#[cfg(test)]
690pub(crate) mod test_support;
691#[cfg(test)]
692mod tests;