Skip to main content

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