structured_zstd/decoding/mod.rs
1//! RFC 8878 Zstandard decoder.
2//!
3//! Three entry points are exposed, each with progressively lower-level
4//! control:
5//!
6//! * [`StreamingDecoder`] — implements [`crate::io::Read`] over a compressed
7//! byte stream, transparently parsing the frame header and concatenated
8//! frames. The typical choice for application code.
9//! * [`FrameDecoder`] — single-frame interface; use when the caller manages
10//! the input buffer manually (zero-copy slices, network framing, etc).
11//! * [`DictionaryHandle`] — pre-parsed dictionary handle. Parse the
12//! dictionary bytes once with [`DictionaryHandle::decode_dict`] and reuse
13//! the handle across every subsequent decode; saves the per-frame
14//! dictionary parse cost when the same dictionary is used many times in a
15//! row.
16//!
17//! Both decoders expose dictionary-aware constructors / methods,
18//! though the exact naming differs:
19//!
20//! * [`StreamingDecoder::new_with_dictionary_handle`] /
21//! [`StreamingDecoder::new_with_dictionary_bytes`]
22//! * [`FrameDecoder::decode_all_with_dict_handle`] /
23//! [`FrameDecoder::decode_all_with_dict_bytes`]
24//!
25//! The `_handle` variants reuse a previously parsed
26//! [`DictionaryHandle`]; the `_bytes` variants parse the dictionary
27//! per call (suitable for one-off decodes).
28//!
29//! Errors surface through [`errors::FrameDecoderError`] and the per-decoder
30//! error types in the [`errors`] submodule.
31
32pub mod errors;
33mod frame_decoder;
34mod streaming_decoder;
35
36pub use dictionary::{Dictionary, DictionaryHandle, MAGIC_NUM as DICTIONARY_MAGIC};
37pub use frame_decoder::{BlockDecodingStrategy, ContentChecksum, FrameDecoder};
38
39/// Largest window a frame may declare before this decoder refuses it.
40///
41/// The bound is what makes decoding untrusted input safe: a frame header can
42/// ask for a window far larger than the data behind it, and honouring that
43/// would let a few bytes of input demand gigabytes of memory. Callers that
44/// impose their own ceiling can compare against this one to see which is the
45/// stricter, and tools can report the bound they actually enforce.
46pub use crate::common::MAXIMUM_ALLOWED_WINDOW_SIZE;
47#[cfg(feature = "lsm")]
48pub use frame_decoder::{PartialDecode, ResumeInput, ResumeState};
49pub use streaming_decoder::StreamingDecoder;
50
51/// Decompressed size a frame declares in its header, as read by
52/// [`read_frame_content_size`] without decoding the frame body.
53#[derive(Copy, Clone, Debug, PartialEq, Eq)]
54pub enum FrameContentSize {
55 /// The header carried an explicit `Frame_Content_Size` field (in bytes).
56 Known(u64),
57 /// The header did not declare a content size; the true size is only
58 /// known after decoding (or from out-of-band knowledge).
59 Unknown,
60}
61
62/// Read the decompressed size a frame declares in its header, without
63/// decoding the frame body.
64///
65/// Parses only the leading frame header of `src`. Returns
66/// [`FrameContentSize::Known`] when the header carries an explicit
67/// `Frame_Content_Size`, or [`FrameContentSize::Unknown`] when it does not.
68/// This backs the C `ZSTD_getFrameContentSize` entry point, where the two
69/// variants map to a concrete size and `ZSTD_CONTENTSIZE_UNKNOWN`.
70///
71/// # Errors
72/// Returns [`ReadFrameHeaderError`](errors::ReadFrameHeaderError) when `src`
73/// is too short to hold a header, carries a bad magic number, or begins with
74/// a skippable frame.
75///
76/// ```rust
77/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
78/// use structured_zstd::decoding::{read_frame_content_size, FrameContentSize};
79/// let frame = compress_slice_to_vec(&[42u8; 100], CompressionLevel::Default);
80/// assert_eq!(read_frame_content_size(&frame).unwrap(), FrameContentSize::Known(100));
81/// ```
82pub fn read_frame_content_size(
83 src: &[u8],
84) -> Result<FrameContentSize, errors::ReadFrameHeaderError> {
85 let (header, _consumed) = frame::read_frame_header_with_format(src, false)?;
86 Ok(if header.fcs_declared() {
87 FrameContentSize::Known(header.frame_content_size())
88 } else {
89 FrameContentSize::Unknown
90 })
91}
92
93/// Error from [`find_frame_compressed_size`].
94#[derive(Debug)]
95pub enum FrameSizeError {
96 /// The frame header could not be parsed.
97 Header(errors::ReadFrameHeaderError),
98 /// The buffer ends before the frame's blocks (or trailing checksum) are
99 /// complete.
100 Truncated,
101 /// A block declared the reserved block type, which is invalid per RFC 8878.
102 ReservedBlock,
103 /// A block declared a `Block_Size` larger than the frame's
104 /// `Block_Maximum_Size` (`min(Window_Size, 128 KiB)`), which is invalid per
105 /// RFC 8878 §3.1.1.2. Accepting it would let a corrupt frame pass a size
106 /// query and make the no-`Frame_Content_Size` decompressed-bound
107 /// under-count (each block can regenerate at most `Block_Maximum_Size`).
108 OversizedBlock,
109}
110
111/// On-disk byte length of the FIRST frame in `src` — magic number, frame
112/// header, every block, and the trailing content checksum when present —
113/// computed by walking the block headers without decoding any block body.
114///
115/// For a skippable frame, returns its full `8 + Frame_Size` length. This backs
116/// the C `ZSTD_findFrameCompressedSize` entry point; the returned value is the
117/// offset at which a following concatenated frame would begin.
118///
119/// # Errors
120/// [`FrameSizeError`] when the header is unreadable, the buffer is truncated
121/// mid-frame, or a block uses the reserved type.
122///
123/// ```rust
124/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
125/// use structured_zstd::decoding::find_frame_compressed_size;
126/// let frame = compress_slice_to_vec(&[5u8; 256], CompressionLevel::Default);
127/// assert_eq!(find_frame_compressed_size(&frame).unwrap(), frame.len());
128/// ```
129pub fn find_frame_compressed_size(src: &[u8]) -> Result<usize, FrameSizeError> {
130 let (header, header_len) = match frame::read_frame_header_with_format(src, false) {
131 Ok(parsed) => parsed,
132 // Skippable frame: magic (4) + Frame_Size field (4) + payload.
133 Err(errors::ReadFrameHeaderError::SkipFrame { length, .. }) => {
134 return 8usize
135 .checked_add(length as usize)
136 .filter(|end| *end <= src.len())
137 .ok_or(FrameSizeError::Truncated);
138 }
139 Err(e) => return Err(FrameSizeError::Header(e)),
140 };
141
142 let walk = walk_blocks(src, header_len as usize, frame_block_size_max(&header))?;
143 if header.descriptor.content_checksum_flag() {
144 walk.end
145 .checked_add(4)
146 .filter(|end| *end <= src.len())
147 .ok_or(FrameSizeError::Truncated)
148 } else {
149 Ok(walk.end)
150 }
151}
152
153/// Result of walking the block sequence of one frame (between the header and
154/// the optional trailing checksum).
155struct BlockWalk {
156 /// Offset just past the last block (before any content checksum).
157 end: usize,
158 /// Number of blocks in the frame.
159 count: u64,
160}
161
162/// `Block_Maximum_Size` for the frame: `min(Window_Size, 128 KiB)`. Per RFC
163/// 8878 §3.1.1.2 every block's `Block_Size` is bounded by this, and each block
164/// regenerates at most this many bytes. Single-segment frames omit the
165/// `Window_Descriptor`; their window equals the declared content size.
166fn frame_block_size_max(header: &frame::FrameHeader) -> usize {
167 let window_size = match header.window_descriptor() {
168 Some(desc) => {
169 let exponent = u64::from(desc >> 3);
170 let mantissa = u64::from(desc & 0x7);
171 let window_base = 1u64 << (10 + exponent);
172 window_base + (window_base / 8) * mantissa
173 }
174 None => header.frame_content_size(),
175 };
176 // The 128 KiB cap keeps the result within usize on every target.
177 window_size.min(128 * 1024) as usize
178}
179
180/// Walk the block headers of a single frame starting at `start` (the offset of
181/// the first block header), validating each fits in `src` and declares a
182/// `Block_Size` no larger than `max_block_size` (the frame's
183/// `Block_Maximum_Size`). Does not consume the trailing content checksum.
184/// Shared by [`find_frame_compressed_size`] and [`frame_decompressed_bound`] so
185/// the on-disk-size and block-count views never diverge.
186fn walk_blocks(
187 src: &[u8],
188 start: usize,
189 max_block_size: usize,
190) -> Result<BlockWalk, FrameSizeError> {
191 let mut offset = start;
192 let mut count = 0u64;
193 loop {
194 // 3-byte block header (RFC 8878 §3.1.1.2): bit0 last-block flag,
195 // bits1-2 block type, bits3-23 Block_Size.
196 let hdr = src
197 .get(offset..offset + 3)
198 .ok_or(FrameSizeError::Truncated)?;
199 let raw = u32::from(hdr[0]) | (u32::from(hdr[1]) << 8) | (u32::from(hdr[2]) << 16);
200 let last_block = (raw & 1) != 0;
201 let block_type = (raw >> 1) & 0b11;
202 let block_size = (raw >> 3) as usize;
203 // On-disk bytes following the header: RLE stores a single byte
204 // regardless of the run length; Raw/Compressed store Block_Size bytes;
205 // the reserved type is invalid.
206 let on_disk = match block_type {
207 1 => 1, // RLE
208 0 | 2 => block_size, // Raw / Compressed
209 _ => return Err(FrameSizeError::ReservedBlock),
210 };
211 // RFC 8878 §3.1.1.2: Block_Size MUST NOT exceed Block_Maximum_Size for
212 // any block type (it bounds both the on-disk Raw/Compressed payload and
213 // the RLE/Raw regenerated size). Reject rather than accept a corrupt
214 // declaration that would otherwise pass the size query and let the
215 // no-FCS bound under-count.
216 if block_size > max_block_size {
217 return Err(FrameSizeError::OversizedBlock);
218 }
219 offset = offset
220 .checked_add(3 + on_disk)
221 .filter(|end| *end <= src.len())
222 .ok_or(FrameSizeError::Truncated)?;
223 count += 1;
224 if last_block {
225 break;
226 }
227 }
228 Ok(BlockWalk { end: offset, count })
229}
230
231/// Upper bound on the decompressed size of the FIRST frame in `src`, without
232/// decoding the body. Backs the C `ZSTD_decompressBound` (per-frame term).
233///
234/// Returns the exact size when the header declares `Frame_Content_Size`;
235/// otherwise a valid (loose) bound of `block_count * block_size_max`, where
236/// `block_size_max = min(window_size, 128 KiB)` — every block decompresses to
237/// at most that many bytes. Skippable frames contribute `0`.
238///
239/// # Errors
240/// [`FrameSizeError`] on an unreadable header, truncation, or a reserved block.
241pub fn frame_decompressed_bound(src: &[u8]) -> Result<u64, FrameSizeError> {
242 let (header, header_len) = match frame::read_frame_header_with_format(src, false) {
243 Ok(parsed) => parsed,
244 // Skippable frame contributes 0, but its full payload must be present:
245 // truncation is an error per this function's contract.
246 Err(errors::ReadFrameHeaderError::SkipFrame { length, .. }) => {
247 return 8usize
248 .checked_add(length as usize)
249 .filter(|end| *end <= src.len())
250 .map(|_| 0)
251 .ok_or(FrameSizeError::Truncated);
252 }
253 Err(e) => return Err(FrameSizeError::Header(e)),
254 };
255
256 // Walk the blocks (and the optional checksum trailer) so a truncated frame
257 // is rejected even when Frame_Content_Size is declared — without this the
258 // declared-FCS path would return a bound for an incomplete buffer. The
259 // per-frame block maximum both bounds the walk and scales the no-FCS bound.
260 let block_size_max = frame_block_size_max(&header);
261 let walk = walk_blocks(src, header_len as usize, block_size_max)?;
262 if header.descriptor.content_checksum_flag() {
263 walk.end
264 .checked_add(4)
265 .filter(|end| *end <= src.len())
266 .ok_or(FrameSizeError::Truncated)?;
267 }
268
269 if header.fcs_declared() {
270 return Ok(header.frame_content_size());
271 }
272 // Saturating is intentional here: this is an UPPER bound, so capping at the
273 // maximum representable value is the correct ceiling for a pathologically
274 // large frame, not a masked arithmetic bug. Each of `walk.count` blocks
275 // regenerates at most `block_size_max` bytes (now enforced by `walk_blocks`,
276 // so the bound can no longer be undercut by an oversized block header).
277 Ok(walk.count.saturating_mul(block_size_max as u64))
278}
279
280/// Frame header fields decoded by [`read_frame_header_info`], mirroring the
281/// values the C `ZSTD_getFrameHeader` fills into a `ZSTD_FrameHeader`.
282#[derive(Copy, Clone, Debug)]
283pub struct FrameHeaderInfo {
284 /// Declared decompressed size, or [`FrameContentSize::Unknown`] when the
285 /// header omits the `Frame_Content_Size` field.
286 pub content_size: FrameContentSize,
287 /// Decoder window size in bytes (the minimum buffer needed to decode the
288 /// frame). For single-segment frames this equals the content size.
289 pub window_size: u64,
290 /// Dictionary id required to decode the frame, if the header carries one.
291 pub dictionary_id: Option<u32>,
292 /// Whether a 32-bit content checksum trails the frame.
293 pub content_checksum: bool,
294 /// Header length in bytes, measured in the parsed input format: it includes
295 /// the 4-byte magic number in the default format, but excludes it when
296 /// parsed as magicless (`read_frame_header_info(.., true)`), since those 4
297 /// bytes are not present on the wire in that mode.
298 pub header_size: usize,
299}
300
301/// Length in bytes of the frame header at the start of `src`, including the
302/// 4-byte magic number (the offset at which the first block begins). Backs the
303/// C `ZSTD_frameHeaderSize`.
304///
305/// # Errors
306/// [`ReadFrameHeaderError`](errors::ReadFrameHeaderError) when the header is
307/// too short, has a bad magic number, or is a skippable frame.
308pub fn frame_header_size(src: &[u8]) -> Result<usize, errors::ReadFrameHeaderError> {
309 let (_header, consumed) = frame::read_frame_header_with_format(src, false)?;
310 Ok(consumed as usize)
311}
312
313/// Decode the leading frame header fields of `src` without decoding the body.
314///
315/// Backs the C `ZSTD_getFrameHeader`. When `magicless` is `true` the 4-byte
316/// magic prefix is assumed absent (the `ZSTD_f_zstd1_magicless` format); the
317/// caller must know out-of-band that the stream is magicless. The reported
318/// [`FrameHeaderInfo::window_size`] is the raw value derived from the header
319/// (no maximum-window policy applied here; that bound is enforced at decode
320/// time), so callers see the frame's own declared window even when it exceeds
321/// a decoder limit.
322///
323/// # Errors
324/// As [`read_frame_content_size`].
325///
326/// ```rust
327/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
328/// use structured_zstd::decoding::{read_frame_header_info, FrameContentSize};
329/// let frame = compress_slice_to_vec(&[7u8; 512], CompressionLevel::Default);
330/// let info = read_frame_header_info(&frame, false).unwrap();
331/// assert_eq!(info.content_size, FrameContentSize::Known(512));
332/// assert!(info.window_size >= 512);
333/// ```
334pub fn read_frame_header_info(
335 src: &[u8],
336 magicless: bool,
337) -> Result<FrameHeaderInfo, errors::ReadFrameHeaderError> {
338 let (header, consumed) = frame::read_frame_header_with_format(src, magicless)?;
339 let content_size = if header.fcs_declared() {
340 FrameContentSize::Known(header.frame_content_size())
341 } else {
342 FrameContentSize::Unknown
343 };
344 // Compute the window size without the decode-time maximum-window check
345 // (RFC 8878 §3.1.1.1.2). `window_descriptor()` returns `None` for a
346 // single-segment frame, where the window equals the content size.
347 let window_size = match header.window_descriptor() {
348 Some(desc) => {
349 let exponent = u64::from(desc >> 3);
350 let mantissa = u64::from(desc & 0x7);
351 let window_base = 1u64 << (10 + exponent);
352 window_base + (window_base / 8) * mantissa
353 }
354 None => header.frame_content_size(),
355 };
356 Ok(FrameHeaderInfo {
357 content_size,
358 window_size,
359 dictionary_id: header.dictionary_id(),
360 content_checksum: header.descriptor.content_checksum_flag(),
361 header_size: consumed as usize,
362 })
363}
364
365pub(crate) mod block_decoder;
366pub(crate) mod buffer_backend;
367pub(crate) mod decode_buffer;
368pub(crate) mod dictionary;
369pub(crate) mod exec_sequence_inline;
370// FlatBuf is the compile-time-monomorphised "frame fits in window"
371// backend selected via `DecodeBuffer<FlatBuf>`. `FrameDecoder`'s
372// `DecoderScratchKind` picks it when the frame header has
373// `Single_Segment_flag` set; the ring backend remains the default
374// for multi-segment frames. See backlog item #132 for the wiring
375// rationale.
376pub(crate) mod flat_buf;
377pub(crate) mod frame;
378pub(crate) mod literals_section_decoder;
379pub(crate) mod prefetch;
380mod ringbuffer;
381#[allow(dead_code)]
382pub(crate) mod scratch;
383// Per-kernel monolithic sequence-section decoder entry points. Each
384// kernel has its own self-contained function with the full pipeline
385// (outer init, both arms, decode_one, execute_one) inlined inside one
386// `#[target_feature]`-scoped body. The dispatcher in
387// `sequence_section_decoder::decode_and_execute_sequences` selects the
388// kernel ONCE per call via cached `detect_cpu_kernel`. aarch64 Neon
389// and Sve still go through the K-generic
390// `decode_and_execute_sequences_impl` shared body until their own
391// monoliths land.
392//
393// The shared helpers (`decode_and_execute_sequences_impl`,
394// `run_pipelined_sequence_loop`, `decode_one_sequence_inline`, the
395// `execute_one_sequence_pipelined*` wrappers) live on aarch64
396// (Neon/Sve dispatch arms in `decode_and_execute_sequences`) and in
397// tests, but are orphan on x86_64 production builds where the
398// per-kernel monoliths bypass them entirely. Each carries
399// `#[allow(dead_code)]` so the `-D warnings` clippy gate stays green
400// on x86_64 without losing the cross-arch reuse. The vestigial
401// `_bmi2`/`_avx2`/`_vbmi2` variants are pre-R12 macro-dispatch
402// helpers with no remaining callers; they should be cleaned up in
403// a follow-up PR once the per-kernel monolithic shape is fully
404// settled.
405#[cfg(all(target_arch = "x86_64", feature = "kernel-avx2"))]
406pub(crate) mod seq_decoder_avx2;
407#[cfg(all(target_arch = "x86_64", feature = "kernel-bmi2"))]
408pub(crate) mod seq_decoder_bmi2;
409pub(crate) mod seq_decoder_scalar;
410#[cfg(all(target_arch = "x86_64", feature = "kernel-vbmi2"))]
411pub(crate) mod seq_decoder_vbmi2;
412pub(crate) mod sequence_execution;
413pub(crate) mod sequence_section_decoder;
414pub(crate) mod simd_copy;
415/// Diagnostic-only re-export of the copy-shape histogram counters. Public
416/// only when the `copy-shape-stats` feature is on (off in shipping builds).
417#[cfg(feature = "copy-shape-stats")]
418pub use simd_copy::shape_stats;
419// `UserSliceBackend` is the compile-time-monomorphised backend that
420// writes directly into the caller's `&mut [u8]` output slice, used
421// by the `FrameDecoder::decode_all` direct-decode path. It
422// eliminates the `FlatBuf` drain copy + anonymous-page-fault cost
423// on large literal sections. Wiring happens via
424// `DecodeBuffer<UserSliceBackend<'a>>`; the lifetime binds the
425// backend to the caller's slice for the call duration.
426pub(crate) mod user_slice_buf;
427
428#[cfg(feature = "bench-internals")]
429pub(crate) use self::simd_copy::copy_bytes_overshooting_for_bench;
430
431#[cfg(test)]
432mod frame_inspection_tests;