structured_zstd/decoding/mod.rs
1//! RFC 8878 Zstandard decoder.
2//!
3//! The entry points:
4//!
5//! * [`StreamingDecoder`] — implements [`crate::io::Read`] over a compressed
6//! byte stream, transparently parsing the frame header and concatenated
7//! frames. The typical choice for application code.
8//! * [`FrameDecoder`] — single-frame interface; use when the caller manages
9//! the input buffer manually (zero-copy slices, network framing, etc).
10//! * `workspace::WorkspaceDecoder` (feature `caller-workspace`): streaming
11//! decoder over a byte slice the caller sizes and owns, with no allocation
12//! and limits on window, output and work. For consumers that account their
13//! memory themselves, such as browser bundles verifying a download.
14//! * [`DictionaryHandle`] — pre-parsed dictionary handle. Parse the
15//! dictionary bytes once with [`DictionaryHandle::decode_dict`] and reuse
16//! the handle across every subsequent decode; saves the per-frame
17//! dictionary parse cost when the same dictionary is used many times in a
18//! row.
19//!
20//! Both decoders expose dictionary-aware constructors / methods,
21//! though the exact naming differs:
22//!
23//! * [`StreamingDecoder::new_with_dictionary_handle`] /
24//! [`StreamingDecoder::new_with_dictionary_bytes`]
25//! * [`FrameDecoder::decode_all_with_dict_handle`] /
26//! [`FrameDecoder::decode_all_with_dict_bytes`]
27//!
28//! The `_handle` variants reuse a previously parsed
29//! [`DictionaryHandle`]; the `_bytes` variants parse the dictionary
30//! per call (suitable for one-off decodes).
31//!
32//! Errors surface through [`errors::FrameDecoderError`] and the per-decoder
33//! error types in the [`errors`] submodule.
34
35pub mod errors;
36mod frame_decoder;
37mod streaming_decoder;
38#[cfg(feature = "caller-workspace")]
39#[cfg_attr(docsrs, doc(cfg(feature = "caller-workspace")))]
40pub mod workspace;
41
42pub use dictionary::{Dictionary, DictionaryHandle, MAGIC_NUM as DICTIONARY_MAGIC};
43pub use frame_decoder::{BlockDecodingStrategy, ContentChecksum, FrameDecoder};
44
45/// Largest window a frame may declare before a decoder that holds the window
46/// itself refuses it, unless [`FrameDecoder::set_max_window_size`] raised the
47/// ceiling.
48///
49/// The bound is what makes decoding untrusted input safe: a frame header can
50/// ask for a window far larger than the data behind it, and honouring that
51/// would let a few bytes of input demand gigabytes of memory.
52pub use crate::common::MAXIMUM_ALLOWED_WINDOW_SIZE;
53
54/// Largest window any decode accepts, the ceiling
55/// [`FrameDecoder::set_max_window_size`] can be raised to.
56pub use crate::common::MAX_DECODER_WINDOW_SIZE;
57#[cfg(feature = "lsm")]
58pub use frame_decoder::{PartialDecode, ResumeInput, ResumeState};
59pub use streaming_decoder::{DictionaryRefused, FinishedFrame, StreamingDecoder};
60
61/// Decompressed size a frame declares in its header, as read by
62/// [`read_frame_content_size`] without decoding the frame body.
63#[derive(Copy, Clone, Debug, PartialEq, Eq)]
64pub enum FrameContentSize {
65 /// The header carried an explicit `Frame_Content_Size` field (in bytes).
66 Known(u64),
67 /// The header did not declare a content size; the true size is only
68 /// known after decoding (or from out-of-band knowledge).
69 Unknown,
70}
71
72/// Read the decompressed size a frame declares in its header, without
73/// decoding the frame body.
74///
75/// Parses only the leading frame header of `src`. Returns
76/// [`FrameContentSize::Known`] when the header carries an explicit
77/// `Frame_Content_Size`, or [`FrameContentSize::Unknown`] when it does not.
78/// This backs the C `ZSTD_getFrameContentSize` entry point, where the two
79/// variants map to a concrete size and `ZSTD_CONTENTSIZE_UNKNOWN`.
80///
81/// # Errors
82/// Returns [`ReadFrameHeaderError`](errors::ReadFrameHeaderError) when `src`
83/// is too short to hold a header, carries a bad magic number, or begins with
84/// a skippable frame.
85///
86/// ```rust
87/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
88/// use structured_zstd::decoding::{read_frame_content_size, FrameContentSize};
89/// let frame = compress_slice_to_vec(&[42u8; 100], CompressionLevel::Default);
90/// assert_eq!(read_frame_content_size(&frame).unwrap(), FrameContentSize::Known(100));
91/// ```
92pub fn read_frame_content_size(
93 src: &[u8],
94) -> Result<FrameContentSize, errors::ReadFrameHeaderError> {
95 let (header, _consumed) = frame::read_frame_header_with_format(src, false)?;
96 Ok(if header.fcs_declared() {
97 FrameContentSize::Known(header.frame_content_size())
98 } else {
99 FrameContentSize::Unknown
100 })
101}
102
103/// Error from [`find_frame_compressed_size`].
104#[derive(Debug)]
105pub enum FrameSizeError {
106 /// The frame header could not be parsed.
107 Header(errors::ReadFrameHeaderError),
108 /// The buffer ends before the frame's blocks (or trailing checksum) are
109 /// complete.
110 Truncated,
111 /// A block declared the reserved block type, which is invalid per RFC 8878.
112 ReservedBlock,
113 /// A block declared a `Block_Size` larger than the frame's
114 /// `Block_Maximum_Size` (`min(Window_Size, 128 KiB)`), which is invalid per
115 /// RFC 8878 §3.1.1.2. Accepting it would let a corrupt frame pass a size
116 /// query and make the no-`Frame_Content_Size` decompressed-bound
117 /// under-count (each block can regenerate at most `Block_Maximum_Size`).
118 OversizedBlock,
119}
120
121/// On-disk byte length of the FIRST frame in `src` — magic number, frame
122/// header, every block, and the trailing content checksum when present —
123/// computed by walking the block headers without decoding any block body.
124///
125/// For a skippable frame, returns its full `8 + Frame_Size` length. This backs
126/// the C `ZSTD_findFrameCompressedSize` entry point; the returned value is the
127/// offset at which a following concatenated frame would begin.
128///
129/// # Errors
130/// [`FrameSizeError`] when the header is unreadable, the buffer is truncated
131/// mid-frame, or a block uses the reserved type.
132///
133/// ```rust
134/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
135/// use structured_zstd::decoding::find_frame_compressed_size;
136/// let frame = compress_slice_to_vec(&[5u8; 256], CompressionLevel::Default);
137/// assert_eq!(find_frame_compressed_size(&frame).unwrap(), frame.len());
138/// ```
139pub fn find_frame_compressed_size(src: &[u8]) -> Result<usize, FrameSizeError> {
140 let (header, header_len) = match frame::read_frame_header_with_format(src, false) {
141 Ok(parsed) => parsed,
142 // Skippable frame: magic (4) + Frame_Size field (4) + payload.
143 Err(errors::ReadFrameHeaderError::SkipFrame { length, .. }) => {
144 return 8usize
145 .checked_add(length as usize)
146 .filter(|end| *end <= src.len())
147 .ok_or(FrameSizeError::Truncated);
148 }
149 Err(e) => return Err(FrameSizeError::Header(e)),
150 };
151
152 let walk = walk_blocks(src, header_len as usize, frame_block_size_max(&header))?;
153 if header.descriptor.content_checksum_flag() {
154 walk.end
155 .checked_add(4)
156 .filter(|end| *end <= src.len())
157 .ok_or(FrameSizeError::Truncated)
158 } else {
159 Ok(walk.end)
160 }
161}
162
163/// Error from [`find_decompressed_size`].
164#[derive(Debug)]
165pub enum DecompressedSizeError {
166 /// A frame header could not be parsed.
167 Header(errors::ReadFrameHeaderError),
168 /// A frame's blocks could not be walked to the start of the next one.
169 Frame(FrameSizeError),
170 /// The declared sizes add up past `u64::MAX`.
171 Overflow,
172}
173
174/// Total decompressed size the frames in `src` declare, without decoding any
175/// of them: [`FrameContentSize::Known`] with the sum, skippable frames counting
176/// zero, or [`FrameContentSize::Unknown`] as soon as one frame does not declare
177/// its size. Backs the C `ZSTD_findDecompressedSize` entry point.
178///
179/// # Errors
180/// [`DecompressedSizeError`] when a frame is malformed or truncated, or the
181/// sizes overflow `u64`.
182///
183/// ```rust
184/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
185/// use structured_zstd::decoding::{find_decompressed_size, FrameContentSize};
186/// let mut frames = compress_slice_to_vec(&[1u8; 300], CompressionLevel::Default);
187/// frames.extend(compress_slice_to_vec(&[2u8; 200], CompressionLevel::Default));
188/// assert_eq!(find_decompressed_size(&frames).unwrap(), FrameContentSize::Known(500));
189/// ```
190pub fn find_decompressed_size(src: &[u8]) -> Result<FrameContentSize, DecompressedSizeError> {
191 let mut rest = src;
192 let mut total: u64 = 0;
193 while !rest.is_empty() {
194 match read_frame_content_size(rest) {
195 Ok(FrameContentSize::Known(size)) => {
196 total = total
197 .checked_add(size)
198 .ok_or(DecompressedSizeError::Overflow)?;
199 }
200 Ok(FrameContentSize::Unknown) => return Ok(FrameContentSize::Unknown),
201 // A skippable frame regenerates nothing.
202 Err(errors::ReadFrameHeaderError::SkipFrame { .. }) => {}
203 Err(err) => return Err(DecompressedSizeError::Header(err)),
204 }
205 let consumed = find_frame_compressed_size(rest).map_err(DecompressedSizeError::Frame)?;
206 rest = &rest[consumed..];
207 }
208 Ok(FrameContentSize::Known(total))
209}
210
211/// Result of walking the block sequence of one frame (between the header and
212/// the optional trailing checksum).
213struct BlockWalk {
214 /// Offset just past the last block (before any content checksum).
215 end: usize,
216 /// Number of blocks in the frame.
217 count: u64,
218}
219
220/// `Block_Maximum_Size` for the frame: `min(Window_Size, 128 KiB)`. Per RFC
221/// 8878 §3.1.1.2 every block's `Block_Size` is bounded by this, and each block
222/// regenerates at most this many bytes. Single-segment frames omit the
223/// `Window_Descriptor`; their window equals the declared content size.
224fn frame_block_size_max(header: &frame::FrameHeader) -> usize {
225 let window_size = match header.window_descriptor() {
226 Some(desc) => {
227 let exponent = u64::from(desc >> 3);
228 let mantissa = u64::from(desc & 0x7);
229 let window_base = 1u64 << (10 + exponent);
230 window_base + (window_base / 8) * mantissa
231 }
232 None => header.frame_content_size(),
233 };
234 // The 128 KiB cap keeps the result within usize on every target.
235 window_size.min(128 * 1024) as usize
236}
237
238/// Walk the block headers of a single frame starting at `start` (the offset of
239/// the first block header), validating each fits in `src` and declares a
240/// `Block_Size` no larger than `max_block_size` (the frame's
241/// `Block_Maximum_Size`). Does not consume the trailing content checksum.
242/// Shared by [`find_frame_compressed_size`] and [`frame_decompressed_bound`] so
243/// the on-disk-size and block-count views never diverge.
244fn walk_blocks(
245 src: &[u8],
246 start: usize,
247 max_block_size: usize,
248) -> Result<BlockWalk, FrameSizeError> {
249 let mut offset = start;
250 let mut count = 0u64;
251 loop {
252 // 3-byte block header (RFC 8878 §3.1.1.2): bit0 last-block flag,
253 // bits1-2 block type, bits3-23 Block_Size.
254 let hdr = src
255 .get(offset..offset + 3)
256 .ok_or(FrameSizeError::Truncated)?;
257 let raw = u32::from(hdr[0]) | (u32::from(hdr[1]) << 8) | (u32::from(hdr[2]) << 16);
258 let last_block = (raw & 1) != 0;
259 let block_type = (raw >> 1) & 0b11;
260 let block_size = (raw >> 3) as usize;
261 // On-disk bytes following the header: RLE stores a single byte
262 // regardless of the run length; Raw/Compressed store Block_Size bytes;
263 // the reserved type is invalid.
264 let on_disk = match block_type {
265 1 => 1, // RLE
266 0 | 2 => block_size, // Raw / Compressed
267 _ => return Err(FrameSizeError::ReservedBlock),
268 };
269 // RFC 8878 §3.1.1.2: Block_Size MUST NOT exceed Block_Maximum_Size for
270 // any block type (it bounds both the on-disk Raw/Compressed payload and
271 // the RLE/Raw regenerated size). Reject rather than accept a corrupt
272 // declaration that would otherwise pass the size query and let the
273 // no-FCS bound under-count.
274 if block_size > max_block_size {
275 return Err(FrameSizeError::OversizedBlock);
276 }
277 offset = offset
278 .checked_add(3 + on_disk)
279 .filter(|end| *end <= src.len())
280 .ok_or(FrameSizeError::Truncated)?;
281 count += 1;
282 if last_block {
283 break;
284 }
285 }
286 Ok(BlockWalk { end: offset, count })
287}
288
289/// Upper bound on the decompressed size of the FIRST frame in `src`, without
290/// decoding the body. Backs the C `ZSTD_decompressBound` (per-frame term).
291///
292/// Returns the exact size when the header declares `Frame_Content_Size`;
293/// otherwise a valid (loose) bound of `block_count * block_size_max`, where
294/// `block_size_max = min(window_size, 128 KiB)` — every block decompresses to
295/// at most that many bytes. Skippable frames contribute `0`.
296///
297/// # Errors
298/// [`FrameSizeError`] on an unreadable header, truncation, or a reserved block.
299pub fn frame_decompressed_bound(src: &[u8]) -> Result<u64, FrameSizeError> {
300 let (header, header_len) = match frame::read_frame_header_with_format(src, false) {
301 Ok(parsed) => parsed,
302 // Skippable frame contributes 0, but its full payload must be present:
303 // truncation is an error per this function's contract.
304 Err(errors::ReadFrameHeaderError::SkipFrame { length, .. }) => {
305 return 8usize
306 .checked_add(length as usize)
307 .filter(|end| *end <= src.len())
308 .map(|_| 0)
309 .ok_or(FrameSizeError::Truncated);
310 }
311 Err(e) => return Err(FrameSizeError::Header(e)),
312 };
313
314 // Walk the blocks (and the optional checksum trailer) so a truncated frame
315 // is rejected even when Frame_Content_Size is declared — without this the
316 // declared-FCS path would return a bound for an incomplete buffer. The
317 // per-frame block maximum both bounds the walk and scales the no-FCS bound.
318 let block_size_max = frame_block_size_max(&header);
319 let walk = walk_blocks(src, header_len as usize, block_size_max)?;
320 if header.descriptor.content_checksum_flag() {
321 walk.end
322 .checked_add(4)
323 .filter(|end| *end <= src.len())
324 .ok_or(FrameSizeError::Truncated)?;
325 }
326
327 if header.fcs_declared() {
328 return Ok(header.frame_content_size());
329 }
330 // Saturating is intentional here: this is an UPPER bound, so capping at the
331 // maximum representable value is the correct ceiling for a pathologically
332 // large frame, not a masked arithmetic bug. Each of `walk.count` blocks
333 // regenerates at most `block_size_max` bytes (now enforced by `walk_blocks`,
334 // so the bound can no longer be undercut by an oversized block header).
335 Ok(walk.count.saturating_mul(block_size_max as u64))
336}
337
338/// What the first four bytes of a buffer announce, read by [`frame_magic`].
339#[derive(Copy, Clone, Debug, PartialEq, Eq)]
340pub enum FrameMagic {
341 /// A Zstandard frame (RFC 8878 §3.1.1).
342 Zstd,
343 /// A skippable frame (RFC 8878 §3.1.2) with its magic variant, `0..=15`.
344 Skippable(u8),
345}
346
347/// Which frame `src` starts with, judged by its magic number alone. Backs the
348/// C `ZSTD_isFrame` / `ZSTD_isSkippableFrame`. `None` when `src` is shorter
349/// than four bytes or the magic is no frame's.
350///
351/// ```rust
352/// use structured_zstd::decoding::{frame_magic, FrameMagic};
353/// assert_eq!(frame_magic(&[0x28, 0xB5, 0x2F, 0xFD]), Some(FrameMagic::Zstd));
354/// assert_eq!(frame_magic(&[0x53, 0x2A, 0x4D, 0x18]), Some(FrameMagic::Skippable(3)));
355/// assert_eq!(frame_magic(b"zip"), None);
356/// ```
357pub fn frame_magic(src: &[u8]) -> Option<FrameMagic> {
358 let magic = u32::from_le_bytes(src.get(..4)?.try_into().ok()?);
359 if magic == crate::common::MAGIC_NUM {
360 Some(FrameMagic::Zstd)
361 } else if magic & 0xFFFF_FFF0 == crate::skippable::SKIPPABLE_MAGIC_START {
362 Some(FrameMagic::Skippable((magic & 0xF) as u8))
363 } else {
364 None
365 }
366}
367
368/// Bytes the compressed input must trail the end of the output by for every
369/// frame in `src` to decode in place: the compressed data sits at the end of
370/// the output buffer and the decoder writes from its start. Backs the C
371/// `ZSTD_decompressionMargin`.
372///
373/// A frame costs its header, its checksum and three bytes per block, a
374/// skippable frame its whole length, and one largest block is added on top:
375/// a block may expand to its full size before its compressed bytes are
376/// consumed (upstream `zstd_decompress.c:838-879`).
377///
378/// # Errors
379/// [`FrameSizeError`] on an unreadable header, truncation, or an invalid block.
380///
381/// ```rust
382/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
383/// use structured_zstd::decoding::decompression_margin;
384/// let frame = compress_slice_to_vec(&[9u8; 4096], CompressionLevel::Default);
385/// assert!(decompression_margin(&frame).unwrap() >= frame.len().min(4096));
386/// ```
387pub fn decompression_margin(src: &[u8]) -> Result<usize, FrameSizeError> {
388 let mut rest = src;
389 let mut margin = 0usize;
390 let mut max_block_size = 0usize;
391 while !rest.is_empty() {
392 let consumed = match frame::read_frame_header_with_format(rest, false) {
393 Ok((header, header_len)) => {
394 let block_size_max = frame_block_size_max(&header);
395 let walk = walk_blocks(rest, header_len as usize, block_size_max)?;
396 let checksum = if header.descriptor.content_checksum_flag() {
397 4
398 } else {
399 0
400 };
401 let end = walk
402 .end
403 .checked_add(checksum)
404 .filter(|end| *end <= rest.len())
405 .ok_or(FrameSizeError::Truncated)?;
406 // Header, block headers and checksum all lie inside
407 // `..end <= rest.len()`, so the margin never exceeds the input.
408 margin += header_len as usize + checksum + 3 * walk.count as usize;
409 max_block_size = max_block_size.max(block_size_max);
410 end
411 }
412 Err(errors::ReadFrameHeaderError::SkipFrame { .. }) => {
413 let end = find_frame_compressed_size(rest)?;
414 margin += end;
415 end
416 }
417 Err(e) => return Err(FrameSizeError::Header(e)),
418 };
419 rest = &rest[consumed..];
420 }
421 Ok(margin + max_block_size)
422}
423
424/// Smallest output buffer a block-by-block decode of a frame can run in:
425/// the window, plus room for a block written after it and split literals,
426/// capped by the content size when the frame declares one. Backs the C
427/// `ZSTD_decodingBufferSize_min`; `None` when the result does not fit `usize`.
428///
429/// The two 32-byte terms are the over-copy allowance the sequence executor's
430/// wide copies may write past a match (upstream `WILDCOPY_OVERLENGTH`,
431/// `zstd_decompress.c:1965-1986`).
432///
433/// ```rust
434/// use structured_zstd::decoding::min_decoding_buffer_size;
435/// // A 1 MiB window with no declared content size.
436/// assert_eq!(min_decoding_buffer_size(1 << 20, None), Some((1 << 20) + 2 * 131_072 + 64));
437/// // A 1 KiB window holds no block over 1 KiB, so both block terms shrink.
438/// assert_eq!(min_decoding_buffer_size(1 << 10, None), Some((1 << 10) + 2 * 1024 + 64));
439/// // A frame whose whole content is smaller than that.
440/// assert_eq!(min_decoding_buffer_size(1 << 20, Some(1000)), Some(1000));
441/// ```
442pub fn min_decoding_buffer_size(
443 window_size: u64,
444 frame_content_size: Option<u64>,
445) -> Option<usize> {
446 const WILDCOPY_OVERLENGTH: u64 = 32;
447 // Both block terms are the largest block the frame can hold, not a fixed
448 // 128 KiB, since no block exceeds the window: RFC 8878 3.1.1.2.4,
449 // Block_Maximum_Size = min(Window_Size, 128 KiB); upstream
450 // zstd_decompress.c:1972-1980 computes window + 2 * that block + 64.
451 let block_size = window_size.min(crate::common::MAX_BLOCK_SIZE as u64);
452 let needed = window_size.checked_add(2 * block_size + 2 * WILDCOPY_OVERLENGTH);
453 let size = match (frame_content_size, needed) {
454 (Some(content), Some(needed)) => content.min(needed),
455 // A window past `u64` still decodes in a buffer the content fits.
456 (Some(content), None) => content,
457 (None, Some(needed)) => needed,
458 (None, None) => return None,
459 };
460 usize::try_from(size).ok()
461}
462
463/// Frame header fields decoded by [`read_frame_header_info`], mirroring the
464/// values the C `ZSTD_getFrameHeader` fills into a `ZSTD_FrameHeader`.
465#[derive(Copy, Clone, Debug)]
466pub struct FrameHeaderInfo {
467 /// Declared decompressed size, or [`FrameContentSize::Unknown`] when the
468 /// header omits the `Frame_Content_Size` field.
469 pub content_size: FrameContentSize,
470 /// Decoder window size in bytes (the minimum buffer needed to decode the
471 /// frame). For single-segment frames this equals the content size.
472 pub window_size: u64,
473 /// Dictionary id required to decode the frame, if the header carries one.
474 pub dictionary_id: Option<u32>,
475 /// Whether a 32-bit content checksum trails the frame.
476 pub content_checksum: bool,
477 /// Header length in bytes, measured in the parsed input format: it includes
478 /// the 4-byte magic number in the default format, but excludes it when
479 /// parsed as magicless (`read_frame_header_info(.., true)`), since those 4
480 /// bytes are not present on the wire in that mode.
481 pub header_size: usize,
482}
483
484/// Length in bytes of the frame header at the start of `src`, including the
485/// 4-byte magic number (the offset at which the first block begins). Backs the
486/// C `ZSTD_frameHeaderSize`.
487///
488/// Only the magic number and the descriptor byte are needed: the length
489/// follows from the descriptor alone, so a caller holding just that five-byte
490/// prefix learns how much of the header is still to read. Upstream's
491/// `ZSTD_frameHeaderSize` measures it the same way
492/// (`zstd_decompress.c:416-429`), without looking at the descriptor's other
493/// bits, so a header this library refuses to decode is still measured.
494///
495/// # Errors
496/// [`ReadFrameHeaderError`](errors::ReadFrameHeaderError) when `src` is shorter
497/// than five bytes, has a bad magic number, or is a skippable frame.
498///
499/// ```rust
500/// use structured_zstd::decoding::frame_header_size;
501/// // Magic number and a descriptor announcing a window byte only.
502/// assert_eq!(frame_header_size(&[0x28, 0xB5, 0x2F, 0xFD, 0x00]).unwrap(), 6);
503/// ```
504pub fn frame_header_size(src: &[u8]) -> Result<usize, errors::ReadFrameHeaderError> {
505 frame::measure_header_from_slice(src)
506}
507
508/// [`frame_header_size`] for a frame in either format: with `magicless` the
509/// header starts at its descriptor byte and its length counts no magic
510/// number.
511///
512/// ```rust
513/// use structured_zstd::decoding::frame_header_size_in_format;
514/// // A descriptor announcing a window byte only.
515/// assert_eq!(frame_header_size_in_format(&[0x00], true).unwrap(), 2);
516/// assert_eq!(frame_header_size_in_format(&[0x28, 0xB5, 0x2F, 0xFD, 0x00], false).unwrap(), 6);
517/// ```
518///
519/// # Errors
520/// As [`frame_header_size`]; a magicless input fails only when it is empty.
521pub fn frame_header_size_in_format(
522 src: &[u8],
523 magicless: bool,
524) -> Result<usize, errors::ReadFrameHeaderError> {
525 frame::measure_header_with_format(src, magicless)
526}
527
528/// How many bytes from its start the frame header beginning `src` takes to
529/// read whole, judged from what `src` holds of it: the bytes that hold the
530/// descriptor until they are all here (five, or one without the magic
531/// number), then the header's own length, and eight for a skippable frame.
532/// A caller reading a header in pieces asks this before
533/// [`read_frame_header_info`]; upstream's `ZSTD_getFrameHeader_advanced`
534/// returns the same counts.
535///
536/// ```rust
537/// use structured_zstd::decoding::frame_header_input_needed;
538/// assert_eq!(frame_header_input_needed(&[0x28, 0xB5], false).unwrap(), 5);
539/// // Magic number and a descriptor announcing a window byte only.
540/// assert_eq!(frame_header_input_needed(&[0x28, 0xB5, 0x2F, 0xFD, 0x00], false).unwrap(), 6);
541/// assert!(frame_header_input_needed(&[0x00, 0x01], false).is_err());
542/// ```
543///
544/// # Errors
545/// [`ReadFrameHeaderError::BadMagicNumber`](errors::ReadFrameHeaderError::BadMagicNumber)
546/// when the bytes start no known magic number.
547pub fn frame_header_input_needed(
548 src: &[u8],
549 magicless: bool,
550) -> Result<usize, errors::ReadFrameHeaderError> {
551 frame::header_input_needed(src, magicless)
552}
553
554/// Decode the leading frame header fields of `src` without decoding the body.
555///
556/// Backs the C `ZSTD_getFrameHeader`. When `magicless` is `true` the 4-byte
557/// magic prefix is assumed absent (the `ZSTD_f_zstd1_magicless` format); the
558/// caller must know out-of-band that the stream is magicless. The reported
559/// [`FrameHeaderInfo::window_size`] is the raw value derived from the header
560/// (no maximum-window policy applied here; that bound is enforced at decode
561/// time), so callers see the frame's own declared window even when it exceeds
562/// a decoder limit.
563///
564/// # Errors
565/// As [`read_frame_content_size`].
566///
567/// ```rust
568/// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
569/// use structured_zstd::decoding::{read_frame_header_info, FrameContentSize};
570/// let frame = compress_slice_to_vec(&[7u8; 512], CompressionLevel::Default);
571/// let info = read_frame_header_info(&frame, false).unwrap();
572/// assert_eq!(info.content_size, FrameContentSize::Known(512));
573/// assert!(info.window_size >= 512);
574/// ```
575pub fn read_frame_header_info(
576 src: &[u8],
577 magicless: bool,
578) -> Result<FrameHeaderInfo, errors::ReadFrameHeaderError> {
579 let (header, consumed) = frame::read_frame_header_with_format(src, magicless)?;
580 let content_size = if header.fcs_declared() {
581 FrameContentSize::Known(header.frame_content_size())
582 } else {
583 FrameContentSize::Unknown
584 };
585 // Compute the window size without the decode-time maximum-window check
586 // (RFC 8878 §3.1.1.1.2). `window_descriptor()` returns `None` for a
587 // single-segment frame, where the window equals the content size.
588 let window_size = match header.window_descriptor() {
589 Some(desc) => {
590 let exponent = u64::from(desc >> 3);
591 let mantissa = u64::from(desc & 0x7);
592 let window_base = 1u64 << (10 + exponent);
593 window_base + (window_base / 8) * mantissa
594 }
595 None => header.frame_content_size(),
596 };
597 Ok(FrameHeaderInfo {
598 content_size,
599 window_size,
600 dictionary_id: header.dictionary_id(),
601 content_checksum: header.descriptor.content_checksum_flag(),
602 header_size: consumed as usize,
603 })
604}
605
606pub(crate) mod block_decoder;
607pub(crate) mod buffer_backend;
608pub(crate) mod decode_buffer;
609pub(crate) mod dictionary;
610pub(crate) mod exec_sequence_inline;
611// FlatBuf is the compile-time-monomorphised "frame fits in window"
612// backend selected via `DecodeBuffer<FlatBuf>`. `FrameDecoder`'s
613// `DecoderScratchKind` picks it when the frame header has
614// `Single_Segment_flag` set; the ring backend remains the default
615// for multi-segment frames. See backlog item #132 for the wiring
616// rationale.
617pub(crate) mod flat_buf;
618pub(crate) mod frame;
619pub(crate) mod literals_section_decoder;
620pub(crate) mod prefetch;
621mod ringbuffer;
622#[allow(dead_code)]
623pub(crate) mod scratch;
624// Per-kernel monolithic sequence-section decoder entry points. Each
625// kernel has its own self-contained function with the full pipeline
626// (outer init, both arms, decode_one, execute_one) inlined inside one
627// `#[target_feature]`-scoped body. The dispatcher in
628// `sequence_section_decoder::decode_and_execute_sequences` selects the
629// kernel ONCE per call via cached `detect_cpu_kernel`. aarch64 Neon
630// and Sve still go through the K-generic
631// `decode_and_execute_sequences_impl` shared body until their own
632// monoliths land.
633//
634// The shared helpers (`decode_and_execute_sequences_impl`,
635// `run_pipelined_sequence_loop`, `decode_one_sequence_inline`, the
636// `execute_one_sequence_pipelined*` wrappers) live on aarch64
637// (Neon/Sve dispatch arms in `decode_and_execute_sequences`) and in
638// tests, but are orphan on x86_64 production builds where the
639// per-kernel monoliths bypass them entirely. Each carries
640// `#[allow(dead_code)]` so the `-D warnings` clippy gate stays green
641// on x86_64 without losing the cross-arch reuse. The vestigial
642// `_bmi2`/`_avx2`/`_vbmi2` variants are pre-R12 macro-dispatch
643// helpers with no remaining callers; they should be cleaned up in
644// a follow-up PR once the per-kernel monolithic shape is fully
645// settled.
646#[cfg(all(target_arch = "x86_64", feature = "kernel-avx2"))]
647pub(crate) mod seq_decoder_avx2;
648#[cfg(all(target_arch = "x86_64", feature = "kernel-bmi2"))]
649pub(crate) mod seq_decoder_bmi2;
650pub(crate) mod seq_decoder_scalar;
651#[cfg(all(target_arch = "x86_64", feature = "kernel-vbmi2"))]
652pub(crate) mod seq_decoder_vbmi2;
653pub(crate) mod sequence_execution;
654pub(crate) mod sequence_section_decoder;
655pub(crate) mod simd_copy;
656/// Diagnostic-only re-export of the copy-shape histogram counters. Public
657/// only when the `copy-shape-stats` feature is on (off in shipping builds).
658#[cfg(feature = "copy-shape-stats")]
659pub use simd_copy::shape_stats;
660// `UserSliceBackend` is the compile-time-monomorphised backend that
661// writes directly into the caller's `&mut [u8]` output slice, used
662// by the `FrameDecoder::decode_all` direct-decode path. It
663// eliminates the `FlatBuf` drain copy + anonymous-page-fault cost
664// on large literal sections. Wiring happens via
665// `DecodeBuffer<UserSliceBackend<'a>>`; the lifetime binds the
666// backend to the caller's slice for the call duration.
667pub(crate) mod user_slice_buf;
668
669#[cfg(feature = "bench-internals")]
670pub(crate) use self::simd_copy::copy_bytes_overshooting_for_bench;
671
672#[cfg(test)]
673mod block_api_tests;
674#[cfg(test)]
675mod frame_inspection_tests;