structured_zstd/decoding/frame_decoder.rs
1//! Framedecoder is the main low-level struct users interact with to decode zstd frames
2//!
3//! Zstandard compressed data is made of one or more frames. Each frame is independent and can be
4//! decompressed independently of other frames. This module contains structures
5//! and utilities that can be used to decode a frame.
6
7use super::frame;
8use crate::decoding;
9use crate::decoding::block_decoder::BlockDecoder;
10use crate::decoding::buffer_backend::BufferBackend;
11use crate::decoding::dictionary::{Dictionary, DictionaryHandle};
12use crate::decoding::errors::{DecodeBlockContentError, FrameDecoderError};
13use crate::decoding::flat_buf::FlatBuf;
14use crate::decoding::ringbuffer::RingBuffer;
15use crate::decoding::scratch::DecoderScratch;
16use crate::io::{Error, Read, Write};
17use alloc::collections::BTreeMap;
18use alloc::vec::Vec;
19use core::convert::TryInto;
20
21use crate::common::MAXIMUM_ALLOWED_WINDOW_SIZE;
22
23/// Build the block-header decode error. With the `lsm` feature it captures
24/// the failing block's index and frame offset (block-precise recovery);
25/// without it, the legacy positionless variant — so the default build's
26/// error surface stays byte-identical to the upstream zstd.
27#[cfg(feature = "lsm")]
28fn block_header_decode_error(
29 source: crate::decoding::errors::BlockHeaderReadError,
30 block_index: u32,
31 frame_offset: u32,
32) -> FrameDecoderError {
33 FrameDecoderError::FailedToReadBlockHeaderAt {
34 source,
35 block_index,
36 frame_offset,
37 }
38}
39#[cfg(not(feature = "lsm"))]
40fn block_header_decode_error(
41 source: crate::decoding::errors::BlockHeaderReadError,
42 _block_index: u32,
43 _frame_offset: u32,
44) -> FrameDecoderError {
45 FrameDecoderError::FailedToReadBlockHeader(source)
46}
47
48/// Build the block-body decode error. With `lsm` it captures the block
49/// index, frame offset, and the failing block's structural metadata
50/// (reconstructed from its header); without it, the legacy variant.
51#[cfg(feature = "lsm")]
52fn block_body_decode_error(
53 source: DecodeBlockContentError,
54 block_index: u32,
55 frame_offset: u32,
56 header: &crate::blocks::block::BlockHeader,
57 header_size: u8,
58) -> FrameDecoderError {
59 use crate::blocks::block::BlockType;
60 // Physical wire body vs the raw `Block_Size` field: RLE writes a single
61 // body byte while `Block_Size` carries the repeat count; Raw/Compressed
62 // bodies match the field.
63 let (body_size, block_size_field) = match header.block_type {
64 BlockType::RLE => (1u32, header.decompressed_size),
65 _ => (header.content_size, header.content_size),
66 };
67 FrameDecoderError::FailedToReadBlockBodyAt {
68 source,
69 block_index,
70 frame_offset,
71 block: crate::encoding::frame_emit_info::FrameBlock {
72 offset_in_frame: frame_offset,
73 header_size,
74 body_size,
75 block_size_field,
76 block_type: header.block_type,
77 last_block: header.last_block,
78 // Raw/RLE carry their regenerated size in the header;
79 // a Compressed block's is unknown until decoded, so
80 // `read_block_header` leaves `decompressed_size` 0 here.
81 decompressed_size: header.decompressed_size,
82 },
83 }
84}
85#[cfg(not(feature = "lsm"))]
86fn block_body_decode_error(
87 source: DecodeBlockContentError,
88 _block_index: u32,
89 _frame_offset: u32,
90 _header: &crate::blocks::block::BlockHeader,
91 _header_size: u8,
92) -> FrameDecoderError {
93 FrameDecoderError::FailedToReadBlockBody(source)
94}
95
96/// Low level Zstandard decoder that can be used to decompress frames with fine control over when and how many bytes are decoded.
97///
98/// This decoder is able to decode frames only partially and gives control
99/// over how many bytes/blocks will be decoded at a time (so you don't have to decode a 10GB file into memory all at once).
100/// It reads bytes as needed from a provided source and can be read from to collect partial results.
101///
102/// If you want to just read the whole frame with an `io::Read` without having to deal with manually calling [FrameDecoder::decode_blocks]
103/// you can use the provided [crate::decoding::StreamingDecoder] wich wraps this FrameDecoder.
104///
105/// Workflow is as follows:
106/// ```
107/// use structured_zstd::decoding::BlockDecodingStrategy;
108///
109/// # #[cfg(feature = "std")]
110/// use std::io::{Read, Write};
111///
112/// // no_std environments can use the crate's own Read traits
113/// # #[cfg(not(feature = "std"))]
114/// use structured_zstd::io::{Read, Write};
115///
116/// fn decode_this(mut file: impl Read) {
117/// //Create a new decoder
118/// let mut frame_dec = structured_zstd::decoding::FrameDecoder::new();
119/// let mut result = Vec::new();
120///
121/// // Use reset or init to make the decoder ready to decode the frame from the io::Read
122/// frame_dec.reset(&mut file).unwrap();
123///
124/// // Loop until the frame has been decoded completely
125/// while !frame_dec.is_finished() {
126/// // decode (roughly) batch_size many bytes
127/// frame_dec.decode_blocks(&mut file, BlockDecodingStrategy::UptoBytes(1024)).unwrap();
128///
129/// // read from the decoder to collect bytes from the internal buffer
130/// let bytes_read = frame_dec.read(result.as_mut_slice()).unwrap();
131///
132/// // then do something with it
133/// do_something(&result[0..bytes_read]);
134/// }
135///
136/// // handle the last chunk of data
137/// while frame_dec.can_collect() > 0 {
138/// let x = frame_dec.read(result.as_mut_slice()).unwrap();
139///
140/// do_something(&result[0..x]);
141/// }
142/// }
143///
144/// fn do_something(data: &[u8]) {
145/// # #[cfg(feature = "std")]
146/// std::io::stdout().write_all(data).unwrap();
147/// }
148/// ```
149pub struct FrameDecoder {
150 state: Option<FrameDecoderState>,
151 /// Test-only observability: frames decoded via `run_direct_decode`.
152 /// The direct and buffered paths are byte-identical, so dispatch
153 /// regressions (e.g. re-excluding dictionary frames from the direct
154 /// gate) are invisible to output assertions; tests pin the path here.
155 #[cfg(test)]
156 direct_frames: u64,
157 // Registered dictionaries are stored by shared handle (Arc/Rc) so a
158 // single content copy is referenced by every frame the decoder decodes
159 // (upstream zstd `ZSTD_refDDict`), rather than re-copied into the decode buffer
160 // per frame. `add_dict` wraps an owned `Dictionary` into a handle.
161 owned_dicts: BTreeMap<u32, DictionaryHandle>,
162 #[cfg(target_has_atomic = "ptr")]
163 shared_dicts: BTreeMap<u32, DictionaryHandle>,
164 #[cfg(not(target_has_atomic = "ptr"))]
165 shared_dicts: (),
166 /// `ZSTD_f_zstd1_magicless` — when true, [`Self::init`] / [`Self::reset`]
167 /// expect frames without the 4-byte magic number prefix.
168 /// Default false (standard zstd format).
169 magicless: bool,
170 /// Largest window a frame may declare on the paths where the decoder
171 /// holds the window itself. Default [`MAXIMUM_ALLOWED_WINDOW_SIZE`]; set
172 /// via [`Self::set_max_window_size`].
173 max_window_size: u64,
174 /// The most any block may hold; see [`Self::set_max_block_size`].
175 max_block_size: usize,
176 /// How the optional content checksum is handled. Default
177 /// [`ContentChecksum::EmitOnly`] (compute + expose, no error on
178 /// mismatch). Set via [`Self::set_content_checksum`].
179 content_checksum: ContentChecksum,
180 /// Pinned `Dictionary_ID` expectation set via
181 /// [`Self::expect_dict_id`]. `None` (default) disables the
182 /// check; `Some(0)` matches frames whose header omits the
183 /// optional dict_id (treated as "no dictionary"). Validated in
184 /// [`Self::reset`] AFTER the frame header parses successfully
185 /// and BEFORE any block decode work.
186 #[cfg(feature = "lsm")]
187 expect_dict_id: Option<u32>,
188 /// Pinned `Window_Descriptor` byte expectation set via
189 /// [`Self::expect_window_descriptor`]. `None` (default)
190 /// disables the check. Validated in [`Self::reset`] AFTER the
191 /// frame header parses successfully and BEFORE any block
192 /// decode work. Single-segment frames (which omit the
193 /// `Window_Descriptor` byte from the wire) surface as
194 /// [`crate::decoding::errors::FrameDecoderError::UnexpectedWindowDescriptor`]
195 /// with `found: None`.
196 #[cfg(feature = "lsm")]
197 expect_window_descriptor: Option<u8>,
198 /// When `true`, the per-block decode loop XXH64-hashes each
199 /// block's decompressed bytes and stores the low-32-bit digest in
200 /// [`Self::computed_block_checksums`]. Default `false` (zero
201 /// cost). Set via [`Self::enable_per_block_checksums`]. Gated on
202 /// `all(lsm, hash)` because XXH64 lives behind the `hash`
203 /// feature.
204 #[cfg(all(feature = "lsm", feature = "hash"))]
205 per_block_checksums_enabled: bool,
206 /// Per-block XXH64 (low 32 bits) digests captured during the
207 /// current frame's decode when `per_block_checksums_enabled` is
208 /// set. Reset at the start of every new frame. Gated on
209 /// `all(lsm, hash)` (see `per_block_checksums_enabled`).
210 #[cfg(all(feature = "lsm", feature = "hash"))]
211 computed_block_checksums: alloc::vec::Vec<u32>,
212 /// Best kernel this CPU offers, resolved once per decoder rather than per
213 /// block decoder built. A chunked decode enters `decode_from_to` once per
214 /// caller-sized target, so detecting there put an atomic read and a branch
215 /// on every call; feature detection belongs before the work, not inside it.
216 kernel: crate::cpu_kernel::CpuKernelTag,
217 /// A dictionary [`Self::arm_dict`] installed before the decoder had a
218 /// frame state to hold it, taken by the first
219 /// [`Self::reset_with_active_dict`]. Only ever `Some` while `state` is
220 /// `None`: once a state exists the dictionary lives there.
221 armed_dict: Option<DictionaryHandle>,
222 /// The v0.5-v0.7 frame a `Read`-driven reset started, decoded step by
223 /// step in place of `state`. Boxed so a decoder that never meets one
224 /// carries a pointer, not the stream.
225 #[cfg(feature = "legacy")]
226 legacy: Option<alloc::boxed::Box<decoding::legacy::LegacyStream>>,
227}
228
229/// How the decoder treats a frame's optional XXH64 content checksum
230/// (RFC 8878 Content_Checksum_flag). The XXH64 pass over the decompressed
231/// output is a measurable share of decode time, so it is made skippable.
232///
233/// ```
234/// use structured_zstd::decoding::{ContentChecksum, FrameDecoder};
235/// let mut decoder = FrameDecoder::new();
236/// decoder.set_content_checksum(ContentChecksum::Verify);
237/// ```
238#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
239pub enum ContentChecksum {
240 /// Skip the XXH64 pass entirely: no compute, no verify.
241 /// `get_calculated_checksum()` returns `None`.
242 None,
243 /// Compute the checksum and expose it via the accessors, but do not
244 /// error on a mismatch. This is the default and matches the historical
245 /// behaviour (callers verify manually if they wish).
246 #[default]
247 EmitOnly,
248 /// Compute the checksum and compare it against the frame's stored value;
249 /// a disagreement fails the decode with
250 /// [`FrameDecoderError::ChecksumMismatch`].
251 /// Without the `hash` feature there is no way to compute a digest, so
252 /// `Verify` cannot detect a mismatch and behaves like `None`.
253 Verify,
254}
255
256/// Decode-relevant identity of a frame, used to reject a [`ResumeState`]
257/// captured from one frame being applied to a frame of a different shape. Covers
258/// every header field that changes how blocks decode (buffer sizing, backend
259/// kind, entropy/dictionary context, trailing-checksum handling, declared
260/// content size, magicless framing).
261///
262/// This is a SHAPE guard, not a content-unique fingerprint: two distinct frames
263/// that happen to share all these header fields produce the same key (no cheap
264/// header field uniquely identifies frame content). It catches the realistic
265/// accidental misuse — applying a snapshot to a frame with a different
266/// window/dictionary/size — with a typed error instead of byte-wrong output.
267/// Pairing a `ResumeState` with the correct frame's compressed source and
268/// `window_prime` remains the caller's contract.
269#[cfg(feature = "lsm")]
270#[derive(Clone, Copy, PartialEq, Eq, Debug)]
271struct FrameKey {
272 window_size: u64,
273 frame_content_size: u64,
274 /// `Dictionary_ID` declared in the frame header (`None` when omitted).
275 dictionary_id: Option<u32>,
276 /// Dictionary actually applied to the decoder (`state.using_dict`). This is
277 /// distinct from `dictionary_id`: a frame with a dictless header can still
278 /// be decoded with an explicit dictionary via `reset_with_dict_handle` /
279 /// `force_dict`, and two such decodes with different dictionaries must NOT
280 /// compare equal — keying only on the header field would miss that.
281 active_dictionary_id: Option<u32>,
282 single_segment: bool,
283 content_checksum: bool,
284 magicless: bool,
285}
286
287#[cfg(feature = "lsm")]
288impl FrameKey {
289 fn from_state(state: &FrameDecoderState, magicless: bool) -> FrameKey {
290 let header = &state.frame_header;
291 FrameKey {
292 window_size: header.window_size().unwrap_or(0),
293 frame_content_size: header.frame_content_size(),
294 dictionary_id: header.dictionary_id(),
295 active_dictionary_id: state.using_dict,
296 single_segment: header.descriptor.single_segment_flag(),
297 content_checksum: header.descriptor.content_checksum_flag(),
298 magicless,
299 }
300 }
301}
302
303/// XXH64 of a contiguous byte slice — the resume-side counterpart to
304/// [`DecoderScratchKind::window_tail_hash`]. Streaming XXH64 is chunk-boundary
305/// independent, so this single-slice hash equals the emit-side two-slice hash
306/// over the same bytes.
307#[cfg(all(feature = "lsm", feature = "hash"))]
308fn xxh64_of(bytes: &[u8]) -> u64 {
309 use core::hash::Hasher;
310 let mut h = twox_hash::XxHash64::with_seed(0);
311 h.write(bytes);
312 h.finish()
313}
314
315/// Cross-block decode state needed to resume a cold partial decode at an inner
316/// block boundary, emitted by [`FrameDecoder::decode_blocks_partial`] when its
317/// `emit_resume` argument is `true` (returned in
318/// [`PartialDecode::resume_state`]) and fed back via that same method's
319/// [`resume`](FrameDecoder::decode_blocks_partial) argument
320/// ([`ResumeInput`]).
321///
322/// A zstd block does not carry all the state required to decode it in
323/// isolation: besides the shared match window (the decompressed output history),
324/// a Compressed block may reuse the previous block's entropy tables via
325/// `Repeat_Mode` (literals Huffman + the LL/OF/ML FSE distributions) and always
326/// continues the running repeat-offset history. This snapshot carries exactly
327/// that carry-over state plus the resume coordinates, so resuming is
328/// byte-identical to a contiguous decode even across a dropped decoder. The
329/// window itself is NOT stored here — the caller supplies it back through
330/// [`ResumeInput::window_prime`] from the decompressed output it already
331/// persists. Neither is the dictionary: for a dictionary frame the caller
332/// re-attaches it to the resuming decoder via [`FrameDecoder::reset`] /
333/// [`FrameDecoder::reset_with_dict_handle`] (it already holds the dictionary
334/// from encode time), and the snapshot records only the dictionary's identity
335/// so a resume under a different dictionary is rejected.
336///
337/// Behind the `lsm` Cargo feature.
338#[cfg(feature = "lsm")]
339#[cfg_attr(docsrs, doc(cfg(feature = "lsm")))]
340pub struct ResumeState {
341 /// Identity of the frame this state was captured from. Compared against the
342 /// frame currently reset into the decoder before any state is restored, so a
343 /// snapshot from a different frame shape is rejected with
344 /// [`FrameDecoderError::ResumeFrameMismatch`] instead of silently producing
345 /// byte-wrong output.
346 frame_key: FrameKey,
347 /// Index of the block to resume AT (the first block NOT yet decoded).
348 block_index: u32,
349 /// Cumulative decompressed byte count produced before `block_index`.
350 output_offset: u64,
351 /// FSE tables (LL/OF/ML) as of the last decoded block — the source for a
352 /// `Repeat_Mode` resume block.
353 fse: crate::decoding::scratch::FSEScratch,
354 /// Huffman literals table as of the last decoded block — the source for a
355 /// treeless (repeat) literals resume block.
356 huf: crate::decoding::scratch::HuffmanScratch,
357 /// Running repeat-offset history (`offset_hist`) as of the last decoded
358 /// block.
359 offset_hist: [u32; 3],
360 /// XXH64 of the exact window-prime bytes (the last `min(window_size,
361 /// output_offset)` decompressed bytes) captured at emit. Verified at resume
362 /// against the caller-supplied [`ResumeInput::window_prime`]: a content
363 /// mismatch (wrong frame, wrong or corrupted prime) is a near-unique
364 /// (≈2⁻⁶⁴) signal and is rejected with
365 /// [`FrameDecoderError::ResumeFrameMismatch`]. This is the content-exact
366 /// guard; [`FrameKey`] is the cheap shape pre-check that works without the
367 /// `hash` feature. Behind `all(lsm, hash)`.
368 #[cfg(feature = "hash")]
369 window_hash: u64,
370}
371
372#[cfg(feature = "lsm")]
373impl ResumeState {
374 /// Inner block index this state resumes at (the first block not yet
375 /// decoded). Pass it as the `end_block` lower bound (and as `start_block`)
376 /// of the resuming
377 /// [`decode_blocks_partial`](FrameDecoder::decode_blocks_partial) call.
378 pub fn block_index(&self) -> u32 {
379 self.block_index
380 }
381
382 /// Cumulative decompressed byte count produced before
383 /// [`block_index`](Self::block_index) — i.e. the decompressed offset at
384 /// which the resumed output begins. Equals
385 /// `FrameEmitInfo::decompressed_byte_range(block_index).start`. Use it to
386 /// slice the `window_prime` tail the resumed call needs.
387 pub fn output_offset(&self) -> u64 {
388 self.output_offset
389 }
390}
391
392// Manual Debug: the entropy tables are large internal scratch with no useful
393// Debug surface; only the resume coordinates are worth printing (and this lets
394// `PartialDecode` keep its derived Debug).
395#[cfg(feature = "lsm")]
396impl core::fmt::Debug for ResumeState {
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 f.debug_struct("ResumeState")
399 .field("block_index", &self.block_index)
400 .field("output_offset", &self.output_offset)
401 .finish_non_exhaustive()
402 }
403}
404
405/// Resume input fed to [`FrameDecoder::decode_blocks_partial`]'s `resume`
406/// argument to continue a cold partial decode without re-decompressing the
407/// preceding blocks.
408///
409/// Behind the `lsm` Cargo feature.
410#[cfg(feature = "lsm")]
411#[cfg_attr(docsrs, doc(cfg(feature = "lsm")))]
412pub struct ResumeInput<'a> {
413 /// The caller's already-decompressed output ending just before
414 /// [`ResumeState::block_index`]. Must contain at least the last
415 /// `min(window_size, output_offset)` bytes (a full match window, or the
416 /// whole prefix when it is shorter than one window); anything beyond the
417 /// last `window_size` bytes is ignored, so passing the entire prefix is
418 /// also valid (capped internally, bounding resume memory to one window).
419 pub window_prime: &'a [u8],
420 /// Cross-block entropy/repcode state emitted by the prior
421 /// [`decode_blocks_partial`](FrameDecoder::decode_blocks_partial) call.
422 pub state: &'a ResumeState,
423}
424
425/// Backend-tagged decode scratch — chosen at frame-reset time based
426/// on the parsed `FrameHeader.descriptor.single_segment_flag()` and
427/// kept stable through the lifetime of the frame. The match in each
428/// helper below dispatches **once per call** (e.g. once per block in
429/// `decode_block_content`, once per drain in `drain_to_writer`) —
430/// never inside the hot push/repeat loop, which is fully
431/// monomorphised through the `DecoderScratch<B>` generic.
432enum DecoderScratchKind {
433 Ring(DecoderScratch<RingBuffer>),
434 Flat(DecoderScratch<FlatBuf>),
435}
436
437impl DecoderScratchKind {
438 fn new_ring(window_size: usize) -> Self {
439 // Lazy ring-buffer allocation: do NOT `reserve(window_size)` here.
440 // The direct-decode path (`run_direct_decode`) writes through
441 // `UserSliceBackend` and never touches the ring; allocating it
442 // eagerly wastes one full window of peak memory on the common
443 // direct-eligible frame. On the non-direct path the window is
444 // pre-reserved once at frame entry (`decode_all_impl` and
445 // `decode_blocks` both call `DecoderScratchKind::reserve_buffer`
446 // before any block writes), so multi-block frames pay one
447 // amortised grow instead of repeated `reserve_amortized` steps
448 // per block. Issue #279 round 2.
449 let s = DecoderScratch::<RingBuffer>::new(window_size);
450 Self::Ring(s)
451 }
452
453 /// Construct a flat-backed scratch for a single-segment frame.
454 /// `frame_content_size` is the upcoming output size in bytes
455 /// (== `window_size` when the flag is set).
456 ///
457 /// Lazy buffer allocation (mirrors [`Self::new_ring`]): do NOT
458 /// pre-size the `FlatBuf`. The direct-decode path
459 /// (`run_direct_decode`) writes through `UserSliceBackend` and never
460 /// touches this buffer, so eagerly allocating a full FCS wastes one
461 /// whole content-size of peak memory on the common direct-eligible
462 /// single-segment frame. The non-direct fallback reserves it once via
463 /// `reserve_buffer(window_size)` at frame entry before any block
464 /// write (`FlatBuf::reserve` adds the `WILDCOPY_OVERLENGTH` slack),
465 /// and every inline-exec site (trait method and per-kernel macros)
466 /// now carries a tight-tail bounded copy, so a tight buffer can never
467 /// overshoot regardless of construction-time slack.
468 fn new_flat(frame_content_size: usize) -> Self {
469 let s = DecoderScratch::<FlatBuf>::new(frame_content_size);
470 Self::Flat(s)
471 }
472
473 /// Reset (or transition between) backends for a new frame.
474 /// Reuses the existing `DecoderScratch` allocations (FSE / HUF
475 /// tables, sequence vec, etc.) when the backend kind is unchanged
476 /// — only the underlying buffer is re-sized for the new frame.
477 /// Building a fresh `DecoderScratch` on every frame would
478 /// re-allocate everything and was measured at +255 % vs ring on
479 /// small frames; reusing it keeps the small-frame cost flat.
480 fn reset(&mut self, frame: &frame::FrameHeader, window_size: usize) {
481 if frame.descriptor.single_segment_flag() {
482 match self {
483 Self::Flat(s) => {
484 s.reset(window_size);
485 // `DecoderScratch::reset` clears the backing buffer and
486 // updates `window_size` WITHOUT reserving it (it may still
487 // resize the per-block scratch Vecs up to
488 // `min(window_size, MAX_BLOCK_SIZE)`). Backing-buffer
489 // capacity is decided one layer up: direct-eligible frames
490 // never touch it, and the non-direct path pre-reserves once
491 // via `reserve_buffer(window_size)` at frame entry.
492 }
493 Self::Ring(_) => *self = Self::new_flat(window_size),
494 }
495 } else {
496 match self {
497 Self::Ring(s) => s.reset(window_size),
498 Self::Flat(_) => *self = Self::new_ring(window_size),
499 }
500 }
501 }
502
503 fn init_from_dict(&mut self, dict: &DictionaryHandle) {
504 match self {
505 Self::Ring(s) => s.init_from_dict(dict),
506 Self::Flat(s) => s.init_from_dict(dict),
507 }
508 }
509
510 #[inline]
511 fn buffer_len(&self) -> usize {
512 match self {
513 Self::Ring(s) => s.buffer.len(),
514 Self::Flat(s) => s.buffer.len(),
515 }
516 }
517
518 fn buffer_deliver_newest(&mut self, n: usize, target: &mut [u8]) {
519 match self {
520 Self::Ring(s) => s.buffer.deliver_newest(n, target),
521 Self::Flat(s) => s.buffer.deliver_newest(n, target),
522 }
523 }
524
525 /// Take `content` into the history as output the caller already holds:
526 /// appended, never handed out, and let go of once past the window.
527 ///
528 /// Taken a piece at a time, no piece longer than a block or the window,
529 /// and trimmed after each: a block far past the window then never makes
530 /// the history hold more than the window and one piece. Upstream keeps a
531 /// pointer to the caller's bytes instead (`ZSTD_insertBlock`), which a
532 /// decoder owning its history cannot.
533 fn buffer_insert(&mut self, content: &[u8], kernel: crate::cpu_kernel::CpuKernelTag) {
534 let window = match self {
535 Self::Ring(s) => s.buffer.window_size,
536 Self::Flat(s) => s.buffer.window_size,
537 };
538 let piece = window.clamp(1, crate::common::MAX_BLOCK_SIZE as usize);
539 for chunk in content.chunks(piece) {
540 self.buffer_push(chunk, kernel);
541 match self {
542 Self::Ring(s) => {
543 s.buffer.mark_delivered();
544 s.buffer.drop_to_window_size()
545 }
546 Self::Flat(s) => {
547 s.buffer.mark_delivered();
548 s.buffer.drop_to_window_size()
549 }
550 };
551 }
552 }
553
554 fn workspace_bytes(&self) -> usize {
555 match self {
556 Self::Ring(s) => s.workspace_bytes(),
557 Self::Flat(s) => s.workspace_bytes(),
558 }
559 }
560
561 /// Pre-reserve the backing buffer to `window_size` in a single
562 /// allocation. Called once on the non-direct (`decode_blocks`) path
563 /// after direct-eligibility is ruled out, so multi-segment fallback
564 /// decodes don't pay repeated `reserve_amortized` grow steps
565 /// (128 KiB → 256 KiB → ... → window) as blocks accumulate.
566 ///
567 /// Direct-eligible frames never call this and pay zero backing-buffer
568 /// allocation for the window, on BOTH backends: `new_ring` and
569 /// `new_flat` are each lazy (no pre-reserve), so a direct-eligible
570 /// frame writes only through `UserSliceBackend` and leaves this
571 /// buffer empty.
572 ///
573 /// `target` is the TARGET buffer capacity (`decoding_buffer_size`) and
574 /// `growth_limit` the most later growth may reach
575 /// (`decoding_buffer_limit`); the method itself computes the shortfall
576 /// past the bytes already buffered before calling the backend's
577 /// ADDITIONAL-semantics `reserve_exact`. That keeps re-entries (the
578 /// decode_all fallback loop runs `decode_blocks` once per strategy
579 /// chunk, and streaming callers invoke it per call) from growing a
580 /// window-full buffer toward 2x window, while per-block growth keeps
581 /// the amortized `reserve`.
582 /// Hand the buffer what the frame declared it would produce, so the
583 /// per-block reservation can stop at the frame's remainder.
584 #[inline]
585 fn set_declared_content(&mut self, content_size: Option<u64>) {
586 match self {
587 Self::Ring(s) => s.buffer.set_declared_content(content_size),
588 Self::Flat(s) => s.buffer.set_declared_content(content_size),
589 }
590 }
591
592 #[inline]
593 fn reserve_buffer(&mut self, target: usize, growth_limit: usize) {
594 let window_size = target;
595 // Exact growth: this is the one-shot pre-reservation, and a request
596 // landing one slack past the retained capacity (e.g. a dictionary
597 // prefix already loaded into the buffer) must not DOUBLE a
598 // window-sized allocation through the amortized policy. Per-block
599 // growth keeps the amortized `reserve`.
600 //
601 // `reserve_exact` takes ADDITIONAL capacity, so request only the
602 // shortfall past the bytes already buffered: the decode_all
603 // fallback loop re-enters `decode_blocks` once per strategy chunk,
604 // and re-requesting the full window each iteration would grow a
605 // window-sized buffer toward 2x window.
606 match self {
607 Self::Ring(s) => {
608 // The ring's growth rounds to the next power of two, capped at
609 // its limit; with the limit at the target for this one-shot
610 // reservation it lands exactly there, and later growth (a
611 // compressed block's own reservation) may then reach the
612 // frame's real limit, never double past it.
613 s.buffer.set_growth_limit(window_size);
614 let additional = window_size.saturating_sub(s.buffer.len());
615 s.buffer.reserve_exact(additional);
616 s.buffer.set_growth_limit(growth_limit);
617 }
618 Self::Flat(s) => {
619 let additional = window_size.saturating_sub(s.buffer.len());
620 s.buffer.reserve_exact(additional);
621 }
622 }
623 }
624
625 /// Last `n` bytes of the visible buffer as `(s1, s2)` (wrap-aware).
626 /// Routes through whichever backend the current scratch holds.
627 #[cfg(all(feature = "lsm", feature = "hash"))]
628 fn last_n_as_slices(&self, n: usize) -> (&[u8], &[u8]) {
629 match self {
630 Self::Ring(s) => s.buffer.last_n_as_slices(n),
631 Self::Flat(s) => s.buffer.last_n_as_slices(n),
632 }
633 }
634
635 fn buffer_drain(&mut self) -> Vec<u8> {
636 match self {
637 Self::Ring(s) => s.buffer.drain(),
638 Self::Flat(s) => s.buffer.drain(),
639 }
640 }
641
642 fn buffer_drain_to_window_size(&mut self) -> Option<Vec<u8>> {
643 match self {
644 Self::Ring(s) => s.buffer.drain_to_window_size(),
645 Self::Flat(s) => s.buffer.drain_to_window_size(),
646 }
647 }
648
649 fn buffer_drain_to_writer(&mut self, sink: impl Write) -> Result<usize, Error> {
650 match self {
651 Self::Ring(s) => s.buffer.drain_to_writer(sink),
652 Self::Flat(s) => s.buffer.drain_to_writer(sink),
653 }
654 }
655
656 fn buffer_drain_to_window_size_writer(&mut self, sink: impl Write) -> Result<usize, Error> {
657 match self {
658 Self::Ring(s) => s.buffer.drain_to_window_size_writer(sink),
659 Self::Flat(s) => s.buffer.drain_to_window_size_writer(sink),
660 }
661 }
662
663 fn buffer_undelivered(&self) -> usize {
664 match self {
665 Self::Ring(s) => s.buffer.undelivered(),
666 Self::Flat(s) => s.buffer.undelivered(),
667 }
668 }
669
670 fn buffer_read(&mut self, target: &mut [u8]) -> Result<usize, Error> {
671 match self {
672 Self::Ring(s) => s.buffer.read(target),
673 Self::Flat(s) => s.buffer.read(target),
674 }
675 }
676
677 /// [`Self::buffer_read`], also reporting the decoded bytes `target` had no
678 /// room for.
679 fn buffer_read_reporting_pending(
680 &mut self,
681 target: &mut [u8],
682 ) -> Result<(usize, usize), Error> {
683 match self {
684 Self::Ring(s) => s.buffer.read_reporting_pending(target),
685 Self::Flat(s) => s.buffer.read_reporting_pending(target),
686 }
687 }
688
689 fn buffer_read_all(&mut self, target: &mut [u8]) -> Result<usize, Error> {
690 match self {
691 Self::Ring(s) => s.buffer.read_all(target),
692 Self::Flat(s) => s.buffer.read_all(target),
693 }
694 }
695
696 /// Drop visible output beyond `window_size` without producing it,
697 /// keeping the most recent `window_size` bytes available to back
698 /// future match copies. Used by `decode_blocks_partial` to bound
699 /// memory while decoding the leading (skipped) blocks into the window.
700 #[cfg(feature = "lsm")]
701 fn buffer_drop_to_window_size(&mut self) -> usize {
702 match self {
703 Self::Ring(s) => s.buffer.drop_to_window_size(),
704 Self::Flat(s) => s.buffer.drop_to_window_size(),
705 }
706 }
707
708 /// Drop exactly `n` bytes from the front of the visible output without
709 /// producing them. Used by `decode_blocks_partial` to discard the
710 /// leading blocks' window-context bytes once the in-range blocks are
711 /// decoded (match resolution complete), leaving only the in-range output.
712 #[cfg(feature = "lsm")]
713 fn buffer_discard_front(&mut self, n: usize) {
714 match self {
715 Self::Ring(s) => s.buffer.discard_front(n),
716 Self::Flat(s) => s.buffer.discard_front(n),
717 }
718 }
719
720 /// Prime the match window with the caller's already-decompressed tail for
721 /// a resumed partial decode. Routes through whichever backend the current
722 /// scratch holds. See
723 /// [`DecodeBuffer::prime_window`](crate::decoding::decode_buffer::DecodeBuffer::prime_window).
724 #[cfg(feature = "lsm")]
725 fn prime_window(
726 &mut self,
727 prefix: &[u8],
728 total_output: u64,
729 kernel: crate::cpu_kernel::CpuKernelTag,
730 ) {
731 match self {
732 Self::Ring(s) => s.buffer.prime_window(prefix, total_output, kernel),
733 Self::Flat(s) => s.buffer.prime_window(prefix, total_output, kernel),
734 }
735 }
736
737 /// Total decompressed bytes produced so far (the buffer's running output
738 /// counter, unaffected by window drops / drains): the frame's length once
739 /// its last block is decoded, and a captured resume state's offset.
740 fn total_output(&self) -> u64 {
741 match self {
742 Self::Ring(s) => s.buffer.total_output(),
743 Self::Flat(s) => s.buffer.total_output(),
744 }
745 }
746
747 /// Set the output counter to the length of a frame the direct path decoded
748 /// into the caller's slice, which never passes through this buffer.
749 fn set_total_output(&mut self, produced: u64) {
750 match self {
751 Self::Ring(s) => s.buffer.set_total_output(produced),
752 Self::Flat(s) => s.buffer.set_total_output(produced),
753 }
754 }
755
756 /// Append `data` to the buffer, copied under the kernel a literals-only
757 /// block takes: the scalar tier stays portable.
758 fn buffer_push(&mut self, data: &[u8], kernel: crate::cpu_kernel::CpuKernelTag) {
759 use crate::cpu_kernel::{BaselineKernel, CpuKernelTag, ScalarKernel};
760 let scalar = kernel == CpuKernelTag::Scalar;
761 match self {
762 Self::Ring(s) if scalar => s.buffer.push::<ScalarKernel>(data),
763 Self::Ring(s) => s.buffer.push::<BaselineKernel>(data),
764 Self::Flat(s) if scalar => s.buffer.push::<ScalarKernel>(data),
765 Self::Flat(s) => s.buffer.push::<BaselineKernel>(data),
766 }
767 }
768
769 /// Clone the cross-block entropy/repcode state (FSE + Huffman tables +
770 /// `offset_hist`) out of the live scratch for a [`ResumeState`] snapshot.
771 #[cfg(feature = "lsm")]
772 fn export_entropy(
773 &self,
774 dict: Option<&crate::decoding::dictionary::Dictionary>,
775 ) -> (
776 crate::decoding::scratch::FSEScratch,
777 crate::decoding::scratch::HuffmanScratch,
778 [u32; 3],
779 ) {
780 let (fse_src, huf_src, offset_hist) = match self {
781 Self::Ring(s) => (&s.fse, &s.huf, s.offset_hist),
782 Self::Flat(s) => (&s.fse, &s.huf, s.offset_hist),
783 };
784 // The live scratch may still be `Dict`-sourced; `reinit_from` /
785 // `reinit_resolved_from` resolve those axes through the borrow into a
786 // self-contained `Local` snapshot, so the dictionary is required here
787 // whenever the captured frame is dict-backed.
788 let mut fse = crate::decoding::scratch::FSEScratch::new();
789 fse.reinit_from(fse_src, dict);
790 let mut huf = crate::decoding::scratch::HuffmanScratch::new();
791 huf.reinit_resolved_from(huf_src, dict);
792 (fse, huf, offset_hist)
793 }
794
795 /// Install entropy/repcode state from a [`ResumeState`] into the live
796 /// scratch so a `Repeat_Mode` / treeless resume block resolves against the
797 /// same tables a contiguous decode would have carried over.
798 #[cfg(feature = "lsm")]
799 fn restore_entropy(&mut self, state: &ResumeState) {
800 // The `ResumeState` snapshot is a self-contained `Local` materialization
801 // (export resolved every Dict axis into local bytes and detached), so no
802 // dictionary borrow is needed to install it back.
803 match self {
804 Self::Ring(s) => {
805 s.fse.reinit_from(&state.fse, None);
806 s.huf.reinit_resolved_from(&state.huf, None);
807 s.offset_hist = state.offset_hist;
808 }
809 Self::Flat(s) => {
810 s.fse.reinit_from(&state.fse, None);
811 s.huf.reinit_resolved_from(&state.huf, None);
812 s.offset_hist = state.offset_hist;
813 }
814 }
815 }
816
817 /// XXH64 of the window-prime bytes for a [`ResumeState`]: the last
818 /// `min(window_size, buffer_len)` bytes of the current buffer, which at emit
819 /// time are exactly the match-window context the resume block will see.
820 /// Wrap-aware via `last_n_as_slices` — streaming XXH64 over the two slices
821 /// equals a single hash over the contiguous `window_prime` at resume.
822 #[cfg(all(feature = "lsm", feature = "hash"))]
823 fn window_tail_hash(&self, window_size: usize) -> u64 {
824 use core::hash::Hasher;
825 let n = core::cmp::min(window_size, self.buffer_len());
826 let (s1, s2) = self.last_n_as_slices(n);
827 let mut h = twox_hash::XxHash64::with_seed(0);
828 h.write(s1);
829 h.write(s2);
830 h.finish()
831 }
832
833 fn decode_block_content<R: Read>(
834 &mut self,
835 decoder: &mut BlockDecoder,
836 header: &crate::blocks::block::BlockHeader,
837 source: R,
838 dict: Option<&crate::decoding::dictionary::Dictionary>,
839 ) -> Result<u64, DecodeBlockContentError> {
840 match self {
841 Self::Ring(s) => decoder.decode_block_content(header, s, dict, source),
842 Self::Flat(s) => decoder.decode_block_content(header, s, dict, source),
843 }
844 }
845
846 /// [`Self::decode_block_content`] for a block whose content is already in
847 /// memory: a compressed block is decoded from `source` in place instead of
848 /// being copied into the block buffer first. Advances `source` past it.
849 fn decode_block_content_from_slice(
850 &mut self,
851 decoder: &mut BlockDecoder,
852 header: &crate::blocks::block::BlockHeader,
853 source: &mut &[u8],
854 dict: Option<&crate::decoding::dictionary::Dictionary>,
855 ) -> Result<u64, DecodeBlockContentError> {
856 match self {
857 Self::Ring(s) => decoder.decode_block_content_from_slice(header, s, dict, source),
858 Self::Flat(s) => decoder.decode_block_content_from_slice(header, s, dict, source),
859 }
860 }
861
862 #[cfg(feature = "hash")]
863 fn hash_finish(&self) -> u64 {
864 use core::hash::Hasher;
865 match self {
866 Self::Ring(s) => s.buffer.hash.finish(),
867 Self::Flat(s) => s.buffer.hash.finish(),
868 }
869 }
870
871 /// Hand the decoder's block limit to the inner `DecodeBuffer` for the
872 /// frame starting.
873 fn set_block_limit(&mut self, limit: usize) {
874 match self {
875 Self::Ring(s) => s.buffer.set_block_limit(limit),
876 Self::Flat(s) => s.buffer.set_block_limit(limit),
877 }
878 }
879
880 /// Forward the drain-time hash toggle to the inner `DecodeBuffer`
881 /// (streaming path). Called by the frame layer from the decoder's
882 /// `ContentChecksum` mode before each decode.
883 #[cfg(feature = "hash")]
884 fn set_compute_hash(&mut self, compute: bool) {
885 match self {
886 Self::Ring(s) => s.buffer.set_compute_hash(compute),
887 Self::Flat(s) => s.buffer.set_compute_hash(compute),
888 }
889 }
890}
891
892/// The dictionary of `dicts` a frame naming `dict_id` decodes with: the one
893/// carrying that ID, else the last, as upstream keeps the most recently
894/// referenced DDict when its set holds none for the frame
895/// (`ZSTD_DCtx_selectFrameDDict`). `dicts` is not empty.
896fn dictionary_for_frame(dicts: &[DictionaryHandle], dict_id: Option<u32>) -> &DictionaryHandle {
897 dict_id
898 .and_then(|id| dicts.iter().rev().find(|dict| dict.id() == id))
899 .or_else(|| dicts.last())
900 .expect("callers pass at least one dictionary")
901}
902
903struct FrameDecoderState {
904 pub frame_header: frame::FrameHeader,
905 decoder_scratch: DecoderScratchKind,
906 frame_finished: bool,
907 block_counter: usize,
908 bytes_read_counter: u64,
909 check_sum: Option<u32>,
910 using_dict: Option<u32>,
911 /// The dictionary handle applied to this frame, owned for the frame's whole
912 /// decode so the block loop can hand the scratch a `&Dictionary` borrow at
913 /// every `Dict`-sourced table read. ONE refcount clone per dict-apply (not
914 /// per block, not per frame on the reuse path — the same handle stays held
915 /// across `init_with_dict_handle` -> `decode_blocks`); the decode loop
916 /// borrows from this field (disjoint from `decoder_scratch`) with zero
917 /// further clones. `None` on the no-dict path. Cleared by `reset`.
918 active_dict: Option<DictionaryHandle>,
919 /// A ceiling lowered below this frame's window after the frame started;
920 /// see [`FrameDecoder::set_max_window_size`]. Every decode step of the frame
921 /// is refused while it is set. Cleared when the next frame starts.
922 window_refused_at: Option<u64>,
923 /// The decoder's block limit when this frame started; see
924 /// [`FrameDecoder::set_max_block_size`].
925 block_limit: usize,
926}
927
928pub enum BlockDecodingStrategy {
929 All,
930 UptoBlocks(usize),
931 UptoBytes(usize),
932}
933
934/// Outcome of [`FrameDecoder::decode_blocks_partial`]: the decompressed
935/// bytes of the requested inner-block range plus where (if anywhere)
936/// decoding stopped early.
937///
938/// Behind the `lsm` Cargo feature.
939#[cfg(feature = "lsm")]
940#[derive(Debug)]
941pub struct PartialDecode {
942 /// Decompressed bytes of the in-range blocks actually decoded, in
943 /// frame order, as one contiguous buffer. `data.len()` equals the sum
944 /// of the decompressed sizes of blocks `start_block .. start_block +
945 /// blocks_decoded`.
946 pub data: alloc::vec::Vec<u8>,
947 /// First block whose output is in [`data`](Self::data): the requested
948 /// `start_block` on a fresh decode, or [`ResumeState::block_index`] when
949 /// resuming (the caller-supplied `start_block` is ignored in resume mode).
950 pub start_block: u32,
951 /// Number of in-range blocks successfully decoded into
952 /// [`data`](Self::data).
953 pub blocks_decoded: u32,
954 /// `Some((block_index, error))` if decoding stopped on a failing block
955 /// before reaching `end_block` (a corrupt block inside the range, or a
956 /// leading block needed for window context). `None` if the requested
957 /// range decoded cleanly or the frame's last block was reached first.
958 ///
959 /// When the failing block is a leading context block
960 /// (`block_index < start_block`), the in-range window could not be
961 /// built so [`data`](Self::data) is empty and `blocks_decoded` is 0.
962 pub stopped_at: Option<(u32, FrameDecoderError)>,
963 /// `true` if the frame's last block was reached during this decode.
964 pub frame_finished: bool,
965 /// Cross-block carry-over state for resuming the next extent. Feed it back
966 /// (with the matching `window_prime`) via the `resume` argument of a
967 /// later [`FrameDecoder::decode_blocks_partial`] to continue from
968 /// [`ResumeState::block_index`] without re-decompressing the prefix.
969 ///
970 /// `None` in two cases: emission was not requested (`emit_resume = false`),
971 /// OR this decode reached the frame's last block ([`frame_finished`] is
972 /// `true`) — there is no following block to resume from, so no snapshot is
973 /// emitted even with `emit_resume = true`. Callers walking a frame
974 /// incrementally should therefore stop when `frame_finished` is set rather
975 /// than treat a `None` here as "emission disabled".
976 ///
977 /// [`frame_finished`]: Self::frame_finished
978 pub resume_state: Option<ResumeState>,
979}
980
981impl FrameDecoderState {
982 /// Window size to actually reserve for this frame's decode buffer.
983 /// A declared content size caps the useful window: matches can never
984 /// reference further back than the bytes that will ever exist, so an
985 /// encoder-declared window above the FCS (e.g. a level-preset window
986 /// on a smaller input) must not inflate the reservation. Every
987 /// `reserve_buffer` site routes through this so the cap is uniform
988 /// across `decode_all_impl`, `decode_blocks`, and the partial path.
989 fn useful_window_size(&self) -> usize {
990 let window_size = self.frame_header.window_size().unwrap_or(0);
991 if self.frame_header.fcs_declared() {
992 window_size.min(self.frame_header.frame_content_size()) as usize
993 } else {
994 window_size as usize
995 }
996 }
997
998 /// The most a frame decoding through the buffer holds, which its growth
999 /// stops at. A single-segment frame's buffer holds its whole content,
1000 /// which is its window. A multi-segment frame's ring holds the
1001 /// content-capped window plus room for the next block, since each
1002 /// compressed block reserves a whole block before it decodes: without it
1003 /// the first block past a full window grew the ring and copied the window
1004 /// across, and a limit at the content left no block of room once the
1005 /// window filled. Upstream sizes its stream buffer as window + block too
1006 /// (`ZSTD_decodingBufferSize_min`); it can cap at the content because its
1007 /// buffer is not a ring.
1008 ///
1009 /// A declared content size does NOT cap this. A frame can declare less than
1010 /// its blocks go on to produce, and that is caught by the check against the
1011 /// declaration once the bytes exist; a limit that stopped the ring short of
1012 /// them would instead have the write run out of buffer, which the ring
1013 /// asserts on rather than reports. Only the up-front reservation takes the
1014 /// declaration ([`Self::decoding_buffer_size`]).
1015 fn decoding_buffer_limit(&self) -> usize {
1016 let useful_window = self.useful_window_size();
1017 if self.frame_header.descriptor.single_segment_flag() {
1018 return useful_window;
1019 }
1020 let window_size = self.frame_header.window_size().unwrap_or(0) as usize;
1021 // No overflow: the window was checked against
1022 // `MAXIMUM_ALLOWED_WINDOW_SIZE` when the header was taken.
1023 useful_window + window_size.min(crate::common::MAX_BLOCK_SIZE as usize)
1024 }
1025
1026 /// What to reserve up front: the limit, except for a multi-segment frame
1027 /// whose declared content fits its window, which reserves just its
1028 /// content. Only compressed blocks need the block of room past it, and
1029 /// they reserve it themselves; the limit then caps that growth at one
1030 /// block. Such frames are rare (encoders mark a frame that fits its window
1031 /// single-segment), and a small Raw or RLE one should not pay a block.
1032 ///
1033 /// A frame that declares its size never reserves past the declaration: the
1034 /// block of room is there for what a block still has to produce, and an
1035 /// honest frame produces exactly what it promised. A 1 MiB window declaring
1036 /// one byte more reserved a whole block of room for that byte. A frame that
1037 /// goes on to exceed its declaration grows into the limit and is then caught
1038 /// by the check against it.
1039 fn decoding_buffer_size(&self) -> usize {
1040 let window_size = self.frame_header.window_size().unwrap_or(0);
1041 if !self.frame_header.fcs_declared() {
1042 return self.decoding_buffer_limit();
1043 }
1044 let declared = self.frame_header.frame_content_size();
1045 if declared <= window_size {
1046 return self.useful_window_size();
1047 }
1048 self.decoding_buffer_limit()
1049 .min(usize::try_from(declared).unwrap_or(usize::MAX))
1050 }
1051
1052 /// Reserve this frame's decode buffer ([`Self::decoding_buffer_size`])
1053 /// and cap its later growth ([`Self::decoding_buffer_limit`]).
1054 fn reserve_decoding_buffer(&mut self) {
1055 let target = self.decoding_buffer_size();
1056 let growth_limit = self.decoding_buffer_limit();
1057 // What the frame promised to produce, so the per-block reservation can
1058 // ask for the smaller of a block and what is left of that promise.
1059 let declared = self
1060 .frame_header
1061 .fcs_declared()
1062 .then(|| self.frame_header.frame_content_size());
1063 self.decoder_scratch.set_declared_content(declared);
1064 self.decoder_scratch.reserve_buffer(target, growth_limit);
1065 }
1066
1067 /// Construct a new frame decoder state, reading the frame header
1068 /// from `source`. When `magicless` is `true`, the 4-byte magic
1069 /// number prefix is NOT consumed (upstream zstd `ZSTD_f_zstd1_magicless`).
1070 /// Crate-internal — reached only via `FrameDecoder::init` /
1071 /// `FrameDecoder::init_with_dict_handle`. The decode buffer is
1072 /// allocated lazily on BOTH backends (`new_ring` and `new_flat`):
1073 /// direct-eligible frames pay zero buffer allocation, and the
1074 /// non-direct fallback reserves `window_size` once in
1075 /// `decode_all_impl` / `decode_blocks` via `reserve_buffer` before
1076 /// any block write.
1077 #[inline]
1078 pub(crate) fn new_with_format(
1079 source: impl Read,
1080 magicless: bool,
1081 max_window: u64,
1082 block_limit: usize,
1083 ) -> Result<FrameDecoderState, FrameDecoderError> {
1084 let (frame, header_size) = frame::read_frame_header_with_format(source, magicless)?;
1085 Self::new_with_parsed_header(frame, header_size, max_window, block_limit)
1086 }
1087
1088 /// Build a fresh state from an already-parsed frame header (the non-parsing
1089 /// tail of [`Self::new_with_format`]). Shared by the `Read` path and the
1090 /// slice-direct path ([`FrameDecoder::reset_from_slice`]). A window past
1091 /// `max_window` is refused, and no block may hold more than `block_limit`.
1092 pub(crate) fn new_with_parsed_header(
1093 frame: frame::FrameHeader,
1094 header_size: u8,
1095 max_window: u64,
1096 block_limit: usize,
1097 ) -> Result<FrameDecoderState, FrameDecoderError> {
1098 let window_size = checked_window_size(&frame, max_window)?;
1099
1100 let mut decoder_scratch = if frame.descriptor.single_segment_flag() {
1101 DecoderScratchKind::new_flat(window_size as usize)
1102 } else {
1103 DecoderScratchKind::new_ring(window_size as usize)
1104 };
1105 decoder_scratch.set_block_limit(block_limit);
1106 Ok(FrameDecoderState {
1107 frame_header: frame,
1108 frame_finished: false,
1109 block_counter: 0,
1110 decoder_scratch,
1111 bytes_read_counter: u64::from(header_size),
1112 check_sum: None,
1113 using_dict: None,
1114 active_dict: None,
1115 window_refused_at: None,
1116 block_limit,
1117 })
1118 }
1119
1120 /// The most one block of this frame may hold: the smaller of its window,
1121 /// 128 KiB and the decoder's block limit.
1122 fn block_maximum(&self) -> usize {
1123 let window = self
1124 .frame_header
1125 .window_size()
1126 .map_or(usize::MAX, |w| usize::try_from(w).unwrap_or(usize::MAX));
1127 decoding::block_decoder::block_maximum(window).min(self.block_limit)
1128 }
1129
1130 /// Reset this state for a new frame read from `source`, reusing
1131 /// existing allocations. When `magicless` is `true`, the frame
1132 /// header is read WITHOUT expecting a magic-number prefix
1133 /// (upstream zstd `ZSTD_f_zstd1_magicless`). Crate-internal — reached
1134 /// only via `FrameDecoder::reset`.
1135 ///
1136 /// `DecodeBuffer::reset` no longer reserves window_size for either
1137 /// backend — capacity decisions live one layer up. Both backends are
1138 /// lazy: direct-eligible frames pay zero backing-buffer allocation
1139 /// here (they write through `UserSliceBackend`), and the non-direct
1140 /// path is pre-reserved by `decode_all_impl` / `decode_blocks` via
1141 /// `DecoderScratchKind::reserve_buffer(window_size)` before any block
1142 /// write. A reused scratch whose new frame fits within prior capacity
1143 /// reuses it; a larger one grows on that same `reserve_buffer` call.
1144 #[inline]
1145 pub(crate) fn reset_with_format(
1146 &mut self,
1147 source: impl Read,
1148 magicless: bool,
1149 max_window: u64,
1150 block_limit: usize,
1151 ) -> Result<(), FrameDecoderError> {
1152 let (frame_header, header_size) = frame::read_frame_header_with_format(source, magicless)?;
1153 self.reset_with_parsed_header(frame_header, header_size, max_window, block_limit)
1154 }
1155
1156 /// Apply an already-parsed frame header to this state (the non-parsing tail
1157 /// of [`Self::reset_with_format`]). Shared by the `Read` path and the slice-direct
1158 /// path ([`FrameDecoder::reset_from_slice`]). A window past `max_window`
1159 /// is refused, and no block may hold more than `block_limit`.
1160 #[inline]
1161 pub(crate) fn reset_with_parsed_header(
1162 &mut self,
1163 frame_header: frame::FrameHeader,
1164 header_size: u8,
1165 max_window: u64,
1166 block_limit: usize,
1167 ) -> Result<(), FrameDecoderError> {
1168 let window_size = checked_window_size(&frame_header, max_window)?;
1169
1170 self.decoder_scratch
1171 .reset(&frame_header, window_size as usize);
1172 self.decoder_scratch.set_block_limit(block_limit);
1173 self.block_limit = block_limit;
1174 self.frame_header = frame_header;
1175 self.frame_finished = false;
1176 self.block_counter = 0;
1177 self.bytes_read_counter = u64::from(header_size);
1178 self.check_sum = None;
1179 self.using_dict = None;
1180 self.window_refused_at = None;
1181 // `active_dict` is intentionally NOT cleared here: it is only ever READ
1182 // while a scratch table source is `Dict`, which `init_from_dict` arms
1183 // on a dict frame and which a no-dict frame leaves `Local` (so a stale
1184 // held handle is never read). Keeping it lets the per-apply `ptr::eq`
1185 // reuse-check below skip the clone when the SAME dictionary is
1186 // re-applied frame-over-frame (the CoordiNode per-label-dict hot path)
1187 // — zero refcount churn on reuse, one clone only on a genuine swap.
1188 Ok(())
1189 }
1190
1191 /// Hold the dictionary handle for this frame's whole decode so the block
1192 /// loop can borrow `&Dictionary` at every `Dict`-sourced read. Clones the
1193 /// handle ONLY when it is a different dictionary than the one already held
1194 /// (`ptr::eq` on the `Arc`'s pointee) — so re-applying the SAME dictionary
1195 /// frame-over-frame (the reuse hot path) costs zero refcount churn.
1196 fn set_active_dict(&mut self, dict: &DictionaryHandle) {
1197 if self
1198 .active_dict
1199 .as_ref()
1200 .is_none_or(|held| !core::ptr::eq(held.as_dict(), dict.as_dict()))
1201 {
1202 self.active_dict = Some(dict.clone());
1203 }
1204 }
1205}
1206
1207/// The window `frame` declares, refused past `max_window`. The ceiling is at
1208/// most [`MAX_DECODER_WINDOW_SIZE`](crate::common::MAX_DECODER_WINDOW_SIZE), so
1209/// the window a frame is admitted with always fits in `usize`.
1210fn checked_window_size(
1211 frame: &frame::FrameHeader,
1212 max_window: u64,
1213) -> Result<u64, FrameDecoderError> {
1214 debug_assert!(max_window <= crate::common::MAX_DECODER_WINDOW_SIZE);
1215 let window_size = frame.window_size()?;
1216 if window_size > max_window {
1217 return Err(FrameDecoderError::WindowSizeTooBig {
1218 requested: window_size,
1219 limit: max_window,
1220 });
1221 }
1222 Ok(window_size)
1223}
1224
1225impl Default for FrameDecoder {
1226 fn default() -> Self {
1227 Self::new()
1228 }
1229}
1230
1231impl FrameDecoder {
1232 /// This will create a new decoder without allocating anything yet.
1233 /// init()/reset() will allocate all needed buffers if it is the first time this decoder is used
1234 /// else they just reset these buffers with not further allocations
1235 pub fn new() -> FrameDecoder {
1236 FrameDecoder {
1237 state: None,
1238 #[cfg(test)]
1239 direct_frames: 0,
1240 owned_dicts: BTreeMap::new(),
1241 #[cfg(target_has_atomic = "ptr")]
1242 shared_dicts: BTreeMap::new(),
1243 #[cfg(not(target_has_atomic = "ptr"))]
1244 shared_dicts: (),
1245 magicless: false,
1246 max_window_size: MAXIMUM_ALLOWED_WINDOW_SIZE,
1247 max_block_size: crate::common::MAX_BLOCK_SIZE as usize,
1248 content_checksum: ContentChecksum::EmitOnly,
1249 #[cfg(feature = "lsm")]
1250 expect_dict_id: None,
1251 #[cfg(feature = "lsm")]
1252 expect_window_descriptor: None,
1253 #[cfg(all(feature = "lsm", feature = "hash"))]
1254 per_block_checksums_enabled: false,
1255 #[cfg(all(feature = "lsm", feature = "hash"))]
1256 computed_block_checksums: alloc::vec::Vec::new(),
1257 kernel: crate::cpu_kernel::detect_cpu_kernel(),
1258 armed_dict: None,
1259 #[cfg(feature = "legacy")]
1260 legacy: None,
1261 }
1262 }
1263
1264 /// The v0.5-v0.7 frame in progress, if the current frame is one.
1265 #[cfg(feature = "legacy")]
1266 #[inline]
1267 fn legacy_frame(&self) -> Option<&decoding::legacy::LegacyStream> {
1268 self.legacy.as_deref().filter(|stream| stream.active())
1269 }
1270
1271 /// [`Self::legacy_frame`], mutable.
1272 #[cfg(feature = "legacy")]
1273 #[inline]
1274 fn legacy_frame_mut(&mut self) -> Option<&mut decoding::legacy::LegacyStream> {
1275 self.legacy.as_deref_mut().filter(|stream| stream.active())
1276 }
1277
1278 /// Refuse a call that takes RFC 8878 blocks while a v0.5-v0.7 frame is
1279 /// the current one: the RFC 8878 state a reused decoder still holds
1280 /// belongs to an earlier frame, possibly one left before its last block.
1281 /// Upstream's block-level calls likewise take no v0.5-v0.7 frame.
1282 #[inline]
1283 fn refuse_during_legacy_frame(&self) -> Result<(), FrameDecoderError> {
1284 #[cfg(feature = "legacy")]
1285 if self.legacy_frame().is_some() {
1286 return Err(FrameDecoderError::NotYetInitialized);
1287 }
1288 Ok(())
1289 }
1290
1291 /// Leave any v0.5-v0.7 frame in progress: an RFC 8878 frame has started.
1292 #[inline]
1293 fn end_legacy_frame(&mut self) {
1294 #[cfg(feature = "legacy")]
1295 if let Some(stream) = self.legacy.as_deref_mut() {
1296 stream.end();
1297 }
1298 }
1299
1300 /// Read the frame header from `source` into the frame state, creating it
1301 /// on first use. A v0.5-v0.7 frame is started instead when its magic
1302 /// number is what the header begins with, its dictionary chosen from
1303 /// `dicts` (the registry's when empty); `Ok(false)` reports that.
1304 fn read_frame_header_into_state<R: Read>(
1305 &mut self,
1306 source: &mut R,
1307 #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle],
1308 ) -> Result<bool, FrameDecoderError> {
1309 // A new header ends any v0.5-v0.7 frame, whether or not it reads.
1310 self.end_legacy_frame();
1311 let magicless = self.magicless;
1312 let max_window = self.max_window_size;
1313 let block_limit = self.max_block_size;
1314 let parsed = match &mut self.state {
1315 Some(s) => s.reset_with_format(&mut *source, magicless, max_window, block_limit),
1316 None => {
1317 FrameDecoderState::new_with_format(&mut *source, magicless, max_window, block_limit)
1318 .map(|state| {
1319 self.state = Some(state);
1320 // A frame that resolves its dictionary by ID drops one armed
1321 // for a forced-dictionary stream that never started.
1322 self.armed_dict = None;
1323 })
1324 }
1325 };
1326 match parsed {
1327 Ok(()) => Ok(true),
1328 #[cfg(feature = "legacy")]
1329 Err(FrameDecoderError::ReadFrameHeaderError(
1330 crate::decoding::errors::ReadFrameHeaderError::BadMagicNumber(magic),
1331 )) if crate::decoding::LegacyVersion::of(&magic.to_le_bytes()).is_some() => {
1332 self.start_legacy_frame(magic, source, dicts)?;
1333 Ok(false)
1334 }
1335 Err(err) => Err(err),
1336 }
1337 }
1338
1339 /// Start the v0.5-v0.7 frame whose magic number `magic` has been read
1340 /// from `source`, reading the rest of its header from it.
1341 #[cfg(feature = "legacy")]
1342 fn start_legacy_frame<R: Read>(
1343 &mut self,
1344 magic: u32,
1345 source: &mut R,
1346 dicts: &[DictionaryHandle],
1347 ) -> Result<(), FrameDecoderError> {
1348 use crate::decoding::errors::ReadFrameHeaderError as ReadError;
1349 use crate::decoding::legacy::{self, HeaderFields};
1350 let version = crate::decoding::LegacyVersion::of(&magic.to_le_bytes())
1351 .expect("the caller checked the magic number");
1352 // The longest header: magic, descriptor, window, dictionary ID and an
1353 // eight-byte content size.
1354 let mut bytes = [0u8; 18];
1355 bytes[..4].copy_from_slice(&magic.to_le_bytes());
1356 // A reader's own error is handed back in the field it failed in, as
1357 // for an RFC 8878 header.
1358 source
1359 .read_exact(&mut bytes[4..5])
1360 .map_err(ReadError::FrameDescriptorReadError)?;
1361 let fields = HeaderFields::of(version, bytes[4]);
1362 let mut at = 5;
1363 let mut read_field = |len: usize, failed: fn(Error) -> ReadError| {
1364 source
1365 .read_exact(&mut bytes[at..at + len])
1366 .map_err(failed)?;
1367 at += len;
1368 Ok::<_, ReadError>(())
1369 };
1370 read_field(fields.window, ReadError::WindowDescriptorReadError)?;
1371 read_field(fields.dict_id, ReadError::DictionaryIdReadError)?;
1372 read_field(fields.content_size, ReadError::FrameContentSizeReadError)?;
1373 let header = legacy::read_header(&bytes[..at])?;
1374 #[cfg(feature = "lsm")]
1375 self.validate_legacy_expectations(&header, &bytes[..at])?;
1376 if header.window > self.max_window_size {
1377 return Err(FrameDecoderError::WindowSizeTooBig {
1378 requested: header.window,
1379 limit: self.max_window_size,
1380 });
1381 }
1382 // The stream keeps the handle, counting it only when the dictionary
1383 // changes; it is taken out while the dictionary is borrowed here.
1384 let mut stream = self.take_legacy_stream();
1385 let started = self
1386 .legacy_frame_dict(header.dict_id, dicts)
1387 .and_then(|dict| Ok(stream.start(header, dict, self.legacy_frame_options())?));
1388 self.legacy = Some(stream);
1389 started
1390 }
1391
1392 /// What the decoder's settings ask of a v0.5-v0.7 frame.
1393 #[cfg(feature = "legacy")]
1394 fn legacy_frame_options(&self) -> decoding::legacy::FrameOptions {
1395 decoding::legacy::FrameOptions {
1396 #[cfg(feature = "hash")]
1397 checksum: self.content_checksum,
1398 block_max: self.max_block_size,
1399 #[cfg(all(feature = "lsm", feature = "hash"))]
1400 block_digests: self.per_block_checksums_enabled,
1401 }
1402 }
1403
1404 /// Decode with a named CPU tier instead of the one this machine selected.
1405 ///
1406 /// Which tier runs is otherwise a property of the host, so a test on any
1407 /// one machine covers exactly one of them. The tiers are separate
1408 /// monomorphs with their own gates and their own argument plumbing, and
1409 /// a divergence between them is precisely what no single-host run can see.
1410 ///
1411 /// The caller is responsible for naming a tier the CPU can actually
1412 /// execute: the monoliths carry `target_feature`, and asking for one the
1413 /// machine lacks is undefined behaviour rather than an error.
1414 #[cfg(test)]
1415 // Its consumer is the per-tier agreement test, and the tiers worth
1416 // disagreeing are the x86 ones, so on other architectures nothing calls it.
1417 #[allow(dead_code)]
1418 pub(crate) fn decode_with_kernel_for_tests(&mut self, kernel: crate::cpu_kernel::CpuKernelTag) {
1419 self.kernel = kernel;
1420 }
1421
1422 /// Heap bytes currently held by the decoder's lazily-grown workspace:
1423 /// the decode-window buffer plus the per-block literal/content buffers
1424 /// and the entropy tables. Returns 0 before the first frame is initialised
1425 /// (no workspace allocated yet). The window allocation dominates and grows
1426 /// with the frame's window size; this is the value to track for decode-time
1427 /// memory pressure, mirroring the workspace term of upstream
1428 /// `ZSTD_sizeof_DCtx`. Shared dictionaries (ref-counted handles) are not
1429 /// counted, matching upstream excluding `refDDict` memory.
1430 pub fn workspace_size(&self) -> usize {
1431 let frame = self
1432 .state
1433 .as_ref()
1434 .map_or(0, |s| s.decoder_scratch.workspace_bytes());
1435 #[cfg(feature = "legacy")]
1436 let frame = frame
1437 + self.legacy.as_ref().map_or(0, |stream| {
1438 core::mem::size_of::<decoding::legacy::LegacyStream>() + stream.heap_bytes()
1439 });
1440 frame
1441 }
1442
1443 /// Select how the frame's optional content checksum is handled
1444 /// (compute, expose, verify, or skip). See [`ContentChecksum`].
1445 /// Default [`ContentChecksum::EmitOnly`]. Takes effect on the next
1446 /// decode; safe to call between frames on a reused decoder. A v0.7
1447 /// frame's checksum covers its content from the first block, so turning
1448 /// it on after that block leaves the frame unchecked.
1449 pub fn set_content_checksum(&mut self, mode: ContentChecksum) {
1450 self.content_checksum = mode;
1451 #[cfg(all(feature = "legacy", feature = "hash"))]
1452 if let Some(stream) = self.legacy_frame_mut() {
1453 stream.set_checksum_mode(mode);
1454 }
1455 }
1456
1457 /// Opt in to per-block XXH64 verification during decode.
1458 /// Default off; zero cost when disabled. Each block's decompressed
1459 /// bytes are XXH64-hashed (low 32 bits) and appended to
1460 /// [`Self::computed_block_checksums`] as the decode progresses.
1461 /// Callers compare the captured digests against externally-stored
1462 /// expected values (e.g. from a per-block sidecar in the
1463 /// containing application protocol).
1464 ///
1465 /// Behind `all(feature = "lsm", feature = "hash")` — the XXH64
1466 /// primitive lives behind the `hash` feature, so this method
1467 /// only compiles when both are enabled.
1468 #[cfg(all(feature = "lsm", feature = "hash"))]
1469 pub fn enable_per_block_checksums(&mut self) {
1470 self.per_block_checksums_enabled = true;
1471 }
1472
1473 /// Per-block XXH64 (low 32 bits) digests captured during the
1474 /// current frame's decode. Empty unless
1475 /// [`Self::enable_per_block_checksums`] was called before
1476 /// [`Self::decode_all`] / [`Self::reset`].
1477 ///
1478 /// Reset at the start of every new frame.
1479 ///
1480 /// Behind `all(feature = "lsm", feature = "hash")`.
1481 #[cfg(all(feature = "lsm", feature = "hash"))]
1482 pub fn computed_block_checksums(&self) -> &[u32] {
1483 #[cfg(feature = "legacy")]
1484 if let Some(stream) = self.legacy_frame() {
1485 return stream.block_digests();
1486 }
1487 &self.computed_block_checksums
1488 }
1489
1490 /// Pin the expected `Dictionary_ID` for the next frame.
1491 ///
1492 /// When `expected` is set, [`Self::init`] / [`Self::reset`]
1493 /// validate it against the parsed frame header BEFORE any
1494 /// block decode work runs. A mismatch returns
1495 /// [`crate::decoding::errors::FrameDecoderError::UnexpectedDictId`]
1496 /// before any block decode and before any output is produced.
1497 /// Scratch buffer allocation / reservation for the decode
1498 /// pipeline happens during frame-header parsing, which is
1499 /// already complete when this validation fires — the cost of
1500 /// scratch sizing is paid even on a mismatched header. The
1501 /// guarantee is "no block decode, no XXH64 init, no partial
1502 /// output", not "zero allocation".
1503 ///
1504 /// `Some(0)` is treated as "no dictionary expected": a frame
1505 /// whose header omits the optional `Dictionary_ID` field
1506 /// (flag value 0) passes the check; a frame that carries an
1507 /// explicit non-zero id fails.
1508 ///
1509 /// `None` (default) disables the check.
1510 ///
1511 /// Primary use case: post-AEAD-decrypt sanity check in
1512 /// wire-format consumers (e.g. lsm-tree's encrypted block
1513 /// format pins the `dict_id` baked into the AAD against the
1514 /// inner zstd frame's `dict_id` to defeat dict-substitution
1515 /// attacks).
1516 ///
1517 /// NOT a replacement for AEAD authentication. NOT the same
1518 /// semantic as upstream zstd `ZSTD_d_windowLogMax` (which is a
1519 /// ceiling-style limit, separate concern).
1520 #[cfg(feature = "lsm")]
1521 #[cfg_attr(docsrs, doc(cfg(feature = "lsm")))]
1522 pub fn expect_dict_id(&mut self, expected: Option<u32>) {
1523 self.expect_dict_id = expected;
1524 }
1525
1526 /// Pin the expected raw `Window_Descriptor` byte (RFC 8878
1527 /// §3.1.1.1.2 layout: `(exp << 3) | mantissa`) for the next
1528 /// frame.
1529 ///
1530 /// When `expected` is set, [`Self::init`] / [`Self::reset`]
1531 /// validate it against the parsed frame header BEFORE any
1532 /// block decode work runs. A mismatch returns
1533 /// [`crate::decoding::errors::FrameDecoderError::UnexpectedWindowDescriptor`].
1534 ///
1535 /// Single-segment frames omit the `Window_Descriptor` byte
1536 /// from the wire entirely. Setting an expectation while
1537 /// receiving a single-segment frame fails the check with
1538 /// `found: None` — there is no on-wire byte to match against,
1539 /// which is reported explicitly rather than silently passing.
1540 ///
1541 /// `None` (default) disables the check.
1542 ///
1543 /// Byte-exact equality, NOT a ceiling. Upstream zstd
1544 /// `ZSTD_d_windowLogMax` is a separate ceiling-style limit
1545 /// available through the C FFI surface; this method is for
1546 /// strict equality validation against a pinned expectation
1547 /// (e.g. lsm-tree's wire format pins the window descriptor
1548 /// from the AAD to defeat decompression-bomb-swap attacks).
1549 #[cfg(feature = "lsm")]
1550 #[cfg_attr(docsrs, doc(cfg(feature = "lsm")))]
1551 pub fn expect_window_descriptor(&mut self, expected: Option<u8>) {
1552 self.expect_window_descriptor = expected;
1553 }
1554
1555 /// Validate the just-parsed frame header against any pinned
1556 /// expectations set via [`Self::expect_dict_id`] /
1557 /// [`Self::expect_window_descriptor`].
1558 ///
1559 /// Returns the typed error variant on mismatch and leaves
1560 /// `self.state` in a re-resettable shape — a subsequent
1561 /// `reset()` will overwrite `frame_header` from the new source
1562 /// without needing intermediate cleanup.
1563 #[cfg(feature = "lsm")]
1564 fn validate_expectations(
1565 &self,
1566 frame_header: &frame::FrameHeader,
1567 ) -> Result<(), FrameDecoderError> {
1568 self.validate_pinned_fields(
1569 frame_header.dictionary_id(),
1570 frame_header.window_descriptor(),
1571 )
1572 }
1573
1574 /// [`Self::validate_expectations`] for a v0.5-v0.7 header, whose fields
1575 /// are held to the same pins: a v0.7 header carries both, v0.5 and v0.6
1576 /// ones neither.
1577 #[cfg(all(feature = "lsm", feature = "legacy"))]
1578 fn validate_legacy_expectations(
1579 &self,
1580 header: &decoding::legacy::FrameHeader,
1581 header_bytes: &[u8],
1582 ) -> Result<(), FrameDecoderError> {
1583 self.validate_pinned_fields(
1584 header.dictionary_id(),
1585 header.window_descriptor(header_bytes),
1586 )
1587 }
1588
1589 /// Hold a header's dictionary ID and window descriptor, `None` where it
1590 /// carries none, to the pinned expectations.
1591 #[cfg(feature = "lsm")]
1592 fn validate_pinned_fields(
1593 &self,
1594 dictionary_id: Option<u32>,
1595 window_descriptor: Option<u8>,
1596 ) -> Result<(), FrameDecoderError> {
1597 if let Some(expected) = self.expect_dict_id {
1598 let found = dictionary_id;
1599 // `Some(0)` is the "no dictionary expected" sentinel —
1600 // matches a frame whose header omits the optional
1601 // dict_id field (which is reported as `None` by the
1602 // parser). All other values must match exactly.
1603 let matches = match (expected, found) {
1604 (0, None) => true,
1605 (e, Some(f)) => e == f,
1606 _ => false,
1607 };
1608 if !matches {
1609 return Err(FrameDecoderError::UnexpectedDictId {
1610 expected: Some(expected),
1611 found,
1612 });
1613 }
1614 }
1615 if let Some(expected) = self.expect_window_descriptor {
1616 let found = window_descriptor;
1617 if found != Some(expected) {
1618 return Err(FrameDecoderError::UnexpectedWindowDescriptor { expected, found });
1619 }
1620 }
1621 Ok(())
1622 }
1623
1624 /// Enable or disable magicless frame format
1625 /// (`ZSTD_f_zstd1_magicless`). When set to `true`, subsequent
1626 /// [`Self::init`] / [`Self::reset`] calls expect the frame header to begin
1627 /// directly with the frame-header descriptor — no 4-byte magic
1628 /// number prefix. Default false. Must match the encoder's
1629 /// magicless setting; the format is unambiguous only when the
1630 /// caller knows it out-of-band.
1631 ///
1632 /// Note: magicless mode also disables skippable-frame detection.
1633 /// The `0x184D2A50..=0x184D2A5F` skippable-frame magic range is
1634 /// only recognised when the 4-byte magic prefix is consumed, so
1635 /// `decode_all` / `init` / `reset` will treat a skippable frame
1636 /// at the head of a magicless stream as a malformed frame header
1637 /// (bad descriptor / window-size error) instead of skipping it.
1638 /// Mixed-format streams that interleave skippable frames must be
1639 /// pre-split by the caller; `set_magicless(true)` is only safe
1640 /// when the entire stream is known to be magicless zstd frames.
1641 pub fn set_magicless(&mut self, magicless: bool) {
1642 self.magicless = magicless;
1643 }
1644
1645 /// Set the largest window a frame may declare when this decoder holds the
1646 /// window itself: [`Self::init`], [`Self::reset`], [`Self::decode_blocks`]
1647 /// and the streaming paths. Upstream zstd's `ZSTD_DCtx_setMaxWindowSize`
1648 /// (and `ZSTD_d_windowLogMax`, as `1 << log`). The default,
1649 /// [`MAXIMUM_ALLOWED_WINDOW_SIZE`], is upstream's; raise it to decode
1650 /// frames from `zstd --long=28` and wider, lower it to cap the memory
1651 /// untrusted input may claim. Decoding into a caller's buffer
1652 /// ([`Self::decode_all`] and its siblings) holds no window, so only
1653 /// [`MAX_DECODER_WINDOW_SIZE`](crate::decoding::MAX_DECODER_WINDOW_SIZE)
1654 /// bounds it there, as upstream's `ZSTD_decompressDCtx`.
1655 ///
1656 /// A ceiling lowered below the window of a frame already started binds that
1657 /// frame too: its next decode step fails with
1658 /// [`FrameDecoderError::WindowSizeTooBig`] instead of reserving the window,
1659 /// until the ceiling is raised again or the next frame starts.
1660 ///
1661 /// # Errors
1662 ///
1663 /// [`FrameDecoderError::WindowCeilingOutOfRange`] when `size` lies
1664 /// outside 1 KiB (the smallest window a frame declares) ..=
1665 /// [`MAX_DECODER_WINDOW_SIZE`](crate::decoding::MAX_DECODER_WINDOW_SIZE);
1666 /// the ceiling in force is kept.
1667 ///
1668 /// # Examples
1669 ///
1670 /// ```
1671 /// use structured_zstd::decoding::FrameDecoder;
1672 ///
1673 /// let mut decoder = FrameDecoder::new();
1674 /// decoder.set_max_window_size(1 << 30).unwrap();
1675 /// assert_eq!(decoder.max_window_size(), 1 << 30);
1676 /// assert!(decoder.set_max_window_size(1 << 9).is_err());
1677 /// ```
1678 pub fn set_max_window_size(&mut self, size: u64) -> Result<(), FrameDecoderError> {
1679 if !(crate::common::MIN_WINDOW_SIZE..=crate::common::MAX_DECODER_WINDOW_SIZE)
1680 .contains(&size)
1681 {
1682 return Err(FrameDecoderError::WindowCeilingOutOfRange { requested: size });
1683 }
1684 self.max_window_size = size;
1685 // Upstream refuses a parameter change mid-frame outright (`stage_wrong`);
1686 // refusing only the frame the new ceiling forbids keeps a decoder whose
1687 // frame was abandoned free to take a new ceiling for the next one. Such
1688 // a frame stays unfinished in `state`: the C ABI's session reset leaves
1689 // it there, and so does a command-line run whose previous input was
1690 // damaged, and both then set the ceiling for the next frame. The cost of
1691 // honouring the change is one branch per decode call (per `read`, per
1692 // Raw-block refill), never per block or byte.
1693 // The header was admitted when the frame started, so its window reads.
1694 if let Some(state) = self.state.as_mut()
1695 && !state.frame_finished
1696 && let Ok(window) = state.frame_header.window_size()
1697 {
1698 state.window_refused_at = (window > size).then_some(size);
1699 }
1700 #[cfg(feature = "legacy")]
1701 if let Some(stream) = self.legacy_frame_mut() {
1702 stream.set_window_ceiling(size);
1703 }
1704 Ok(())
1705 }
1706
1707 /// Refuse the frame in progress when the ceiling was lowered below its
1708 /// window after it started (see [`Self::set_max_window_size`]). Every
1709 /// entry that decodes a step of the frame asks first.
1710 pub(crate) fn check_window_still_admitted(&self) -> Result<(), FrameDecoderError> {
1711 #[cfg(feature = "legacy")]
1712 if let Some(stream) = self.legacy_frame() {
1713 return match stream.window_refusal() {
1714 Some((requested, limit)) => {
1715 Err(FrameDecoderError::WindowSizeTooBig { requested, limit })
1716 }
1717 None => Ok(()),
1718 };
1719 }
1720 if let Some(state) = self.state.as_ref()
1721 && let Some(limit) = state.window_refused_at
1722 {
1723 return Err(FrameDecoderError::WindowSizeTooBig {
1724 requested: state.frame_header.window_size()?,
1725 limit,
1726 });
1727 }
1728 Ok(())
1729 }
1730
1731 /// The window ceiling in force; see [`Self::set_max_window_size`].
1732 pub fn max_window_size(&self) -> u64 {
1733 self.max_window_size
1734 }
1735
1736 /// Refuse any block that holds more than `size` bytes, compressed or
1737 /// decoded, from the next frame on: for input known to come from an
1738 /// encoder that keeps its blocks that small (upstream's
1739 /// `ZSTD_d_maxBlockSize`, the decoding side of `ZSTD_c_maxBlockSize`).
1740 /// `size` is clamped to 1 KiB ..= 128 KiB; `None` restores the format's
1741 /// 128 KiB. A frame whose window is smaller still holds its blocks to the
1742 /// window. Applies on every path, including decoding into a caller's
1743 /// buffer, as upstream's one-shot honours it too. A v0.5-v0.7 stored
1744 /// block, which its format lets run past 128 KiB, is held to `size` only
1745 /// when it is lowered below that.
1746 ///
1747 /// ```
1748 /// use structured_zstd::decoding::FrameDecoder;
1749 /// use structured_zstd::encoding::{compress_to_vec, CompressionLevel};
1750 ///
1751 /// let frame = compress_to_vec(&[7u8; 4096][..], CompressionLevel::Fastest);
1752 /// let mut out = [0u8; 4096];
1753 /// let mut decoder = FrameDecoder::new();
1754 /// decoder.set_max_block_size(Some(1024));
1755 /// // One RLE block of 4 KiB holds more than 1 KiB.
1756 /// assert!(decoder.decode_all(&frame, &mut out).is_err());
1757 /// decoder.set_max_block_size(None);
1758 /// assert_eq!(decoder.decode_all(&frame, &mut out).unwrap(), 4096);
1759 /// ```
1760 pub fn set_max_block_size(&mut self, size: Option<u32>) {
1761 self.max_block_size = size.map_or(crate::common::MAX_BLOCK_SIZE, |s| {
1762 s.clamp(
1763 crate::common::MIN_MAX_BLOCK_SIZE,
1764 crate::common::MAX_BLOCK_SIZE,
1765 )
1766 }) as usize;
1767 }
1768
1769 /// Whether frame headers are read without the magic number.
1770 pub(crate) fn is_magicless(&self) -> bool {
1771 self.magicless
1772 }
1773
1774 /// Whether every block of the frame is decoded and only its trailing
1775 /// content checksum is still to be read, as
1776 /// [`Self::read_content_checksum`] takes it.
1777 pub fn awaits_checksum(&self) -> bool {
1778 // A v0.7 checksum rides in the end block, read as a block.
1779 #[cfg(feature = "legacy")]
1780 if self.legacy_frame().is_some() {
1781 return false;
1782 }
1783 self.state.as_ref().is_some_and(|state| {
1784 state.frame_finished
1785 && state.frame_header.descriptor.content_checksum_flag()
1786 && state.check_sum.is_none()
1787 })
1788 }
1789
1790 /// How many bytes from the start of `pending` the current frame's next
1791 /// step needs in hand: a block header, a whole block, or the trailing
1792 /// checksum; 0 once the frame wants no more input. Upstream zstd's
1793 /// `ZSTD_nextSrcSizeToDecompress`. A block stating more than the frame's
1794 /// block maximum needs only its header, whose decode then reports it.
1795 ///
1796 /// A caller whose input arrives in pieces holds them until this many are
1797 /// in hand, since [`Self::decode_from_to`] moves only on whole steps.
1798 ///
1799 /// ```
1800 /// use structured_zstd::decoding::FrameDecoder;
1801 /// use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
1802 /// let frame = compress_slice_to_vec(&[7u8; 64], CompressionLevel::Default);
1803 /// let mut source = frame.as_slice();
1804 /// let mut decoder = FrameDecoder::new();
1805 /// decoder.reset(&mut source).unwrap();
1806 /// // One byte of a block header: the header itself is the next step.
1807 /// assert_eq!(decoder.input_needed(&source[..1]), 3);
1808 /// // The rest of the frame holds the whole first block.
1809 /// assert!(decoder.input_needed(source) <= source.len());
1810 /// ```
1811 pub fn input_needed(&self, pending: &[u8]) -> usize {
1812 #[cfg(feature = "legacy")]
1813 if let Some(stream) = self.legacy_frame() {
1814 return stream.input_needed(pending);
1815 }
1816 let Some(state) = self.state.as_ref() else {
1817 return 0;
1818 };
1819 if state.frame_finished {
1820 let checksum_due =
1821 state.frame_header.descriptor.content_checksum_flag() && state.check_sum.is_none();
1822 return if checksum_due { 4 } else { 0 };
1823 }
1824 let Some(&[b0, b1, b2]) = pending.first_chunk::<3>() else {
1825 return 3;
1826 };
1827 // RFC 8878 3.1.1.2: Last_Block (bit 0), Block_Type (bits 1-2),
1828 // Block_Size (bits 3-23). A Reserved block (3.1.1.2.2) is invalid
1829 // from its header alone. Block_Size is held to the block maximum for
1830 // every type: for an RLE block it is the repeat count, while its
1831 // content is one byte.
1832 let header = u32::from_le_bytes([b0, b1, b2, 0]);
1833 let block_type = (header >> 1) & 3;
1834 if block_type == 3 {
1835 return 3;
1836 }
1837 let size = (header >> 3) as usize;
1838 if size > state.block_maximum() {
1839 return 3;
1840 }
1841 // The block header is parsed again by the block decoder. Carrying it
1842 // across would cost a stored field per block for three byte loads.
1843 3 + if block_type == 1 { 1 } else { size }
1844 }
1845
1846 /// Start the Raw block whose 3-byte header is `header`, its content to
1847 /// follow in parts through [`Self::raw_block_push`] as the source delivers
1848 /// it, instead of being gathered whole first. Returns the
1849 /// block's size and whether it is the frame's last; `Ok(None)`, with
1850 /// nothing consumed, when `header` is not a Raw block's, or the frame
1851 /// expects no block (its blocks are done and its checksum is next).
1852 /// `reserve_window` is as for [`Self::decode_available`].
1853 pub(crate) fn start_raw_block(
1854 &mut self,
1855 header: &[u8; 3],
1856 reserve_window: bool,
1857 ) -> Result<Option<(u32, bool)>, FrameDecoderError> {
1858 // A v0.5-v0.7 block is decoded whole, as its step.
1859 #[cfg(feature = "legacy")]
1860 if self.legacy_frame().is_some() {
1861 return Ok(None);
1862 }
1863 // Before the window is reserved below.
1864 self.check_window_still_admitted()?;
1865 let kernel = self.kernel;
1866 #[cfg(feature = "hash")]
1867 let checksum_mode = self.content_checksum;
1868 let state = self
1869 .state
1870 .as_mut()
1871 .ok_or(FrameDecoderError::NotYetInitialized)?;
1872 // RFC 8878 3.1.1.2.2: Block_Type 0 is Raw.
1873 if state.frame_finished || (header[0] >> 1) & 3 != 0 {
1874 return Ok(None);
1875 }
1876 // The checksum mode is applied before any of the frame's output is
1877 // written, as `decode_available` applies it.
1878 #[cfg(feature = "hash")]
1879 {
1880 let compute_hash = checksum_mode != ContentChecksum::None
1881 && state.frame_header.descriptor.content_checksum_flag();
1882 state.decoder_scratch.set_compute_hash(compute_hash);
1883 }
1884 let block_index = state.block_counter as u32;
1885 let block_frame_offset = state.bytes_read_counter as u32;
1886 let mut block_dec = decoding::block_decoder::with_kernel(kernel);
1887 let (block_header, header_size) = block_dec
1888 .read_block_header(&header[..])
1889 .map_err(|source| block_header_decode_error(source, block_index, block_frame_offset))?;
1890 decoding::block_decoder::block_fits_the_maximum(&block_header, state.block_maximum())
1891 .map_err(|source| {
1892 block_body_decode_error(
1893 source,
1894 block_index,
1895 block_frame_offset,
1896 &block_header,
1897 header_size,
1898 )
1899 })?;
1900 state.bytes_read_counter += u64::from(header_size);
1901 if state.block_counter == 0 && (reserve_window || state.frame_header.fcs_declared()) {
1902 state.reserve_decoding_buffer();
1903 }
1904 Ok(Some((
1905 block_header.decompressed_size,
1906 block_header.last_block,
1907 )))
1908 }
1909
1910 /// Append `content`, the next part of the Raw block in progress, to the
1911 /// decode buffer.
1912 pub(crate) fn raw_block_push(&mut self, content: &[u8]) {
1913 let kernel = self.kernel;
1914 let state = self
1915 .state
1916 .as_mut()
1917 .expect("a Raw block is started on an initialised frame");
1918 state.decoder_scratch.buffer_push(content, kernel);
1919 }
1920
1921 /// Whether [`Self::enable_per_block_checksums`] asked for block digests.
1922 #[cfg(all(feature = "lsm", feature = "hash"))]
1923 pub(crate) fn per_block_checksums_enabled(&self) -> bool {
1924 self.per_block_checksums_enabled
1925 }
1926
1927 /// Record the digest of a block decoded outside this decoder's own block
1928 /// loops, in block order.
1929 #[cfg(all(feature = "lsm", feature = "hash"))]
1930 pub(crate) fn record_block_checksum(&mut self, digest: u32) {
1931 self.computed_block_checksums.push(digest);
1932 }
1933
1934 /// The error for a source that ended inside the Raw block of `size` bytes
1935 /// in progress: a body read cut short, as the block decoder reports it.
1936 pub(crate) fn raw_block_cut_short(&self, size: u32, last: bool) -> FrameDecoderError {
1937 let state = self
1938 .state
1939 .as_ref()
1940 .expect("a Raw block is started on an initialised frame");
1941 let header = crate::blocks::block::BlockHeader {
1942 last_block: last,
1943 block_type: crate::blocks::block::BlockType::Raw,
1944 decompressed_size: size,
1945 content_size: size,
1946 };
1947 block_body_decode_error(
1948 DecodeBlockContentError::ReadError {
1949 step: crate::blocks::block::BlockType::Raw,
1950 source: Error::from(crate::io::ErrorKind::UnexpectedEof),
1951 },
1952 state.block_counter as u32,
1953 // The header was counted when the block started.
1954 (state.bytes_read_counter - 3) as u32,
1955 &header,
1956 3,
1957 )
1958 }
1959
1960 /// Close the Raw block of `size` bytes whose content is all appended.
1961 pub(crate) fn finish_raw_block(&mut self, size: u32, last: bool) {
1962 let state = self
1963 .state
1964 .as_mut()
1965 .expect("a Raw block is started on an initialised frame");
1966 state.bytes_read_counter += u64::from(size);
1967 state.block_counter += 1;
1968 if last {
1969 state.frame_finished = true;
1970 }
1971 }
1972
1973 /// Decode one block of the frame begun by [`Self::reset`], its 3-byte
1974 /// header and its content given apart, and hand all of its output to
1975 /// `target` at once (upstream `ZSTD_decompressContinue` on a block). The
1976 /// output also stays in the window for the blocks after it, so `target`
1977 /// may be reused for the next one. Returns the bytes written.
1978 ///
1979 /// A frame whose last block is decoded takes no more blocks: its checksum,
1980 /// when it has one, goes to [`Self::read_content_checksum`], and the next
1981 /// frame starts with [`Self::reset`].
1982 ///
1983 /// # Errors
1984 /// As a block [`Self::decode_from_to`] decodes, plus
1985 /// [`FrameDecoderError::TargetTooSmall`] when `target` cannot take the
1986 /// block's output, and [`FrameDecoderError::NotYetInitialized`] when no
1987 /// RFC 8878 frame is waiting for a block (a v0.5-v0.7 frame decodes
1988 /// through [`Self::decode_blocks`] and its kin). A block refused before it is decoded (a
1989 /// bad header, a block past the maximum, content of the wrong length)
1990 /// leaves the frame as it was, as upstream checks the size before it
1991 /// changes any state; one that fails while decoding, or whose output
1992 /// `target` cannot take, ends the frame.
1993 ///
1994 /// ```
1995 /// use structured_zstd::decoding::FrameDecoder;
1996 /// use structured_zstd::encoding::{compress_to_vec, CompressionLevel};
1997 ///
1998 /// let frame = compress_to_vec(&b"one block at a time"[..], CompressionLevel::Fastest);
1999 /// let mut source = frame.as_slice();
2000 /// let mut decoder = FrameDecoder::new();
2001 /// decoder.reset(&mut source).unwrap();
2002 /// let (header, rest) = source.split_first_chunk::<3>().unwrap();
2003 /// let mut out = [0u8; 64];
2004 /// let n = decoder.decode_block(*header, rest, &mut out).unwrap();
2005 /// assert_eq!(&out[..n], b"one block at a time");
2006 /// assert!(decoder.is_finished());
2007 /// ```
2008 pub fn decode_block(
2009 &mut self,
2010 header: [u8; 3],
2011 content: &[u8],
2012 target: &mut [u8],
2013 ) -> Result<usize, FrameDecoderError> {
2014 self.refuse_during_legacy_frame()?;
2015 self.check_window_still_admitted()?;
2016 let kernel = self.kernel;
2017 #[cfg(feature = "hash")]
2018 let checksum_mode = self.content_checksum;
2019 let state = self
2020 .state
2021 .as_mut()
2022 .filter(|state| !state.frame_finished)
2023 .ok_or(FrameDecoderError::NotYetInitialized)?;
2024 #[cfg(feature = "hash")]
2025 {
2026 let compute_hash = checksum_mode != ContentChecksum::None
2027 && state.frame_header.descriptor.content_checksum_flag();
2028 state.decoder_scratch.set_compute_hash(compute_hash);
2029 }
2030 let block_index = state.block_counter as u32;
2031 let block_frame_offset = state.bytes_read_counter as u32;
2032 let mut block_dec = decoding::block_decoder::with_kernel(kernel);
2033 let (block_header, header_size) = block_dec
2034 .read_block_header(&header[..])
2035 .map_err(|source| block_header_decode_error(source, block_index, block_frame_offset))?;
2036 let body_error = |source| {
2037 block_body_decode_error(
2038 source,
2039 block_index,
2040 block_frame_offset,
2041 &block_header,
2042 header_size,
2043 )
2044 };
2045 decoding::block_decoder::block_fits_the_maximum(&block_header, state.block_maximum())
2046 .map_err(body_error)?;
2047 if content.len() != block_header.content_size as usize {
2048 return Err(body_error(DecodeBlockContentError::ReadError {
2049 step: block_header.block_type,
2050 source: Error::from(crate::io::ErrorKind::UnexpectedEof),
2051 }));
2052 }
2053 state.bytes_read_counter += u64::from(header_size);
2054 if state.block_counter == 0 && state.frame_header.fcs_declared() {
2055 state.reserve_decoding_buffer();
2056 }
2057 // The held dictionary is exposed only while this frame uses it, as in
2058 // `decode_available`.
2059 let dict_ref = if state.using_dict.is_some() {
2060 state.active_dict.as_ref().map(|h| h.as_dict())
2061 } else {
2062 None
2063 };
2064 let before = state.decoder_scratch.buffer_len();
2065 let mut source = content;
2066 let read = match state.decoder_scratch.decode_block_content_from_slice(
2067 &mut block_dec,
2068 &block_header,
2069 &mut source,
2070 dict_ref,
2071 ) {
2072 Ok(read) => read,
2073 Err(source) => {
2074 // The frame's counters and history have moved: no later block
2075 // can be decoded against them.
2076 state.frame_finished = true;
2077 return Err(body_error(source));
2078 }
2079 };
2080 state.bytes_read_counter += read;
2081 state.block_counter += 1;
2082 if block_header.last_block {
2083 state.frame_finished = true;
2084 }
2085 // Nothing drains the buffer while a block decodes, so it grew by the
2086 // block's output exactly.
2087 let produced = state.decoder_scratch.buffer_len() - before;
2088 if produced > target.len() {
2089 state.frame_finished = true;
2090 return Err(FrameDecoderError::TargetTooSmall);
2091 }
2092 state
2093 .decoder_scratch
2094 .buffer_deliver_newest(produced, target);
2095 self.verify_content_size()?;
2096 Ok(produced)
2097 }
2098
2099 /// How many content bytes follow the block header `header` of the current
2100 /// frame (its size, or one byte for an RLE block), and whether it is the
2101 /// frame's last block; the header is checked against the frame's block
2102 /// maximum (upstream `ZSTD_getcBlockSize`). What [`Self::decode_block`]
2103 /// wants alongside it.
2104 ///
2105 /// # Errors
2106 /// A reserved block type or a block past the maximum, as
2107 /// [`Self::decode_block`] reports them; [`FrameDecoderError::NotYetInitialized`]
2108 /// when no frame is waiting for a block.
2109 pub fn block_content_len(&self, header: [u8; 3]) -> Result<(usize, bool), FrameDecoderError> {
2110 self.refuse_during_legacy_frame()?;
2111 let state = self
2112 .state
2113 .as_ref()
2114 .filter(|state| !state.frame_finished)
2115 .ok_or(FrameDecoderError::NotYetInitialized)?;
2116 let block_index = state.block_counter as u32;
2117 let block_frame_offset = state.bytes_read_counter as u32;
2118 let mut block_dec = decoding::block_decoder::with_kernel(self.kernel);
2119 let (block_header, header_size) = block_dec
2120 .read_block_header(&header[..])
2121 .map_err(|source| block_header_decode_error(source, block_index, block_frame_offset))?;
2122 decoding::block_decoder::block_fits_the_maximum(&block_header, state.block_maximum())
2123 .map_err(|source| {
2124 block_body_decode_error(
2125 source,
2126 block_index,
2127 block_frame_offset,
2128 &block_header,
2129 header_size,
2130 )
2131 })?;
2132 Ok((block_header.content_size as usize, block_header.last_block))
2133 }
2134
2135 /// Take the 4-byte content checksum that ends the frame whose blocks
2136 /// [`Self::decode_block`] decoded, and check it in
2137 /// [`ContentChecksum::Verify`] mode.
2138 ///
2139 /// # Errors
2140 /// [`FrameDecoderError::ChecksumMismatch`] when it disagrees with the
2141 /// output; [`FrameDecoderError::NotYetInitialized`] when the frame is not
2142 /// waiting for its checksum.
2143 pub fn read_content_checksum(&mut self, checksum: [u8; 4]) -> Result<(), FrameDecoderError> {
2144 if !self.awaits_checksum() {
2145 return Err(FrameDecoderError::NotYetInitialized);
2146 }
2147 let state = self
2148 .state
2149 .as_mut()
2150 .expect("a frame awaiting its checksum has a state");
2151 state.check_sum = Some(u32::from_le_bytes(checksum));
2152 state.bytes_read_counter += 4;
2153 #[cfg(feature = "hash")]
2154 self.verify_content_checksum()?;
2155 Ok(())
2156 }
2157
2158 /// Start a run of blocks outside any frame (upstream
2159 /// `ZSTD_decompressBegin` before `ZSTD_decompressBlock` and
2160 /// `ZSTD_insertBlock`): each block decodes against the output before it,
2161 /// as far back as the window ceiling, and against `dict` when given.
2162 /// Compressed blocks go to [`Self::decode_block`] with a header saying
2163 /// so, and output the caller already holds to [`Self::insert_block`].
2164 ///
2165 /// ```
2166 /// use structured_zstd::decoding::FrameDecoder;
2167 ///
2168 /// let mut decoder = FrameDecoder::new();
2169 /// decoder.begin_blocks(None).unwrap();
2170 /// decoder.insert_block(b"history the next block can match").unwrap();
2171 /// ```
2172 pub fn begin_blocks(
2173 &mut self,
2174 dict: Option<&DictionaryHandle>,
2175 ) -> Result<(), FrameDecoderError> {
2176 // The largest power-of-two window under the ceiling, which is at
2177 // least 1 KiB.
2178 let window_log = 63 - self.max_window_size.leading_zeros();
2179 let mut header = [0u8; 6];
2180 let mut len = 0;
2181 if !self.magicless {
2182 header[..4].copy_from_slice(&crate::common::MAGIC_NUM.to_le_bytes());
2183 len = 4;
2184 }
2185 // RFC 8878 3.1.1.1.1: no content size, no checksum, no dictionary ID,
2186 // a window descriptor follows; 3.1.1.1.2: exponent `log - 10`, no
2187 // mantissa.
2188 header[len] = 0;
2189 header[len + 1] = ((window_log - 10) << 3) as u8;
2190 let mut source = &header[..len + 2];
2191 match dict {
2192 Some(dict) => self.reset_with_dict_handle(&mut source, dict),
2193 None => self.reset(&mut source),
2194 }
2195 }
2196
2197 /// Take `content`, output the caller already holds, into the history of
2198 /// the run [`Self::begin_blocks`] started (upstream `ZSTD_insertBlock`),
2199 /// for a block the encoder could not compress and the caller stored as is.
2200 ///
2201 /// # Errors
2202 /// [`FrameDecoderError::NotYetInitialized`] when no run of blocks is open.
2203 pub fn insert_block(&mut self, content: &[u8]) -> Result<(), FrameDecoderError> {
2204 self.refuse_during_legacy_frame()?;
2205 let kernel = self.kernel;
2206 let state = self
2207 .state
2208 .as_mut()
2209 .filter(|state| !state.frame_finished)
2210 .ok_or(FrameDecoderError::NotYetInitialized)?;
2211 state.decoder_scratch.buffer_insert(content, kernel);
2212 Ok(())
2213 }
2214
2215 #[cfg(target_has_atomic = "ptr")]
2216 fn shared_dict_exists(&self, dict_id: u32) -> bool {
2217 self.shared_dicts.contains_key(&dict_id)
2218 }
2219
2220 #[cfg(not(target_has_atomic = "ptr"))]
2221 fn shared_dict_exists(&self, _dict_id: u32) -> bool {
2222 false
2223 }
2224
2225 fn validate_registered_dictionary(dict: &Dictionary) -> Result<(), FrameDecoderError> {
2226 use crate::decoding::errors::DictionaryDecodeError as dict_err;
2227
2228 // Registration keys the dictionary by its ID, so a zero one has no
2229 // slot to occupy and no frame could ever select it.
2230 if dict.id == 0 {
2231 return Err(FrameDecoderError::from(dict_err::ZeroDictionaryId));
2232 }
2233 Self::validate_dictionary_content(dict)
2234 }
2235
2236 /// Checks a dictionary can be used at all, whatever route it arrived by.
2237 ///
2238 /// Separate from [`Self::validate_registered_dictionary`] because a
2239 /// dictionary supplied explicitly needs no ID: a raw-content dictionary
2240 /// has no header to carry one, and the frames built with it record none.
2241 fn validate_dictionary_content(dict: &Dictionary) -> Result<(), FrameDecoderError> {
2242 use crate::decoding::errors::DictionaryDecodeError as dict_err;
2243
2244 if let Some(index) = dict.offset_hist.iter().position(|&rep| rep == 0) {
2245 return Err(FrameDecoderError::from(
2246 dict_err::ZeroRepeatOffsetInDictionary { index: index as u8 },
2247 ));
2248 }
2249 Ok(())
2250 }
2251
2252 /// init() will allocate all needed buffers if it is the first time this decoder is used
2253 /// else they just reset these buffers with not further allocations
2254 ///
2255 /// Note that all bytes currently in the decodebuffer from any previous frame will be lost. Collect them with collect()/collect_to_writer()
2256 ///
2257 /// equivalent to reset()
2258 #[inline]
2259 pub fn init(&mut self, source: impl Read) -> Result<(), FrameDecoderError> {
2260 self.reset(source)
2261 }
2262
2263 /// Initialize the decoder for a new frame using a pre-parsed dictionary handle.
2264 ///
2265 /// If the frame header has a dictionary ID, this validates it against
2266 /// `dict.id()` and returns [`FrameDecoderError::DictIdMismatch`] on mismatch.
2267 ///
2268 /// If the header omits the optional dictionary ID, this still applies the
2269 /// provided dictionary handle.
2270 ///
2271 /// # Warning
2272 ///
2273 /// This method always applies `dict` unless the frame header contains a
2274 /// non-matching dictionary ID. Callers must only use this API when they
2275 /// already know the frame was encoded with the provided dictionary, even if
2276 /// the frame header omits the dictionary ID or encodes an explicit
2277 /// dictionary ID of `0`.
2278 ///
2279 /// Passing a dictionary for a frame that was not encoded with it can
2280 /// silently corrupt the decoded output.
2281 pub fn init_with_dict_handle(
2282 &mut self,
2283 source: impl Read,
2284 dict: &DictionaryHandle,
2285 ) -> Result<(), FrameDecoderError> {
2286 self.reset_with_dict_handle(source, dict)
2287 }
2288
2289 /// reset() will allocate all needed buffers if it is the first time this decoder is used
2290 /// else they just reset these buffers with not further allocations
2291 ///
2292 /// Note that all bytes currently in the decodebuffer from any previous frame will be lost. Collect them with collect()/collect_to_writer()
2293 ///
2294 /// equivalent to init()
2295 #[inline]
2296 pub fn reset(&mut self, mut source: impl Read) -> Result<(), FrameDecoderError> {
2297 use FrameDecoderError as err;
2298 // Fresh frame → start with an empty per-block checksum vec so
2299 // the values for the next frame don't carry over from the
2300 // previous one.
2301 #[cfg(all(feature = "lsm", feature = "hash"))]
2302 self.computed_block_checksums.clear();
2303 if !self.read_frame_header_into_state(&mut source, &[])? {
2304 return Ok(());
2305 }
2306 let dict_id = self
2307 .state
2308 .as_ref()
2309 .and_then(|state| state.frame_header.dictionary_id());
2310 // Validate any pinned expectations BEFORE block decode work
2311 // runs. Catches dict_id substitution / window-descriptor
2312 // tampering on inputs already authenticated by an outer
2313 // layer (e.g. AEAD). Returning here leaves `self.state` in
2314 // a re-resettable shape — next `reset()` re-parses the
2315 // frame header without intermediate cleanup.
2316 #[cfg(feature = "lsm")]
2317 if let Some(state) = self.state.as_ref() {
2318 self.validate_expectations(&state.frame_header)?;
2319 }
2320 if let Some(dict_id) = dict_id {
2321 let state = self.state.as_mut().expect("state initialized");
2322 let owned_dicts = &self.owned_dicts;
2323 #[cfg(target_has_atomic = "ptr")]
2324 let shared_dicts = &self.shared_dicts;
2325 let dict = owned_dicts
2326 .get(&dict_id)
2327 .or_else(|| {
2328 #[cfg(target_has_atomic = "ptr")]
2329 {
2330 shared_dicts.get(&dict_id)
2331 }
2332 #[cfg(not(target_has_atomic = "ptr"))]
2333 {
2334 None
2335 }
2336 })
2337 .ok_or(err::DictNotProvided { dict_id })?;
2338 state.decoder_scratch.init_from_dict(dict);
2339 state.set_active_dict(dict);
2340 state.using_dict = Some(dict_id);
2341 }
2342 Ok(())
2343 }
2344
2345 /// Slice-direct equivalent of [`reset`](Self::reset) for the in-memory
2346 /// decode path: parses the frame header straight out of `*input` via
2347 /// [`frame::read_frame_header_from_slice`] (no `Read`-trait `read_exact`
2348 /// per field) and advances `*input` past it, then applies it through the
2349 /// shared parsed-header path. Behaviour — including skippable-frame and
2350 /// truncation errors, dictionary-id resolution, and pinned-expectation
2351 /// validation — is identical to `reset`, except the window ceiling: the
2352 /// frame decodes into the caller's buffer and holds no window of its own,
2353 /// so only [`MAX_DECODER_WINDOW_SIZE`](crate::common::MAX_DECODER_WINDOW_SIZE)
2354 /// bounds it, as upstream `ZSTD_decompressDCtx` takes any window.
2355 pub(crate) fn reset_from_slice(&mut self, input: &mut &[u8]) -> Result<(), FrameDecoderError> {
2356 use FrameDecoderError as err;
2357 #[cfg(all(feature = "lsm", feature = "hash"))]
2358 self.computed_block_checksums.clear();
2359 let magicless = self.magicless;
2360 let max_window = crate::common::MAX_DECODER_WINDOW_SIZE;
2361 let block_limit = self.max_block_size;
2362 // A new header ends any v0.5-v0.7 frame, whether or not it reads.
2363 self.end_legacy_frame();
2364 let (frame_header, header_size) = frame::read_frame_header_from_slice(input, magicless)?;
2365 let dict_id = match &mut self.state {
2366 Some(s) => {
2367 s.reset_with_parsed_header(frame_header, header_size, max_window, block_limit)?;
2368 s.frame_header.dictionary_id()
2369 }
2370 None => {
2371 self.state = Some(FrameDecoderState::new_with_parsed_header(
2372 frame_header,
2373 header_size,
2374 max_window,
2375 block_limit,
2376 )?);
2377 self.armed_dict = None;
2378 self.state
2379 .as_ref()
2380 .and_then(|state| state.frame_header.dictionary_id())
2381 }
2382 };
2383 #[cfg(feature = "lsm")]
2384 if let Some(state) = self.state.as_ref() {
2385 self.validate_expectations(&state.frame_header)?;
2386 }
2387 if let Some(dict_id) = dict_id {
2388 let state = self.state.as_mut().expect("state initialized");
2389 let owned_dicts = &self.owned_dicts;
2390 #[cfg(target_has_atomic = "ptr")]
2391 let shared_dicts = &self.shared_dicts;
2392 let dict = owned_dicts
2393 .get(&dict_id)
2394 .or_else(|| {
2395 #[cfg(target_has_atomic = "ptr")]
2396 {
2397 shared_dicts.get(&dict_id)
2398 }
2399 #[cfg(not(target_has_atomic = "ptr"))]
2400 {
2401 None
2402 }
2403 })
2404 .ok_or(err::DictNotProvided { dict_id })?;
2405 state.decoder_scratch.init_from_dict(dict);
2406 state.set_active_dict(dict);
2407 state.using_dict = Some(dict_id);
2408 }
2409 Ok(())
2410 }
2411
2412 /// Reset this decoder for a new frame using a pre-parsed dictionary handle.
2413 ///
2414 /// If the frame header has a dictionary ID, this validates it against
2415 /// `dict.id()` and returns [`FrameDecoderError::DictIdMismatch`] on mismatch.
2416 ///
2417 /// If the header omits the optional dictionary ID, this still applies the
2418 /// provided dictionary handle.
2419 ///
2420 /// # Warning
2421 ///
2422 /// This method always applies `dict` unless the frame header contains a
2423 /// non-matching dictionary ID. Callers must only use this API when they
2424 /// already know the frame was encoded with the provided dictionary, even if
2425 /// the frame header omits the dictionary ID or encodes an explicit
2426 /// dictionary ID of `0`.
2427 ///
2428 /// Passing a dictionary for a frame that was not encoded with it can
2429 /// silently corrupt the decoded output.
2430 pub fn reset_with_dict_handle(
2431 &mut self,
2432 source: impl Read,
2433 dict: &DictionaryHandle,
2434 ) -> Result<(), FrameDecoderError> {
2435 self.reset_with_dict_handles(source, core::slice::from_ref(dict))
2436 }
2437
2438 /// [`reset_with_dict_handle`](Self::reset_with_dict_handle) with a choice
2439 /// of dictionaries: the frame decodes with the one its header names, and
2440 /// a frame that names none takes the last of `dicts`. A frame naming a
2441 /// dictionary missing from `dicts` fails with
2442 /// [`FrameDecoderError::DictIdMismatch`]. An empty `dicts` decodes
2443 /// without a dictionary, as [`Self::reset`] does.
2444 ///
2445 /// ```
2446 /// use structured_zstd::decoding::{Dictionary, DictionaryHandle, FrameDecoder};
2447 ///
2448 /// let first = DictionaryHandle::from_dictionary(
2449 /// Dictionary::from_raw_content(1, b"first dictionary content".to_vec()).unwrap(),
2450 /// );
2451 /// let second = DictionaryHandle::from_dictionary(
2452 /// Dictionary::from_raw_content(2, b"second dictionary content".to_vec()).unwrap(),
2453 /// );
2454 /// // A frame without a dictionary ID: the last dictionary applies.
2455 /// let frame = structured_zstd::encoding::compress_to_vec(
2456 /// &b"payload"[..],
2457 /// structured_zstd::encoding::CompressionLevel::Fastest,
2458 /// );
2459 /// let mut decoder = FrameDecoder::new();
2460 /// decoder
2461 /// .reset_with_dict_handles(&frame[..], &[first, second])
2462 /// .unwrap();
2463 /// ```
2464 pub fn reset_with_dict_handles(
2465 &mut self,
2466 source: impl Read,
2467 dicts: &[DictionaryHandle],
2468 ) -> Result<(), FrameDecoderError> {
2469 if dicts.is_empty() {
2470 return self.reset(source);
2471 }
2472 if let Some(dict) = self.reset_frame_for_dicts(source, dicts)? {
2473 self.state
2474 .as_mut()
2475 .expect("state populated by reset_frame_for_dicts")
2476 .set_active_dict(dict);
2477 }
2478 Ok(())
2479 }
2480
2481 /// Install `dict` as the dictionary [`Self::reset_with_active_dict`]
2482 /// applies to the next frame, before that frame's header is read, so a
2483 /// stream can be set up without touching its source. The dictionary is
2484 /// validated here. A decoder already holding this dictionary keeps its
2485 /// handle and touches no reference count.
2486 pub(crate) fn arm_dict(&mut self, dict: &DictionaryHandle) -> Result<(), FrameDecoderError> {
2487 Self::validate_dictionary_content(dict.as_dict())?;
2488 match self.state.as_mut() {
2489 Some(state) => state.set_active_dict(dict),
2490 None => self.armed_dict = Some(dict.clone()),
2491 }
2492 Ok(())
2493 }
2494
2495 /// [`reset_with_dict_handle`](Self::reset_with_dict_handle) with the
2496 /// dictionary the decoder already holds: the one [`Self::arm_dict`]
2497 /// installed, or the one the previous frame of a forced-dictionary stream
2498 /// used. The handle is moved out and back rather than passed in, so no
2499 /// reference count is touched.
2500 pub(crate) fn reset_with_active_dict(
2501 &mut self,
2502 source: impl Read,
2503 ) -> Result<(), FrameDecoderError> {
2504 let dict = match self
2505 .state
2506 .as_mut()
2507 .and_then(|state| state.active_dict.take())
2508 {
2509 Some(dict) => dict,
2510 None => self
2511 .armed_dict
2512 .take()
2513 .expect("a forced-dictionary stream's decoder holds its dictionary"),
2514 };
2515 let reset = self
2516 .reset_frame_for_dicts(source, core::slice::from_ref(&dict))
2517 .map(|_| ());
2518 // Back in place whatever the outcome, so a failed header leaves the
2519 // decoder holding its dictionary as before: in the state when there is
2520 // one, armed when the first header failed before a state existed.
2521 match self.state.as_mut() {
2522 Some(state) => state.active_dict = Some(dict),
2523 None => self.armed_dict = Some(dict),
2524 }
2525 reset
2526 }
2527
2528 /// Everything [`reset_with_dict_handle`](Self::reset_with_dict_handle)
2529 /// does except storing the handle: parse the header, pick the dictionary
2530 /// of `dicts` the frame decodes with ([`dictionary_for_frame`]), check the
2531 /// ID, and point the entropy tables at it. `dicts` is not empty. `None`
2532 /// when the frame is a v0.5-v0.7 one, which took its dictionary itself.
2533 fn reset_frame_for_dicts<'d>(
2534 &mut self,
2535 mut source: impl Read,
2536 dicts: &'d [DictionaryHandle],
2537 ) -> Result<Option<&'d DictionaryHandle>, FrameDecoderError> {
2538 use FrameDecoderError as err;
2539 // Fresh frame → drop the previous frame's per-block checksum
2540 // digests so the next decode starts with an empty vec.
2541 // Mirrors the same clear in `reset()`; reset_with_dict_handle
2542 // is a parallel entry point so it needs its own call.
2543 #[cfg(all(feature = "lsm", feature = "hash"))]
2544 self.computed_block_checksums.clear();
2545 for dict in dicts {
2546 Self::validate_dictionary_content(dict.as_dict())?;
2547 }
2548 if !self.read_frame_header_into_state(&mut source, dicts)? {
2549 return Ok(None);
2550 }
2551 // Single source of truth: route through the same
2552 // `validate_expectations` used by `reset()`. Routing through
2553 // the helper keeps the two code paths from drifting (e.g.,
2554 // if expect-semantics or error wiring changes later).
2555 #[cfg(feature = "lsm")]
2556 {
2557 let header = &self
2558 .state
2559 .as_ref()
2560 .expect("state populated by reset_with_format/new_with_format")
2561 .frame_header;
2562 self.validate_expectations(header)?;
2563 }
2564 let state = self
2565 .state
2566 .as_mut()
2567 .expect("state populated by reset_with_format/new_with_format");
2568 let frame_dict_id = state.frame_header.dictionary_id();
2569 let dict = dictionary_for_frame(dicts, frame_dict_id);
2570 if let Some(dict_id) = frame_dict_id
2571 && dict_id != dict.id()
2572 {
2573 return Err(err::DictIdMismatch {
2574 expected: dict_id,
2575 provided: dict.id(),
2576 });
2577 }
2578 state.decoder_scratch.init_from_dict(dict);
2579 state.using_dict = Some(dict.id());
2580 Ok(Some(dict))
2581 }
2582
2583 /// Slice-direct equivalent of [`reset_with_dict_handle`](Self::reset_with_dict_handle)
2584 /// for the in-memory decode path: parses the frame header straight out of
2585 /// `*input` via [`frame::read_frame_header_from_slice`] (no `Read`-trait
2586 /// `read_exact` per field) and applies it through the shared parsed-header
2587 /// path, then attaches `dict`. Behaviour — dictionary-id mismatch, pinned
2588 /// expectations, scratch init — is identical to `reset_with_dict_handle`;
2589 /// only the header read avoids the `io::impls` dispatch.
2590 pub(crate) fn reset_from_slice_with_dict_handle(
2591 &mut self,
2592 input: &mut &[u8],
2593 dict: &DictionaryHandle,
2594 ) -> Result<(), FrameDecoderError> {
2595 self.reset_from_slice_with_dict_handles(input, core::slice::from_ref(dict))
2596 }
2597
2598 /// [`Self::reset_from_slice_with_dict_handle`] with the choice of
2599 /// [`Self::reset_with_dict_handles`]. `dicts` is not empty.
2600 fn reset_from_slice_with_dict_handles(
2601 &mut self,
2602 input: &mut &[u8],
2603 dicts: &[DictionaryHandle],
2604 ) -> Result<(), FrameDecoderError> {
2605 use FrameDecoderError as err;
2606 #[cfg(all(feature = "lsm", feature = "hash"))]
2607 self.computed_block_checksums.clear();
2608 for dict in dicts {
2609 Self::validate_dictionary_content(dict.as_dict())?;
2610 }
2611 let magicless = self.magicless;
2612 // Into the caller's buffer, like `reset_from_slice`: no window held.
2613 let max_window = crate::common::MAX_DECODER_WINDOW_SIZE;
2614 let block_limit = self.max_block_size;
2615 self.end_legacy_frame();
2616 let (frame_header, header_size) = frame::read_frame_header_from_slice(input, magicless)?;
2617 match &mut self.state {
2618 Some(s) => {
2619 s.reset_with_parsed_header(frame_header, header_size, max_window, block_limit)?;
2620 }
2621 None => {
2622 self.state = Some(FrameDecoderState::new_with_parsed_header(
2623 frame_header,
2624 header_size,
2625 max_window,
2626 block_limit,
2627 )?);
2628 self.armed_dict = None;
2629 }
2630 }
2631 #[cfg(feature = "lsm")]
2632 {
2633 let header = &self
2634 .state
2635 .as_ref()
2636 .expect("state populated by reset_with_parsed_header/new_with_parsed_header")
2637 .frame_header;
2638 self.validate_expectations(header)?;
2639 }
2640 let state = self
2641 .state
2642 .as_mut()
2643 .expect("state populated by reset_with_parsed_header/new_with_parsed_header");
2644 let frame_dict_id = state.frame_header.dictionary_id();
2645 let dict = dictionary_for_frame(dicts, frame_dict_id);
2646 if let Some(dict_id) = frame_dict_id
2647 && dict_id != dict.id()
2648 {
2649 return Err(err::DictIdMismatch {
2650 expected: dict_id,
2651 provided: dict.id(),
2652 });
2653 }
2654 state.decoder_scratch.init_from_dict(dict);
2655 state.set_active_dict(dict);
2656 state.using_dict = Some(dict.id());
2657 Ok(())
2658 }
2659
2660 /// Add a dictionary that can be selected dynamically by frame dictionary ID.
2661 ///
2662 /// Returns [`FrameDecoderError::DictAlreadyRegistered`] if the ID is already
2663 /// registered (either as owned or shared).
2664 pub fn add_dict(&mut self, dict: Dictionary) -> Result<(), FrameDecoderError> {
2665 self.add_owned_dict(DictionaryHandle::from_dictionary(dict))
2666 }
2667
2668 /// Parse and add a serialized dictionary blob.
2669 pub fn add_dict_from_bytes(&mut self, raw_dictionary: &[u8]) -> Result<(), FrameDecoderError> {
2670 // Parsed into a handle that keeps the bytes in front of the content:
2671 // a v0.5 or v0.6 frame forced onto it matches into them.
2672 self.add_owned_dict(DictionaryHandle::decode_dict(raw_dictionary)?)
2673 }
2674
2675 /// Register `dict` as one the decoder owns, keyed by its ID.
2676 fn add_owned_dict(&mut self, dict: DictionaryHandle) -> Result<(), FrameDecoderError> {
2677 Self::validate_registered_dictionary(dict.as_dict())?;
2678 let dict_id = dict.id();
2679 if self.owned_dicts.contains_key(&dict_id) || self.shared_dict_exists(dict_id) {
2680 return Err(FrameDecoderError::DictAlreadyRegistered { dict_id });
2681 }
2682 self.owned_dicts.insert(dict_id, dict);
2683 Ok(())
2684 }
2685
2686 /// Add a pre-parsed dictionary handle for reuse across decoders.
2687 ///
2688 /// This API is available on targets with pointer-width atomics
2689 /// (`target_has_atomic = "ptr"`).
2690 ///
2691 /// Returns [`FrameDecoderError::DictAlreadyRegistered`] if the ID is already
2692 /// registered (either as owned or shared).
2693 #[cfg(target_has_atomic = "ptr")]
2694 pub fn add_dict_handle(&mut self, dict: DictionaryHandle) -> Result<(), FrameDecoderError> {
2695 Self::validate_registered_dictionary(dict.as_dict())?;
2696 let dict_id = dict.id();
2697 if self.owned_dicts.contains_key(&dict_id) || self.shared_dicts.contains_key(&dict_id) {
2698 return Err(FrameDecoderError::DictAlreadyRegistered { dict_id });
2699 }
2700 self.shared_dicts.insert(dict_id, dict);
2701 Ok(())
2702 }
2703
2704 pub fn force_dict(&mut self, dict_id: u32) -> Result<(), FrameDecoderError> {
2705 use FrameDecoderError as err;
2706 #[cfg(feature = "legacy")]
2707 if self.legacy_frame().is_some() {
2708 // Taken out while the dictionary is borrowed from the registry.
2709 let mut stream = self.take_legacy_stream();
2710 let forced = self
2711 .registered_dict(dict_id)
2712 .ok_or(err::DictNotProvided { dict_id })
2713 .and_then(|dict| Ok(stream.force_dict(dict)?));
2714 self.legacy = Some(stream);
2715 return forced;
2716 }
2717 let state = self.state.as_mut().ok_or(err::NotYetInitialized)?;
2718 let owned_dicts = &self.owned_dicts;
2719 #[cfg(target_has_atomic = "ptr")]
2720 let shared_dicts = &self.shared_dicts;
2721
2722 let dict = owned_dicts
2723 .get(&dict_id)
2724 .or_else(|| {
2725 #[cfg(target_has_atomic = "ptr")]
2726 {
2727 shared_dicts.get(&dict_id)
2728 }
2729 #[cfg(not(target_has_atomic = "ptr"))]
2730 {
2731 None
2732 }
2733 })
2734 .ok_or(err::DictNotProvided { dict_id })?;
2735 state.decoder_scratch.init_from_dict(dict);
2736 state.set_active_dict(dict);
2737 state.using_dict = Some(dict_id);
2738
2739 Ok(())
2740 }
2741
2742 /// Returns how many bytes the frame contains after decompression
2743 pub fn content_size(&self) -> u64 {
2744 #[cfg(feature = "legacy")]
2745 if let Some(stream) = self.legacy_frame() {
2746 return stream.content_size();
2747 }
2748 match &self.state {
2749 None => 0,
2750 Some(s) => s.frame_header.frame_content_size(),
2751 }
2752 }
2753
2754 /// Returns the checksum that was read from the data. Only available after all bytes have been read. It is the last 4 bytes of a zstd-frame
2755 /// (of a v0.7 frame, the 22 bits its end block holds, read where the
2756 /// checksum is computed).
2757 pub fn get_checksum_from_data(&self) -> Option<u32> {
2758 #[cfg(feature = "legacy")]
2759 if let Some(stream) = self.legacy_frame() {
2760 return stream.stored_checksum();
2761 }
2762 let state = self.state.as_ref()?;
2763
2764 state.check_sum
2765 }
2766
2767 /// Returns the checksum that was calculated while decoding.
2768 /// Only a sensible value after all decoded bytes have been collected/read from the FrameDecoder.
2769 /// Returns `None` when the frame header has `content_checksum_flag = 0`:
2770 /// no hash is computed for such frames (the post-decode XXH64 pass was a
2771 /// 63 % decode-wall hotspot on flag-off frames; skipping it when the
2772 /// frame format declares no trailing digest avoids that wasted work).
2773 #[cfg(feature = "hash")]
2774 pub fn get_calculated_checksum(&self) -> Option<u32> {
2775 #[cfg(feature = "legacy")]
2776 if let Some(stream) = self.legacy_frame() {
2777 return stream.calculated_checksum();
2778 }
2779 let state = self.state.as_ref()?;
2780 // `ContentChecksum::None` skips the XXH64 pass entirely, so there is
2781 // no calculated digest to report.
2782 if self.content_checksum == ContentChecksum::None {
2783 return None;
2784 }
2785 if !state.frame_header.descriptor.content_checksum_flag() {
2786 return None;
2787 }
2788 let cksum_64bit = state.decoder_scratch.hash_finish();
2789 //truncate to lower 32bit because reasons...
2790 Some(cksum_64bit as u32)
2791 }
2792
2793 /// Check that a finished frame produced exactly the `Frame_Content_Size` its
2794 /// header declared (RFC 8878 3.1.1.1.4), returning
2795 /// [`FrameDecoderError::FrameContentSizeMismatch`] otherwise. No-op for a
2796 /// frame that declares no size or has not decoded its last block yet.
2797 ///
2798 /// [`decode_all`](Self::decode_all), [`decode_from_to`](Self::decode_from_to)
2799 /// and the streaming reader call this automatically. Callers driving
2800 /// [`decode_blocks`](Self::decode_blocks) directly invoke it once the
2801 /// frame is finished, as upstream zstd checks at its last block.
2802 ///
2803 /// # Examples
2804 /// ```
2805 /// use structured_zstd::decoding::{BlockDecodingStrategy, FrameDecoder};
2806 /// use structured_zstd::encoding::{compress_to_vec, CompressionLevel};
2807 ///
2808 /// let frame = compress_to_vec(&b"declared and delivered"[..], CompressionLevel::Fastest);
2809 /// let mut source = frame.as_slice();
2810 /// let mut decoder = FrameDecoder::new();
2811 /// decoder.init(&mut source).unwrap();
2812 /// decoder.decode_blocks(&mut source, BlockDecodingStrategy::All).unwrap();
2813 /// assert!(decoder.is_finished());
2814 /// decoder.verify_content_size().unwrap();
2815 /// assert_eq!(decoder.collect().unwrap(), b"declared and delivered");
2816 /// ```
2817 pub fn verify_content_size(&self) -> Result<(), FrameDecoderError> {
2818 // A v0.5-v0.7 frame's declared size is not held against its output:
2819 // `LegacyFrame::finish` says why.
2820 #[cfg(feature = "legacy")]
2821 if self.legacy_frame().is_some() {
2822 return Ok(());
2823 }
2824 let Some(state) = self.state.as_ref() else {
2825 return Ok(());
2826 };
2827 if !state.frame_finished || !state.frame_header.fcs_declared() {
2828 return Ok(());
2829 }
2830 let declared = state.frame_header.frame_content_size();
2831 let produced = state.decoder_scratch.total_output();
2832 if produced != declared {
2833 return Err(FrameDecoderError::FrameContentSizeMismatch { declared, produced });
2834 }
2835 Ok(())
2836 }
2837
2838 /// Compare the frame's stored content checksum against the digest the
2839 /// decoder computed, returning [`FrameDecoderError::ChecksumMismatch`] on
2840 /// disagreement. No-op unless the mode is [`ContentChecksum::Verify`] and
2841 /// the frame carries a trailing checksum.
2842 ///
2843 /// [`decode_all`](Self::decode_all) and the streaming reader call this
2844 /// automatically. Callers driving [`decode_blocks`](Self::decode_blocks)
2845 /// directly invoke it themselves once per frame, after the frame is fully
2846 /// decoded AND fully drained (e.g. via [`collect`](Self::collect)), so both
2847 /// the stored value and the running digest are final.
2848 #[cfg(feature = "hash")]
2849 pub fn verify_content_checksum(&self) -> Result<(), FrameDecoderError> {
2850 if self.content_checksum != ContentChecksum::Verify {
2851 return Ok(());
2852 }
2853 // A v0.7 checksum is checked as its end block is read.
2854 #[cfg(feature = "legacy")]
2855 if self.legacy_frame().is_some() {
2856 return Ok(());
2857 }
2858 let Some(state) = self.state.as_ref() else {
2859 return Ok(());
2860 };
2861 if !state.frame_header.descriptor.content_checksum_flag() {
2862 return Ok(());
2863 }
2864 let Some(expected) = state.check_sum else {
2865 return Ok(());
2866 };
2867 let calculated = state.decoder_scratch.hash_finish() as u32;
2868 if expected != calculated {
2869 return Err(FrameDecoderError::ChecksumMismatch {
2870 expected,
2871 calculated,
2872 });
2873 }
2874 Ok(())
2875 }
2876
2877 /// Bytes the current frame has decoded to so far.
2878 pub(crate) fn decoded_size(&self) -> u64 {
2879 #[cfg(feature = "legacy")]
2880 if let Some(stream) = self.legacy_frame() {
2881 return stream.produced();
2882 }
2883 self.state
2884 .as_ref()
2885 .map_or(0, |state| state.decoder_scratch.total_output())
2886 }
2887
2888 /// Counter for how many bytes have been consumed while decoding the frame
2889 pub fn bytes_read_from_source(&self) -> u64 {
2890 #[cfg(feature = "legacy")]
2891 if let Some(stream) = self.legacy_frame() {
2892 return stream.consumed();
2893 }
2894 let state = match &self.state {
2895 None => return 0,
2896 Some(s) => s,
2897 };
2898 state.bytes_read_counter
2899 }
2900
2901 /// Test-only: number of frames decoded through the single-copy direct
2902 /// path (`run_direct_decode`). Lets cross-module tests assert that a
2903 /// given decode took the decode-in-place path rather than the ring drain.
2904 #[cfg(test)]
2905 pub(crate) fn direct_frames(&self) -> u64 {
2906 self.direct_frames
2907 }
2908
2909 /// Test-only: whether the decode state currently holds an owning dictionary
2910 /// handle (`active_dict`). Every path that arms `Dict`-sourced scratch tables
2911 /// must also install this handle, or a later dict-table read resolves `None`.
2912 #[cfg(test)]
2913 pub(crate) fn active_dict_installed(&self) -> bool {
2914 self.state.as_ref().is_some_and(|s| s.active_dict.is_some())
2915 }
2916
2917 /// The dictionary the current frame decodes with, or the one kept from
2918 /// the last frame started with one. [`Self::workspace_size`] leaves it
2919 /// out because the handle is shared; an owner adding up its footprint
2920 /// counts [`Self::dictionaries`], which also has the handles kept for
2921 /// reuse.
2922 ///
2923 /// # Examples
2924 ///
2925 /// ```
2926 /// use structured_zstd::decoding::FrameDecoder;
2927 ///
2928 /// assert!(FrameDecoder::new().dictionary().is_none());
2929 /// ```
2930 pub fn dictionary(&self) -> Option<&DictionaryHandle> {
2931 #[cfg(feature = "legacy")]
2932 if let Some(stream) = self.legacy_frame() {
2933 return stream.dictionary();
2934 }
2935 self.state.as_ref().and_then(|s| s.active_dict.as_ref())
2936 }
2937
2938 /// Every dictionary handle the decoder holds: [`Self::dictionary`] and
2939 /// those it keeps for reuse from frames of another kind (RFC 8878 or
2940 /// v0.5-v0.7) or armed for a frame not started yet. An owner adding up
2941 /// its footprint passes them to [`DictionaryHandle::exclusive_heap_size`]
2942 /// with its own handles; a handle may appear more than once.
2943 ///
2944 /// # Examples
2945 ///
2946 /// ```
2947 /// use structured_zstd::decoding::FrameDecoder;
2948 ///
2949 /// assert_eq!(FrameDecoder::new().dictionaries().count(), 0);
2950 /// ```
2951 pub fn dictionaries(&self) -> impl Iterator<Item = &DictionaryHandle> + Clone {
2952 let frame = self.state.as_ref().and_then(|s| s.active_dict.as_ref());
2953 #[cfg(feature = "legacy")]
2954 let legacy = self.legacy.as_deref().and_then(|s| s.dictionary());
2955 #[cfg(not(feature = "legacy"))]
2956 let legacy: Option<&DictionaryHandle> = None;
2957 frame.into_iter().chain(legacy).chain(&self.armed_dict)
2958 }
2959
2960 /// Whether the current frames last block has been decoded yet
2961 /// If this returns true you can call the drain* functions to get all content
2962 /// (the read() function will drain automatically if this returns true)
2963 pub fn is_finished(&self) -> bool {
2964 #[cfg(feature = "legacy")]
2965 if let Some(stream) = self.legacy_frame() {
2966 return stream.is_finished();
2967 }
2968 let state = match &self.state {
2969 None => return true,
2970 Some(s) => s,
2971 };
2972 if state.frame_header.descriptor.content_checksum_flag() {
2973 state.frame_finished && state.check_sum.is_some()
2974 } else {
2975 state.frame_finished
2976 }
2977 }
2978
2979 /// Counter for how many blocks have already been decoded
2980 pub fn blocks_decoded(&self) -> usize {
2981 #[cfg(feature = "legacy")]
2982 if let Some(stream) = self.legacy_frame() {
2983 return stream.blocks_decoded();
2984 }
2985 let state = match &self.state {
2986 None => return 0,
2987 Some(s) => s,
2988 };
2989 state.block_counter
2990 }
2991
2992 /// Decodes blocks from a reader. It requires that the framedecoder has been initialized first.
2993 /// The Strategy influences how many blocks will be decoded before the function returns
2994 /// This is important if you want to manage memory consumption carefully. If you don't care
2995 /// about that you can just choose the strategy "All" and have all blocks of the frame decoded into the buffer
2996 pub fn decode_blocks(
2997 &mut self,
2998 mut source: impl Read,
2999 strat: BlockDecodingStrategy,
3000 ) -> Result<bool, FrameDecoderError> {
3001 use FrameDecoderError as err;
3002 self.check_window_still_admitted()?;
3003 #[cfg(feature = "legacy")]
3004 if let Some(stream) = self.legacy_frame_mut() {
3005 let produced_before = stream.produced();
3006 let mut blocks = 0usize;
3007 while !stream.is_finished() {
3008 stream.read_step(&mut source)?;
3009 blocks += 1;
3010 let enough = match strat {
3011 BlockDecodingStrategy::All => false,
3012 BlockDecodingStrategy::UptoBlocks(n) => blocks >= n,
3013 BlockDecodingStrategy::UptoBytes(n) => {
3014 stream.produced() - produced_before >= n as u64
3015 }
3016 };
3017 if enough {
3018 break;
3019 }
3020 }
3021 return Ok(stream.is_finished());
3022 }
3023 // Apply the content-checksum mode to the streaming drain hash before
3024 // any block decodes into the ring. Hash only when a digest is both
3025 // wanted (mode != None) AND present in the frame (content_checksum_flag
3026 // set) — a flag-off frame has nothing to verify or expose, so hashing
3027 // it is wasted work. Mirrors the direct path and get_calculated_checksum.
3028 #[cfg(feature = "hash")]
3029 let checksum_mode = self.content_checksum;
3030 let state = self.state.as_mut().ok_or(err::NotYetInitialized)?;
3031 #[cfg(feature = "hash")]
3032 {
3033 let compute_hash = checksum_mode != ContentChecksum::None
3034 && state.frame_header.descriptor.content_checksum_flag();
3035 state.decoder_scratch.set_compute_hash(compute_hash);
3036 }
3037
3038 // Streaming entry point: pre-reserve the backing buffer to
3039 // the FCS-capped window plus a block so multi-block frames don't pay
3040 // repeated `reserve_amortized` grow steps (128 KiB → 256 KiB → ... →
3041 // window) as blocks accumulate, nor a copy of the window when it
3042 // fills. `decode_all` does the same up front in `decode_all_impl`;
3043 // this mirrors it for callers driving `decode_blocks` directly.
3044 // Idempotent — the backend's `reserve` early-returns when capacity
3045 // is already sufficient.
3046 state.reserve_decoding_buffer();
3047
3048 let mut block_dec = decoding::block_decoder::with_kernel(self.kernel);
3049
3050 let buffer_size_before = state.decoder_scratch.buffer_len();
3051 let block_counter_before = state.block_counter;
3052 loop {
3053 vprintln!("################");
3054 vprintln!("Next Block: {}", state.block_counter);
3055 vprintln!("################");
3056 // Capture the failing-block coordinates BEFORE the header read so
3057 // the error carries where it happened: `bytes_read_counter` is the
3058 // frame-absolute offset of this block's header (not yet advanced),
3059 // `block_counter` its 0-based index. Used by both the header- and
3060 // body-error builders below (block-precise recovery under `lsm`).
3061 let block_index = state.block_counter as u32;
3062 let block_frame_offset = state.bytes_read_counter as u32;
3063 let (block_header, block_header_size) =
3064 block_dec.read_block_header(&mut source).map_err(|source| {
3065 block_header_decode_error(source, block_index, block_frame_offset)
3066 })?;
3067 state.bytes_read_counter += u64::from(block_header_size);
3068
3069 vprintln!();
3070 vprintln!(
3071 "Found {} block with size: {}, which will be of size: {}",
3072 block_header.block_type,
3073 block_header.content_size,
3074 block_header.decompressed_size
3075 );
3076
3077 #[cfg(all(feature = "lsm", feature = "hash"))]
3078 let len_before_block: Option<usize> = if self.per_block_checksums_enabled {
3079 Some(state.decoder_scratch.buffer_len())
3080 } else {
3081 None
3082 };
3083 // Only expose the held dictionary while THIS frame is dict-backed
3084 // (`using_dict` is set per dict-apply, cleared on reset). A reused
3085 // decoder keeps `active_dict` across a no-dict frame for the
3086 // `ptr::eq` reuse-skip, so it must be gated here or a stray
3087 // out-of-window offset on a dictless frame would resolve against the
3088 // stale dictionary content instead of erroring.
3089 let dict_ref = if state.using_dict.is_some() {
3090 state.active_dict.as_ref().map(|h| h.as_dict())
3091 } else {
3092 None
3093 };
3094 let bytes_read_in_block_body = state
3095 .decoder_scratch
3096 .decode_block_content(&mut block_dec, &block_header, &mut source, dict_ref)
3097 .map_err(|source| {
3098 block_body_decode_error(
3099 source,
3100 block_index,
3101 block_frame_offset,
3102 &block_header,
3103 block_header_size,
3104 )
3105 })?;
3106 state.bytes_read_counter += bytes_read_in_block_body;
3107
3108 // Per-block XXH64 (low 32 bits) of the just-decompressed
3109 // bytes. Hashed from `last_n_as_slices` so RingBuffer wrap
3110 // is handled in-place, no extra copy.
3111 #[cfg(all(feature = "lsm", feature = "hash"))]
3112 if let Some(len_before_block) = len_before_block {
3113 let added = state.decoder_scratch.buffer_len() - len_before_block;
3114 let (s1, s2) = state.decoder_scratch.last_n_as_slices(added);
3115 let mut h = twox_hash::XxHash64::with_seed(0);
3116 use core::hash::Hasher;
3117 h.write(s1);
3118 h.write(s2);
3119 self.computed_block_checksums.push(h.finish() as u32);
3120 }
3121
3122 state.block_counter += 1;
3123
3124 vprintln!("Output: {}", state.decoder_scratch.buffer_len());
3125
3126 if block_header.last_block {
3127 state.frame_finished = true;
3128 if state.frame_header.descriptor.content_checksum_flag() {
3129 let mut chksum = [0u8; 4];
3130 source
3131 .read_exact(&mut chksum)
3132 .map_err(err::FailedToReadChecksum)?;
3133 state.bytes_read_counter += 4;
3134 let chksum = u32::from_le_bytes(chksum);
3135 state.check_sum = Some(chksum);
3136 }
3137 break;
3138 }
3139
3140 match strat {
3141 BlockDecodingStrategy::All => { /* keep going */ }
3142 BlockDecodingStrategy::UptoBlocks(n) => {
3143 if state.block_counter - block_counter_before >= n {
3144 break;
3145 }
3146 }
3147 BlockDecodingStrategy::UptoBytes(n) => {
3148 if state.decoder_scratch.buffer_len() - buffer_size_before >= n {
3149 break;
3150 }
3151 }
3152 }
3153 }
3154
3155 Ok(state.frame_finished)
3156 }
3157
3158 /// Decode the inner blocks `[start_block, end_block)` of the current
3159 /// frame and return their decompressed bytes as one contiguous buffer.
3160 ///
3161 /// Serves two consumer needs with one call:
3162 ///
3163 /// - **Range-query performance:** decode only the inner zstd blocks that
3164 /// cover a key range instead of the whole frame. Blocks before
3165 /// `start_block` are decoded into the window (zstd blocks share one
3166 /// window, so a leading block's bytes may be the match source for an
3167 /// in-range block and cannot simply be skipped) but their output is not
3168 /// returned; blocks at or after `end_block` are not decoded at all,
3169 /// which is the trailing-block work saving. Map a decompressed byte
3170 /// offset to a block index with
3171 /// [`FrameEmitInfo::decompressed_byte_range`].
3172 /// - **Best-effort recovery:** if a block decode fails, decoding stops,
3173 /// the clean prefix of in-range output is preserved in
3174 /// [`PartialDecode::data`], and the failure is reported via
3175 /// [`PartialDecode::stopped_at`]. Passing `(0, u32::MAX)` decodes the
3176 /// whole frame, stopping at the first corrupt block (pure recovery).
3177 ///
3178 /// `end_block` is exclusive; pass `u32::MAX` to decode to the end of the
3179 /// frame. Call on a freshly [`reset`](Self::reset) decoder (it decodes
3180 /// from the frame's first block).
3181 ///
3182 /// # Resume (cold incremental / top-up)
3183 ///
3184 /// A plain call drains its in-range output from the match window on return,
3185 /// so two consecutive calls cannot resume one another and growing a decoded
3186 /// extent would mean re-decoding the covering prefix from block 0
3187 /// (`O(extent)` per growth, `O(N²)` for a forward walk). The `resume` /
3188 /// `emit_resume` arguments make a symmetric one-call grow-loop possible:
3189 ///
3190 /// - `emit_resume = true` captures the cross-block carry-over state (entropy
3191 /// tables + repcode history + the next block index / output offset) into
3192 /// [`PartialDecode::resume_state`]. The entropy-table snapshot clone is
3193 /// only paid when this is set. The snapshot is `None` when the decode
3194 /// reaches the frame's last block ([`PartialDecode::frame_finished`]):
3195 /// there is no following block to resume from, so an incremental walk
3196 /// stops on `frame_finished` rather than on a `None` snapshot.
3197 /// - `resume = Some(`[`ResumeInput`]`)` continues from a previously emitted
3198 /// [`ResumeState`] WITHOUT re-decompressing the preceding blocks: the
3199 /// match window is primed from [`ResumeInput::window_prime`] and the
3200 /// entropy/repcode tables are restored from the state, so a `Repeat_Mode`
3201 /// resume block resolves byte-identically to a contiguous decode — even
3202 /// across a dropped (cold) decoder.
3203 ///
3204 /// When `resume` is `Some`, decoding resumes at
3205 /// [`ResumeState::block_index`] and the `start_block` argument is ignored
3206 /// (pass `resume.state.block_index()`); position `source` at that block's
3207 /// compressed frame offset
3208 /// ([`FrameEmitInfo::blocks`]`[block_index].offset_in_frame`). After a
3209 /// resumed call, [`bytes_read_from_source`](Self::bytes_read_from_source)
3210 /// and any `stopped_at` offsets are relative to the repositioned `source`.
3211 ///
3212 /// **Dictionaries:** [`ResumeState`] does NOT carry the dictionary content.
3213 /// For a dictionary frame, attach the dictionary to the resuming decoder the
3214 /// same way as for a fresh decode — [`reset`](Self::reset) with the
3215 /// dictionary registered (or
3216 /// [`reset_with_dict_handle`](Self::reset_with_dict_handle)) BEFORE this
3217 /// call — so dict-sourced matches near the frame start resolve. The caller
3218 /// already holds the dictionary (it supplied it at encode time), so
3219 /// re-supplying it on resume is free; storing it in the snapshot would only
3220 /// duplicate it. The resume guard records the applied dictionary's identity
3221 /// and rejects ([`FrameDecoderError::ResumeFrameMismatch`]) a resume whose
3222 /// active dictionary differs from the one the snapshot was captured under.
3223 ///
3224 /// # Errors
3225 ///
3226 /// Returns [`FrameDecoderError::NotYetInitialized`] if the decoder has not
3227 /// been reset or the current frame is a v0.5-v0.7 one,
3228 /// [`FrameDecoderError::WindowSizeTooBig`] if the window
3229 /// ceiling was lowered below the frame's window after it started (see
3230 /// [`set_max_window_size`](Self::set_max_window_size)),
3231 /// [`FrameDecoderError::InvalidBlockRange`] if the effective
3232 /// start exceeds `end_block`, [`FrameDecoderError::ResumeWindowTooShort`]
3233 /// if `resume`'s `window_prime` is shorter than the match window the resume
3234 /// block can reach back into (`min(window_size, output_offset)`), and
3235 /// [`FrameDecoderError::ResumeFrameMismatch`] if the snapshot was captured
3236 /// from a frame with a different decode shape / dictionary, or (with the
3237 /// `hash` feature) a `window_prime` whose content does not match what was
3238 /// captured — all rejected up front rather than silently mis-resolving
3239 /// matches. A corrupt block is NOT an `Err` here: it is reported via
3240 /// [`PartialDecode::stopped_at`] so the clean prefix survives.
3241 ///
3242 /// [`FrameEmitInfo::decompressed_byte_range`]: crate::encoding::frame_emit_info::FrameEmitInfo::decompressed_byte_range
3243 /// [`FrameEmitInfo::blocks`]: crate::encoding::frame_emit_info::FrameEmitInfo::blocks
3244 #[cfg(feature = "lsm")]
3245 #[cfg_attr(docsrs, doc(cfg(feature = "lsm")))]
3246 pub fn decode_blocks_partial(
3247 &mut self,
3248 mut source: impl Read,
3249 start_block: u32,
3250 end_block: u32,
3251 resume: Option<ResumeInput<'_>>,
3252 emit_resume: bool,
3253 ) -> Result<PartialDecode, FrameDecoderError> {
3254 use FrameDecoderError as err;
3255 // Block indices, resume snapshots and the emit info are RFC 8878
3256 // frame structure; a v0.5-v0.7 frame has none of them.
3257 self.refuse_during_legacy_frame()?;
3258 self.check_window_still_admitted()?;
3259 #[cfg(feature = "hash")]
3260 let checksum_mode = self.content_checksum;
3261 let magicless = self.magicless;
3262 let kernel = self.kernel;
3263 let state = self.state.as_mut().ok_or(err::NotYetInitialized)?;
3264
3265 // Honor the checksum mode before any drain/read can hash: `None` must
3266 // compute no XXH64. `decode_blocks` sets this; the partial path must too,
3267 // or a reused scratch keeps hashing with the default-enabled state.
3268 #[cfg(feature = "hash")]
3269 {
3270 let compute_hash = checksum_mode != ContentChecksum::None
3271 && state.frame_header.descriptor.content_checksum_flag();
3272 state.decoder_scratch.set_compute_hash(compute_hash);
3273 }
3274
3275 // A dictionary that carries no ID cannot be told from another one, and
3276 // the frame key records only that ID: two different raw-content
3277 // dictionaries key alike, so a snapshot would be restored under the
3278 // wrong one — and one taken here could later be. Both directions are
3279 // refused, and refused HERE, before a block is read or a buffer
3280 // reserved. An answer that does not depend on the blocks must not be
3281 // given after decoding them: it would discard the output they produced
3282 // and leave the source and the decoder somewhere the caller cannot
3283 // retry from.
3284 if (resume.is_some() || emit_resume) && state.using_dict == Some(0) {
3285 return Err(err::ResumeUnidentifiedDictionary);
3286 }
3287
3288 // Mirror `decode_blocks`: pre-reserve the backing buffer to the
3289 // FCS-capped window plus a block so multi-block frames don't pay
3290 // repeated grow steps. The RAW frame window stays separately bound —
3291 // the resume logic below bounds match reach by the frame's window
3292 // semantics, not by the (possibly smaller) reservation cap.
3293 let window_size = state.frame_header.window_size().unwrap_or(0) as usize;
3294 state.reserve_decoding_buffer();
3295
3296 // Cold resume: prime the match window + restore entropy/repcode state +
3297 // advance the block cursor BEFORE the loop, so the first in-range block
3298 // resolves its matches and `Repeat_Mode` tables against the caller's
3299 // persisted state instead of re-decoded prefix blocks. The effective
3300 // start is the resume state's block index (the passed `start_block` is
3301 // ignored in resume mode, per the doc).
3302 let effective_start = if let Some(r) = resume {
3303 // Reject a snapshot captured from a different frame shape BEFORE
3304 // touching any decoder state: restoring entropy/repcode tables that
3305 // belong to another frame would silently produce byte-wrong output.
3306 let current_key = FrameKey::from_state(state, magicless);
3307 if current_key != r.state.frame_key {
3308 return Err(err::ResumeFrameMismatch);
3309 }
3310 let output_offset = r.state.output_offset;
3311 // The window the resume block can reach back into is bounded by the
3312 // smaller of the frame's window_size and the bytes produced so far.
3313 let required = core::cmp::min(window_size as u64, output_offset) as usize;
3314 if r.window_prime.len() < required {
3315 return Err(err::ResumeWindowTooShort {
3316 got: r.window_prime.len(),
3317 need: required,
3318 });
3319 }
3320 // Only the most recent `window_size` bytes can ever back a match
3321 // (offset <= window_size by the frame invariant); load just those
3322 // even if the caller handed us a longer prefix, bounding resume
3323 // memory to one window regardless of the skipped prefix's size.
3324 let prime = if r.window_prime.len() > window_size {
3325 &r.window_prime[r.window_prime.len() - window_size..]
3326 } else {
3327 r.window_prime
3328 };
3329 // Content-exact identity: the primed window must hash to what was
3330 // captured at emit. Catches a same-shape-but-different-frame
3331 // snapshot and a wrong/corrupted window_prime (which FrameKey alone
3332 // cannot), before any state is restored. O(window) one-time per
3333 // resume — negligible next to the decode it guards.
3334 #[cfg(feature = "hash")]
3335 if xxh64_of(prime) != r.state.window_hash {
3336 return Err(err::ResumeFrameMismatch);
3337 }
3338 // Validate the effective range (resume mode begins at the resume
3339 // block, ignoring the caller's `start_block`) BEFORE mutating the
3340 // decoder: an inverted `end_block` must fail without priming the
3341 // window / entropy or advancing the cursor, leaving the decoder
3342 // re-resettable rather than in a half-resumed state.
3343 let effective_start = r.state.block_index;
3344 if effective_start > end_block {
3345 return Err(err::InvalidBlockRange {
3346 start_block: effective_start,
3347 end_block,
3348 });
3349 }
3350 state.decoder_scratch.restore_entropy(r.state);
3351 state
3352 .decoder_scratch
3353 .prime_window(prime, output_offset, kernel);
3354 state.block_counter = effective_start as usize;
3355 // The caller repositions `source` to the resume block; report
3356 // consumed bytes relative to that point (reset left this at the
3357 // frame-header size).
3358 state.bytes_read_counter = 0;
3359 effective_start
3360 } else {
3361 // Fresh decode: validate the caller's range (no state to mutate).
3362 if start_block > end_block {
3363 return Err(err::InvalidBlockRange {
3364 start_block,
3365 end_block,
3366 });
3367 }
3368 start_block
3369 };
3370
3371 let mut block_dec = decoding::block_decoder::with_kernel(self.kernel);
3372
3373 // Bytes of prefix-window output that physically precede the first
3374 // in-range block in the buffer. Captured at the prefix → in-range
3375 // transition (after leading blocks were dropped to the window) so we
3376 // can discard exactly those bytes once decoding is done. `None` until
3377 // the first in-range block is reached.
3378 let mut prefix_window_len: Option<usize> = None;
3379 // Exact count of clean in-range decompressed bytes (sum of per-block
3380 // length deltas of the in-range blocks that succeeded). Any partial
3381 // bytes of a failing in-range block are excluded — the fused executor
3382 // rolls the buffer back to the pre-block checkpoint on a sequence
3383 // error, and anything left over is never counted here, so it is not
3384 // drained into `data`.
3385 let mut subset_bytes: u64 = 0;
3386 let mut blocks_decoded: u32 = 0;
3387 let mut stopped_at: Option<(u32, FrameDecoderError)> = None;
3388
3389 loop {
3390 let block_index = state.block_counter as u32;
3391 // Stop before decoding `end_block`: the trailing blocks are never
3392 // touched (the perf win), and the frame's tail is left unread.
3393 if block_index >= end_block || state.frame_finished {
3394 break;
3395 }
3396 let in_range = block_index >= effective_start;
3397 // Snapshot the window length at the prefix → in-range boundary.
3398 if in_range && prefix_window_len.is_none() {
3399 prefix_window_len = Some(state.decoder_scratch.buffer_len());
3400 }
3401
3402 let block_frame_offset = state.bytes_read_counter as u32;
3403 let (block_header, block_header_size) = match block_dec.read_block_header(&mut source) {
3404 Ok(v) => v,
3405 Err(e) => {
3406 stopped_at = Some((
3407 block_index,
3408 block_header_decode_error(e, block_index, block_frame_offset),
3409 ));
3410 break;
3411 }
3412 };
3413 state.bytes_read_counter += u64::from(block_header_size);
3414
3415 let len_before = state.decoder_scratch.buffer_len();
3416 // Only expose the held dictionary while THIS frame is dict-backed
3417 // (`using_dict` is set per dict-apply, cleared on reset). A reused
3418 // decoder keeps `active_dict` across a no-dict frame for the
3419 // `ptr::eq` reuse-skip, so it must be gated here or a stray
3420 // out-of-window offset on a dictless frame would resolve against the
3421 // stale dictionary content instead of erroring.
3422 let dict_ref = if state.using_dict.is_some() {
3423 state.active_dict.as_ref().map(|h| h.as_dict())
3424 } else {
3425 None
3426 };
3427 match state.decoder_scratch.decode_block_content(
3428 &mut block_dec,
3429 &block_header,
3430 &mut source,
3431 dict_ref,
3432 ) {
3433 Ok(body_read) => state.bytes_read_counter += body_read,
3434 Err(e) => {
3435 stopped_at = Some((
3436 block_index,
3437 block_body_decode_error(
3438 e,
3439 block_index,
3440 block_frame_offset,
3441 &block_header,
3442 block_header_size,
3443 ),
3444 ));
3445 break;
3446 }
3447 }
3448 let produced = state.decoder_scratch.buffer_len() - len_before;
3449 // Per-block XXH64 capture, mirroring `decode_blocks`: hash this
3450 // block's just-decoded bytes BEFORE any window drop so the digest
3451 // count stays 1:1 with the blocks decoded on this path too. Covers
3452 // context (out-of-range) blocks as well, matching `decode_blocks`
3453 // which hashes every block it decodes.
3454 #[cfg(all(feature = "lsm", feature = "hash"))]
3455 if self.per_block_checksums_enabled {
3456 use core::hash::Hasher;
3457 let (s1, s2) = state.decoder_scratch.last_n_as_slices(produced);
3458 let mut h = twox_hash::XxHash64::with_seed(0);
3459 h.write(s1);
3460 h.write(s2);
3461 self.computed_block_checksums.push(h.finish() as u32);
3462 }
3463 state.block_counter += 1;
3464 if in_range {
3465 subset_bytes += produced as u64;
3466 blocks_decoded += 1;
3467 }
3468
3469 if block_header.last_block {
3470 state.frame_finished = true;
3471 if state.frame_header.descriptor.content_checksum_flag() {
3472 let mut chksum = [0u8; 4];
3473 match source.read_exact(&mut chksum) {
3474 Ok(()) => {
3475 state.bytes_read_counter += 4;
3476 state.check_sum = Some(u32::from_le_bytes(chksum));
3477 }
3478 // A trailing-checksum read failure does not invalidate
3479 // the decoded bytes; surface it so the caller knows the
3480 // frame tail was truncated, but keep `data`.
3481 Err(e) => {
3482 stopped_at = Some((block_index, err::FailedToReadChecksum(e)));
3483 }
3484 }
3485 }
3486 break;
3487 }
3488
3489 // Leading (out-of-range) block: bound memory to the window. We
3490 // must NOT drop once in-range, or the in-range output we are about
3491 // to return would be discarded.
3492 if !in_range {
3493 state.decoder_scratch.buffer_drop_to_window_size();
3494 }
3495 }
3496
3497 // Emit cross-block carry-over state for a later resume, if requested.
3498 // Captured AFTER the loop (entropy tables / repcode history are final)
3499 // but BEFORE the drain — the drain only touches the visible output, not
3500 // the entropy state or `total_output_counter`. `block_counter` /
3501 // `total_output()` give the resume coordinates: the next block to decode
3502 // and the cumulative decompressed offset before it (clean even after an
3503 // early stop, since a failed block rolls both back to its checkpoint).
3504 // Suppress the snapshot on the terminal block: `block_counter` is then
3505 // one past the last block (EOF), for which there is no next-block source
3506 // position to resume from. A resume needs a real following block.
3507 let resume_state = if emit_resume && !state.frame_finished {
3508 let dict_ref = if state.using_dict.is_some() {
3509 state.active_dict.as_ref().map(|h| h.as_dict())
3510 } else {
3511 None
3512 };
3513 let (fse, huf, offset_hist) = state.decoder_scratch.export_entropy(dict_ref);
3514 Some(ResumeState {
3515 frame_key: FrameKey::from_state(state, magicless),
3516 block_index: state.block_counter as u32,
3517 output_offset: state.decoder_scratch.total_output(),
3518 fse,
3519 huf,
3520 offset_hist,
3521 #[cfg(feature = "hash")]
3522 window_hash: state.decoder_scratch.window_tail_hash(window_size),
3523 })
3524 } else {
3525 None
3526 };
3527
3528 // The visible buffer is now `[prefix window][in-range clean][maybe
3529 // trailing garbage from a failed in-range block]`. Drop the prefix
3530 // window from the front (match resolution is complete, so it is no
3531 // longer needed), then drain exactly the clean in-range byte count.
3532 let w = prefix_window_len.unwrap_or(0);
3533 state.decoder_scratch.buffer_discard_front(w);
3534 let mut data = alloc::vec![0u8; subset_bytes as usize];
3535 state
3536 .decoder_scratch
3537 .buffer_read_all(&mut data)
3538 .map_err(err::FailedToDrainDecodebuffer)?;
3539
3540 // Clear anything still buffered so a later `read()`/`collect()` on this
3541 // decoder cannot surface out-of-range bytes: the leading-block window
3542 // when no in-range block was reached (`prefix_window_len` stayed
3543 // `None`, so `w` was 0), or trailing garbage from a failed in-range
3544 // block. Only the returned `data` is the partial decode's output.
3545 let residual = state.decoder_scratch.buffer_len();
3546 state.decoder_scratch.buffer_discard_front(residual);
3547
3548 Ok(PartialDecode {
3549 data,
3550 start_block: effective_start,
3551 blocks_decoded,
3552 stopped_at,
3553 frame_finished: state.frame_finished,
3554 resume_state,
3555 })
3556 }
3557
3558 /// Collect every decoded byte not collected yet. While the frame is still
3559 /// decoding the window stays inside as history, already handed out; once
3560 /// it is finished ([`Self::is_finished`]) the buffer is emptied.
3561 pub fn collect(&mut self) -> Option<Vec<u8>> {
3562 #[cfg(feature = "legacy")]
3563 if let Some(stream) = self.legacy_frame_mut() {
3564 let out = stream.undelivered_bytes().to_vec();
3565 stream.mark_delivered(out.len());
3566 return Some(out);
3567 }
3568 let finished = self.is_finished();
3569 let state = self.state.as_mut()?;
3570 if finished {
3571 Some(state.decoder_scratch.buffer_drain())
3572 } else {
3573 state.decoder_scratch.buffer_drain_to_window_size()
3574 }
3575 }
3576
3577 /// [`Self::collect`] into `w`.
3578 pub fn collect_to_writer(&mut self, w: impl Write) -> Result<usize, Error> {
3579 #[cfg(feature = "legacy")]
3580 if let Some(stream) = self.legacy_frame_mut() {
3581 // What the writer took before failing counts as handed out.
3582 let (taken, result) =
3583 decoding::decode_buffer::write_all_bytes(w, stream.undelivered_bytes());
3584 stream.mark_delivered(taken);
3585 return result.map(|()| taken);
3586 }
3587 let finished = self.is_finished();
3588 let state = match &mut self.state {
3589 None => return Ok(0),
3590 Some(s) => s,
3591 };
3592 if finished {
3593 state.decoder_scratch.buffer_drain_to_writer(w)
3594 } else {
3595 state.decoder_scratch.buffer_drain_to_window_size_writer(w)
3596 }
3597 }
3598
3599 /// How many decoded bytes are waiting to be collected: every byte decoded
3600 /// and not collected yet, whether or not the frame is finished.
3601 pub fn can_collect(&self) -> usize {
3602 #[cfg(feature = "legacy")]
3603 if let Some(stream) = self.legacy_frame() {
3604 return stream.undelivered();
3605 }
3606 self.state
3607 .as_ref()
3608 .map_or(0, |state| state.decoder_scratch.buffer_undelivered())
3609 }
3610
3611 /// Decodes blocks from the source slice and reads from the decodebuffer into the target slice, one block at a
3612 /// time: output is handed to `target` as each block completes, and no further block is decoded while `target`
3613 /// cannot take what is already decoded. The decode buffer so holds one window plus one block however much input
3614 /// is supplied; call again with the unread input to continue.
3615 /// The source slice may contain only parts of a frame but must contain at least one full block to make progress
3616 ///
3617 /// By all means use decode_blocks if you have a io.Reader available. This is just for compatibility with other decompressors
3618 /// which try to serve an old-style c api
3619 ///
3620 /// Every decoded byte goes to `target` as soon as it has room, the window
3621 /// staying inside as history, so a frame that is not finished yet still
3622 /// hands over everything its blocks so far decode to.
3623 ///
3624 /// Returns (read, written). Both zero means the call made no progress: the
3625 /// source holds no full block and the buffer no undelivered output, so the
3626 /// same input cannot advance. `read == 0` with `written > 0` is progress of
3627 /// the other kind: `target` filled from output already buffered, and the
3628 /// same input decodes further once the caller offers more room.
3629 ///
3630 /// Note that no kind of block can be bigger than 128kb.
3631 /// So to be safe use at least 128*1024 (max block content size) + 3 (block_header size) + 18 (max frame_header size) bytes as your source buffer
3632 ///
3633 /// You may call this function with an empty source after all bytes have been decoded. This is equivalent to just call decoder.read(&mut target)
3634 pub fn decode_from_to(
3635 &mut self,
3636 source: &[u8],
3637 target: &mut [u8],
3638 ) -> Result<(usize, usize), FrameDecoderError> {
3639 let progress = self.decode_available(source, target, false)?;
3640 // Once the frame is fully decoded and drained, its length and running
3641 // digest are final: check the declared size, and the checksum in
3642 // `Verify` mode (no-op otherwise).
3643 if self.is_finished() && self.can_collect() == 0 {
3644 self.verify_content_size()?;
3645 #[cfg(feature = "hash")]
3646 self.verify_content_checksum()?;
3647 }
3648 Ok(progress)
3649 }
3650
3651 /// [`Self::decode_from_to`] without its finish-point checks, for a caller
3652 /// that must not report an error in the same call that delivered bytes
3653 /// (a `Read` adapter) and so runs them itself on a call that delivers
3654 /// none. `reserve_window` sizes the buffer for a frame of unknown size to
3655 /// its window at its first block instead of letting it grow.
3656 pub(crate) fn decode_available(
3657 &mut self,
3658 source: &[u8],
3659 target: &mut [u8],
3660 reserve_window: bool,
3661 ) -> Result<(usize, usize), FrameDecoderError> {
3662 use FrameDecoderError as err;
3663 self.check_window_still_admitted()?;
3664 #[cfg(feature = "legacy")]
3665 if let Some(stream) = self.legacy_frame_mut() {
3666 return Ok(stream.decode(source, target)?);
3667 }
3668 let bytes_read_at_start = match &self.state {
3669 Some(s) => s.bytes_read_counter,
3670 None => 0,
3671 };
3672 // Bytes already handed to `target` by the per-block drain below.
3673 let mut written = 0usize;
3674
3675 if !self.is_finished() || self.state.is_none() {
3676 let mut mt_source = source;
3677
3678 if self.state.is_none() {
3679 self.init(&mut mt_source)?;
3680 // The header began a v0.5-v0.7 frame: its stream decodes the
3681 // rest, the header counted in what was read.
3682 #[cfg(feature = "legacy")]
3683 if let Some(stream) = self.legacy_frame_mut() {
3684 let header = source.len() - mt_source.len();
3685 let (read, written) = stream.decode(mt_source, target)?;
3686 return Ok((header + read, written));
3687 }
3688 }
3689
3690 //pseudo block to scope "state" so we can borrow self again after the block
3691 {
3692 let kernel = self.kernel;
3693 let state = match &mut self.state {
3694 Some(s) => s,
3695 None => panic!("Bug in library"),
3696 };
3697 let mut block_dec = decoding::block_decoder::with_kernel(kernel);
3698
3699 // Honour the content-checksum mode on this hand-rolled decode
3700 // loop (it does not go through `decode_blocks`): hash only when
3701 // a digest is wanted and the frame carries one. `None` skips the
3702 // XXH64 pass; verification happens after the final drain below.
3703 #[cfg(feature = "hash")]
3704 {
3705 let compute_hash = self.content_checksum != ContentChecksum::None
3706 && state.frame_header.descriptor.content_checksum_flag();
3707 state.decoder_scratch.set_compute_hash(compute_hash);
3708 }
3709
3710 if state.frame_header.descriptor.content_checksum_flag()
3711 && state.frame_finished
3712 && state.check_sum.is_none()
3713 {
3714 // The trailing checksum arrived on a separate call (the last
3715 // block finished earlier). Consume it and fall through to the
3716 // shared `self.read` + post-drain verify below — NOT an early
3717 // return — so any output still buffered from a prior
3718 // small-`target` call is flushed on this call too, and the
3719 // checksum is verified through the one shared path.
3720 if mt_source.len() >= 4 {
3721 let chksum = mt_source[..4].try_into().expect("optimized away");
3722 state.bytes_read_counter += 4;
3723 let chksum = u32::from_le_bytes(chksum);
3724 state.check_sum = Some(chksum);
3725 mt_source = &mt_source[4..];
3726 }
3727 }
3728
3729 loop {
3730 // The frame is fully decoded (last block seen, trailer
3731 // consumed above); no more blocks to read. Any leftover
3732 // bytes are not a block header — stop before misreading them.
3733 if state.frame_finished {
3734 break;
3735 }
3736 // Hand every decoded byte to `target` before decoding more,
3737 // and decode no further while `target` cannot take it: the
3738 // buffer then holds one window plus the block being decoded,
3739 // whatever the caller supplies, as upstream
3740 // `ZSTD_decompressStream` flushes each block before the next.
3741 let (read, pending) = state
3742 .decoder_scratch
3743 .buffer_read_reporting_pending(&mut target[written..])
3744 .map_err(err::FailedToDrainDecodebuffer)?;
3745 written += read;
3746 // Stop on a full target as well as on output left behind:
3747 // a drain that empties the buffer into the last of `target`
3748 // leaves nothing pending, and decoding another block then
3749 // consumes input the caller cannot be handed the output of.
3750 // A target of no bytes is not full in that sense: it starts
3751 // at its own length, so the test would fire before any block
3752 // was read and a frame that produces nothing could never
3753 // reach the empty block that ends it. Such a frame decodes
3754 // here; one that does produce bytes buffers its first block
3755 // and stops on the `pending` arm of the next pass.
3756 if pending > 0 || (!target.is_empty() && written == target.len()) {
3757 break;
3758 }
3759 //check if there are enough bytes for the next header
3760 if mt_source.len() < 3 {
3761 break;
3762 }
3763 let block_index = state.block_counter as u32;
3764 let block_frame_offset = state.bytes_read_counter as u32;
3765 let (block_header, block_header_size) = block_dec
3766 .read_block_header(&mut mt_source)
3767 .map_err(|source| {
3768 block_header_decode_error(source, block_index, block_frame_offset)
3769 })?;
3770
3771 // check the needed size for the block before updating counters.
3772 // If not enough bytes are in the source, the header will have to be read again, so act like we never read it in the first place
3773 if mt_source.len() < block_header.content_size as usize {
3774 break;
3775 }
3776 state.bytes_read_counter += u64::from(block_header_size);
3777 // A frame that declares its size gets its buffer in one
3778 // allocation once its first block is in hand, as upstream
3779 // allocates its stream buffer per frame: growing it block by
3780 // block cost a fresh decoder several reallocations, copies
3781 // and page-fault passes per frame. Not on the header alone,
3782 // which would let a header followed by nothing reserve its
3783 // whole declared window. The size is content-capped, so a
3784 // small frame gets a small buffer. A frame of unknown size
3785 // gets its window only when the caller asked for it: the
3786 // `Read` adapter does, as `decode_blocks` always has, since
3787 // growing a large frame by doubling copies it several times;
3788 // the slice API leaves it to grow, so a small unsized frame
3789 // does not pay for a window it never fills.
3790 if state.block_counter == 0
3791 && (reserve_window || state.frame_header.fcs_declared())
3792 {
3793 state.reserve_decoding_buffer();
3794 }
3795
3796 // Only expose the held dictionary while THIS frame is dict-backed
3797 // (`using_dict` is set per dict-apply, cleared on reset). A reused
3798 // decoder keeps `active_dict` across a no-dict frame for the
3799 // `ptr::eq` reuse-skip, so it must be gated here or a stray
3800 // out-of-window offset on a dictless frame would resolve against the
3801 // stale dictionary content instead of erroring.
3802 let dict_ref = if state.using_dict.is_some() {
3803 state.active_dict.as_ref().map(|h| h.as_dict())
3804 } else {
3805 None
3806 };
3807 #[cfg(all(feature = "lsm", feature = "hash"))]
3808 let len_before_block = self
3809 .per_block_checksums_enabled
3810 .then(|| state.decoder_scratch.buffer_len());
3811 // The whole block is in `mt_source` (checked above), so
3812 // it decodes from the slice without a copy.
3813 let bytes_read_in_block_body = state
3814 .decoder_scratch
3815 .decode_block_content_from_slice(
3816 &mut block_dec,
3817 &block_header,
3818 &mut mt_source,
3819 dict_ref,
3820 )
3821 .map_err(|source| {
3822 block_body_decode_error(
3823 source,
3824 block_index,
3825 block_frame_offset,
3826 &block_header,
3827 block_header_size,
3828 )
3829 })?;
3830 state.bytes_read_counter += bytes_read_in_block_body;
3831 // The block's digest, as `decode_blocks` takes it: output
3832 // is drained before a block decodes, never during, so the
3833 // whole block is still in the buffer here.
3834 #[cfg(all(feature = "lsm", feature = "hash"))]
3835 if let Some(len_before_block) = len_before_block {
3836 let added = state.decoder_scratch.buffer_len() - len_before_block;
3837 let (s1, s2) = state.decoder_scratch.last_n_as_slices(added);
3838 let mut h = twox_hash::XxHash64::with_seed(0);
3839 use core::hash::Hasher;
3840 h.write(s1);
3841 h.write(s2);
3842 self.computed_block_checksums.push(h.finish() as u32);
3843 }
3844 state.block_counter += 1;
3845
3846 if block_header.last_block {
3847 state.frame_finished = true;
3848 if state.frame_header.descriptor.content_checksum_flag() {
3849 //if there are enough bytes handle this here. Else the block at the start of this function will handle it at the next call
3850 if mt_source.len() >= 4 {
3851 let chksum = mt_source[..4].try_into().expect("optimized away");
3852 state.bytes_read_counter += 4;
3853 let chksum = u32::from_le_bytes(chksum);
3854 state.check_sum = Some(chksum);
3855 }
3856 }
3857 break;
3858 }
3859 }
3860 }
3861 }
3862
3863 let result_len = written
3864 + self
3865 .read(&mut target[written..])
3866 .map_err(err::FailedToDrainDecodebuffer)?;
3867 let bytes_read_at_end = match &mut self.state {
3868 Some(s) => s.bytes_read_counter,
3869 None => panic!("Bug in library"),
3870 };
3871 let read_len = bytes_read_at_end - bytes_read_at_start;
3872 Ok((read_len as usize, result_len))
3873 }
3874
3875 /// Decode multiple frames into the output slice.
3876 ///
3877 /// `input` must contain an exact number of frames. Skippable frames are allowed and will be
3878 /// skipped during decode.
3879 ///
3880 /// `output` must be large enough to hold the decompressed data. If you don't know
3881 /// how large the output will be, use [`FrameDecoder::decode_blocks`] instead.
3882 ///
3883 /// This calls [`FrameDecoder::init`], and all bytes currently in the decoder will be lost.
3884 ///
3885 /// Returns the number of bytes written to `output`.
3886 pub fn decode_all(
3887 &mut self,
3888 input: &[u8],
3889 output: &mut [u8],
3890 ) -> Result<usize, FrameDecoderError> {
3891 #[cfg(not(feature = "lsm"))]
3892 {
3893 self.decode_all_impl(input, output, |this, src| this.reset_from_slice(src), &[])
3894 }
3895 #[cfg(feature = "lsm")]
3896 {
3897 self.decode_all_impl(
3898 input,
3899 output,
3900 |this, src| this.reset_from_slice(src),
3901 None,
3902 &[],
3903 )
3904 }
3905 }
3906
3907 /// Decode multiple frames into the output slice, invoking `visitor`
3908 /// for every skippable frame encountered before advancing past it.
3909 ///
3910 /// `input` must contain an exact number of frames. Skippable frames
3911 /// (RFC 8878 §3.1.2 magic numbers `0x184D2A50..=0x184D2A5F`) are
3912 /// allowed and will be both visited AND skipped: the visitor gets
3913 /// `(magic_variant, payload)` where `magic_variant` is the low
3914 /// nibble of the magic (`magic - 0x184D2A50`, range `0..=15`) and
3915 /// `payload` is a borrowed slice of the on-wire payload bytes (the
3916 /// skippable frame's `Frame_Size` field worth of data) into
3917 /// `input` — no allocation.
3918 ///
3919 /// The visitor sees skippable frames in stream order; interleaved
3920 /// regular zstd frames continue to decompress into `output` exactly
3921 /// as `decode_all` does.
3922 ///
3923 /// `output` must be large enough to hold the decompressed data.
3924 /// Returns the number of bytes written to `output`.
3925 ///
3926 /// # Example
3927 ///
3928 /// ```ignore
3929 /// use structured_zstd::decoding::FrameDecoder;
3930 ///
3931 /// let mut decoder = FrameDecoder::new();
3932 /// let mut output = vec![0u8; 1024];
3933 /// let mut collected: Vec<(u8, Vec<u8>)> = Vec::new();
3934 /// let n = decoder.decode_all_with_skippable_visitor(
3935 /// input,
3936 /// &mut output,
3937 /// |variant, payload| collected.push((variant, payload.to_vec())),
3938 /// )?;
3939 /// ```
3940 #[cfg(feature = "lsm")]
3941 #[cfg_attr(docsrs, doc(cfg(feature = "lsm")))]
3942 pub fn decode_all_with_skippable_visitor<F>(
3943 &mut self,
3944 input: &[u8],
3945 output: &mut [u8],
3946 mut visitor: F,
3947 ) -> Result<usize, FrameDecoderError>
3948 where
3949 F: FnMut(u8, &[u8]),
3950 {
3951 self.decode_all_impl(
3952 input,
3953 output,
3954 |this, src| this.reset_from_slice(src),
3955 Some(&mut visitor),
3956 &[],
3957 )
3958 }
3959
3960 /// Decode multiple frames into the output slice using a pre-parsed dictionary handle.
3961 ///
3962 /// `input` must contain an exact number of frames. Skippable frames are allowed and will be
3963 /// skipped during decode.
3964 ///
3965 /// `output` must be large enough to hold the decompressed data. If you don't know
3966 /// how large the output will be, use [`FrameDecoder::decode_blocks`] instead.
3967 ///
3968 /// This calls [`FrameDecoder::init_with_dict_handle`], and all bytes currently in the
3969 /// decoder will be lost.
3970 ///
3971 /// # Warning
3972 ///
3973 /// Each decoded frame is initialized with `dict`, even when a frame header
3974 /// omits the optional dictionary ID. Callers must only use this API when
3975 /// they already know the input frames were encoded with the provided
3976 /// dictionary; otherwise decoded output can be silently corrupted.
3977 pub fn decode_all_with_dict_handle(
3978 &mut self,
3979 input: &[u8],
3980 output: &mut [u8],
3981 dict: &DictionaryHandle,
3982 ) -> Result<usize, FrameDecoderError> {
3983 #[cfg(not(feature = "lsm"))]
3984 {
3985 self.decode_all_impl(
3986 input,
3987 output,
3988 |this, src| this.reset_from_slice_with_dict_handle(src, dict),
3989 core::slice::from_ref(dict),
3990 )
3991 }
3992 #[cfg(feature = "lsm")]
3993 {
3994 self.decode_all_impl(
3995 input,
3996 output,
3997 |this, src| this.reset_from_slice_with_dict_handle(src, dict),
3998 None,
3999 core::slice::from_ref(dict),
4000 )
4001 }
4002 }
4003
4004 /// [`decode_all_with_dict_handle`](Self::decode_all_with_dict_handle)
4005 /// with a choice of dictionaries per frame, as
4006 /// [`reset_with_dict_handles`](Self::reset_with_dict_handles) makes it:
4007 /// each frame decodes with the dictionary its header names, or with the
4008 /// last of `dicts` when it names none. An empty `dicts` is
4009 /// [`decode_all`](Self::decode_all).
4010 ///
4011 /// ```
4012 /// use structured_zstd::decoding::{Dictionary, DictionaryHandle, FrameDecoder};
4013 ///
4014 /// let dict = DictionaryHandle::from_dictionary(
4015 /// Dictionary::from_raw_content(7, b"shared dictionary content".to_vec()).unwrap(),
4016 /// );
4017 /// let frame = structured_zstd::encoding::compress_to_vec(
4018 /// &b"payload"[..],
4019 /// structured_zstd::encoding::CompressionLevel::Fastest,
4020 /// );
4021 /// let mut out = [0u8; 7];
4022 /// let mut decoder = FrameDecoder::new();
4023 /// let n = decoder
4024 /// .decode_all_with_dict_handles(&frame, &mut out, &[dict])
4025 /// .unwrap();
4026 /// assert_eq!(&out[..n], b"payload");
4027 /// ```
4028 pub fn decode_all_with_dict_handles(
4029 &mut self,
4030 input: &[u8],
4031 output: &mut [u8],
4032 dicts: &[DictionaryHandle],
4033 ) -> Result<usize, FrameDecoderError> {
4034 if dicts.is_empty() {
4035 return self.decode_all(input, output);
4036 }
4037 #[cfg(not(feature = "lsm"))]
4038 {
4039 self.decode_all_impl(
4040 input,
4041 output,
4042 |this, src| this.reset_from_slice_with_dict_handles(src, dicts),
4043 dicts,
4044 )
4045 }
4046 #[cfg(feature = "lsm")]
4047 {
4048 self.decode_all_impl(
4049 input,
4050 output,
4051 |this, src| this.reset_from_slice_with_dict_handles(src, dicts),
4052 None,
4053 dicts,
4054 )
4055 }
4056 }
4057
4058 /// Whether the decoder sits at the very start of an initialised frame:
4059 /// the header has been read (state populated) but no block has been
4060 /// decoded and the frame is not finished. In this state the wrapped
4061 /// source is positioned exactly after the frame header, so
4062 /// [`Self::decode_current_frame_to_vec`] can decode the rest of the frame
4063 /// straight from the remaining source bytes.
4064 pub(crate) fn is_at_frame_start(&self) -> bool {
4065 // A v0.5-v0.7 frame decodes through its stream, a block at a time.
4066 #[cfg(feature = "legacy")]
4067 if self.legacy_frame().is_some() {
4068 return false;
4069 }
4070 self.state
4071 .as_ref()
4072 .is_some_and(|s| s.block_counter == 0 && !s.frame_finished)
4073 }
4074
4075 /// Decode the CURRENT (already-initialised) frame, APPENDING the
4076 /// decompressed bytes to `output`, and return the number appended.
4077 ///
4078 /// `input` must be the frame's post-header bytes (the wrapped source after
4079 /// `init` consumed the header). Unlike [`Self::decode_all_to_vec`] this
4080 /// neither re-reads a header nor requires the caller to pre-reserve
4081 /// capacity: a frame that declares its content size decodes DIRECTLY into
4082 /// freshly-grown `output` capacity via the single-copy direct path
4083 /// ([`Self::run_direct_decode`]) — bypassing the `Ring`/`FlatBuf` →
4084 /// `read()` drain copy the streaming loop pays — while an unsized frame
4085 /// falls back to the window-bounded ring drain (still one copy, into
4086 /// `output`). Backs [`StreamingDecoder`](crate::decoding::StreamingDecoder)'s
4087 /// `read_to_end` fast path; the caller must ensure
4088 /// [`Self::is_at_frame_start`].
4089 ///
4090 /// # Errors
4091 ///
4092 /// Propagates any [`FrameDecoderError`] from block decode, content-size
4093 /// mismatch, or (in `Verify` mode) checksum validation.
4094 pub(crate) fn decode_current_frame_to_vec(
4095 &mut self,
4096 mut input: &[u8],
4097 output: &mut Vec<u8>,
4098 keep_dictionary: bool,
4099 ) -> Result<usize, FrameDecoderError> {
4100 self.check_window_still_admitted()?;
4101 let start_len = output.len();
4102 // The current frame is already initialised (its header consumed by the
4103 // caller, WITH the dictionary applied if the decoder was constructed
4104 // with one). Decode it, then decode any FOLLOWING concatenated /
4105 // skippable frames in `input` so the whole source is consumed to EOF
4106 // and nothing is dropped (matching `read_to_end` semantics).
4107 self.decode_one_frame_to_vec(&mut input, output)?;
4108 self.decode_concatenated_frames_to_vec(&mut input, output, keep_dictionary)?;
4109 Ok(output.len() - start_len)
4110 }
4111
4112 /// Initialise and decode every frame remaining in `input` (concatenated /
4113 /// skippable), APPENDING to `output`. `input` is advanced as frames are
4114 /// consumed; on return it is empty. With `keep_dictionary` each following
4115 /// frame is initialised with the dictionary the decoder holds
4116 /// ([`Self::reset_with_active_dict`]), so a forced dictionary is preserved
4117 /// even for frames that omit the dictionary id (plain [`Self::init`] would
4118 /// resolve dictionaries by id only). Backs the `read_to_end` fast path (the
4119 /// frames after the current one) and its mid-frame fallback (the frames
4120 /// after the partially-read one).
4121 pub(crate) fn decode_concatenated_frames_to_vec(
4122 &mut self,
4123 input: &mut &[u8],
4124 output: &mut Vec<u8>,
4125 keep_dictionary: bool,
4126 ) -> Result<usize, FrameDecoderError> {
4127 let start_len = output.len();
4128 while !input.is_empty() {
4129 #[cfg(feature = "legacy")]
4130 if !self.magicless && crate::decoding::LegacyVersion::of(input).is_some() {
4131 self.decode_legacy_frame_to_vec(input, output, keep_dictionary)?;
4132 continue;
4133 }
4134 let init_result = if keep_dictionary {
4135 self.reset_with_active_dict(&mut *input)
4136 } else {
4137 self.init(&mut *input)
4138 };
4139 match init_result {
4140 Ok(_) => {}
4141 Err(FrameDecoderError::ReadFrameHeaderError(
4142 crate::decoding::errors::ReadFrameHeaderError::SkipFrame { length, .. },
4143 )) => {
4144 *input = input
4145 .get(length as usize..)
4146 .ok_or(FrameDecoderError::FailedToSkipFrame)?;
4147 continue;
4148 }
4149 Err(e) => return Err(e),
4150 }
4151 self.decode_one_frame_to_vec(&mut *input, output)?;
4152 }
4153 Ok(output.len() - start_len)
4154 }
4155
4156 /// Decode the single CURRENT (already-initialised) frame, APPENDING to
4157 /// `output`. Helper for [`Self::decode_current_frame_to_vec`].
4158 fn decode_one_frame_to_vec(
4159 &mut self,
4160 input: &mut &[u8],
4161 output: &mut Vec<u8>,
4162 ) -> Result<usize, FrameDecoderError> {
4163 let frame_start = output.len();
4164 let (content_size, fcs_declared) = {
4165 let s = self.state.as_ref().expect("frame is initialised");
4166 (
4167 s.frame_header.frame_content_size(),
4168 s.frame_header.fcs_declared(),
4169 )
4170 };
4171 // Direct path: a declared, non-empty content size that FITS in `usize`
4172 // (and whose end offset does not overflow). `usize::try_from` guards the
4173 // 32-bit / oversized-FCS truncation; an unrepresentable size falls
4174 // through to the window-bounded ring drain rather than allocating a
4175 // truncated buffer that would violate `run_direct_decode`'s precondition.
4176 //
4177 // Plausibility gate: the direct path `resize`s `output` to the declared
4178 // size up front, so a tiny/truncated frame declaring a huge (but
4179 // representable) FCS would allocate + zero that whole size before the
4180 // body is validated. zstd's per-block ceiling is MAX_BLOCK_SIZE from as
4181 // little as ~4 input bytes, so the declared size cannot legitimately
4182 // exceed `input.len() * (MAX_BLOCK_SIZE / 4)`. Anything larger falls
4183 // through to the ring drain, which grows only as real bytes are produced
4184 // and errors out cheaply on truncated input. `input` spans the remaining
4185 // source (this frame plus any following ones), so the bound only ever
4186 // over-permits — a legitimate frame is never forced off the direct path.
4187 // saturating_mul is intentional: an overflow means the available input
4188 // is so large that any representable FCS is plausible (cap = "no limit").
4189 const MAX_DECOMPRESSION_RATIO: usize = (crate::common::MAX_BLOCK_SIZE / 4) as usize;
4190 if content_size > 0
4191 && let Ok(cs) = usize::try_from(content_size)
4192 && cs <= input.len().saturating_mul(MAX_DECOMPRESSION_RATIO)
4193 && let Some(frame_end) = frame_start.checked_add(cs)
4194 {
4195 // Reserve exactly the frame's content and decode straight into it
4196 // (single copy, no ring). The direct path writes precisely
4197 // `content_size` bytes (erroring otherwise), so the grown region is
4198 // fully written.
4199 output.resize(frame_end, 0);
4200 // On error, drop the just-grown (zeroed) tail before propagating so
4201 // callers never observe bytes that were never decoded.
4202 let written = match self.run_direct_decode(
4203 &mut *input,
4204 &mut output[frame_start..],
4205 Some(content_size),
4206 ) {
4207 Ok(n) => n,
4208 Err(e) => {
4209 output.truncate(frame_start);
4210 return Err(e);
4211 }
4212 };
4213 output.truncate(frame_start + written);
4214 #[cfg(feature = "hash")]
4215 self.verify_content_checksum()?;
4216 return Ok(written);
4217 }
4218 // The ring-drain fallback below pre-reserves `useful_window_size()`
4219 // (= `window.min(FCS)`), which for a single-segment frame is the
4220 // declared FCS itself — so a truncated single-segment frame lying about
4221 // its size would still allocate the pledged window before the body
4222 // errors, sidestepping the direct-path gate above. Reject such a frame
4223 // up front when its declared (FCS-bearing) window exceeds what the
4224 // available input could plausibly produce. Frames without a declared
4225 // size keep their window-descriptor reservation (already capped at
4226 // `MAXIMUM_ALLOWED_WINDOW_SIZE` at init); a small-window multi-segment
4227 // frame still falls through to the ring drain, which errors cheaply on
4228 // the truncated body.
4229 if fcs_declared
4230 && let Some(state) = self.state.as_ref()
4231 && state.useful_window_size() > input.len().saturating_mul(MAX_DECOMPRESSION_RATIO)
4232 {
4233 return Err(FrameDecoderError::FrameContentSizeMismatch {
4234 declared: content_size,
4235 produced: 0,
4236 });
4237 }
4238 // No declared size, explicit FCS=0, or an unrepresentable FCS: window-
4239 // bounded ring drain, appended directly to `output` via
4240 // `collect_to_writer` (no staging buffer).
4241 loop {
4242 self.decode_blocks(&mut *input, BlockDecodingStrategy::UptoBytes(1024 * 1024))?;
4243 self.collect_to_writer(&mut *output)
4244 .map_err(FrameDecoderError::FailedToDrainDecodebuffer)?;
4245 if self.is_finished() {
4246 // Final flush of the retained window tail.
4247 self.collect_to_writer(&mut *output)
4248 .map_err(FrameDecoderError::FailedToDrainDecodebuffer)?;
4249 break;
4250 }
4251 }
4252 let produced = (output.len() - frame_start) as u64;
4253 // A declared content size MUST match what the body produced — otherwise
4254 // accept the same corrupt frames `decode_all_impl` rejects (e.g. an
4255 // explicit FCS=0 whose body emits bytes). Use `fcs_declared()` so an
4256 // on-wire FCS=0 is validated, while an unknown size is not.
4257 if fcs_declared && produced != content_size {
4258 return Err(FrameDecoderError::FrameContentSizeMismatch {
4259 declared: content_size,
4260 produced,
4261 });
4262 }
4263 #[cfg(feature = "hash")]
4264 self.verify_content_checksum()?;
4265 Ok(produced as usize)
4266 }
4267
4268 /// The dictionary registered under `dict_id`, owned or shared.
4269 #[cfg(feature = "legacy")]
4270 fn registered_dict(&self, dict_id: u32) -> Option<&DictionaryHandle> {
4271 let owned = self.owned_dicts.get(&dict_id);
4272 #[cfg(target_has_atomic = "ptr")]
4273 let owned = owned.or_else(|| self.shared_dicts.get(&dict_id));
4274 owned
4275 }
4276
4277 /// Decode the v0.5-v0.7 frame starting `*input` into the start of
4278 /// `output`, advancing `*input` past it; returns the bytes written.
4279 ///
4280 /// A v0.7 frame naming a dictionary takes the one of `dicts` with that ID
4281 /// (else the last, which then fails the ID check), or with no `dicts` the
4282 /// one registered under it. A frame naming none takes the last of `dicts`.
4283 #[cfg(feature = "legacy")]
4284 fn decode_legacy_frame(
4285 &mut self,
4286 input: &mut &[u8],
4287 output: &mut [u8],
4288 dicts: &[DictionaryHandle],
4289 ) -> Result<usize, FrameDecoderError> {
4290 use crate::decoding::errors::LegacyFrameError;
4291 use crate::decoding::legacy::SliceOutput;
4292 let mut out = SliceOutput {
4293 buf: output,
4294 pos: 0,
4295 };
4296 let mut stream = self.take_legacy_stream();
4297 let decoded = self.decode_whole_legacy_frame(&mut stream, input, dicts, &mut out);
4298 self.legacy = Some(stream);
4299 let consumed = decoded.map_err(|err| match err {
4300 FrameDecoderError::LegacyFrame(LegacyFrameError::OutputTooSmall) => {
4301 FrameDecoderError::TargetTooSmall
4302 }
4303 err => err,
4304 })?;
4305 *input = &input[consumed..];
4306 Ok(out.pos)
4307 }
4308
4309 /// The decoder's v0.5-v0.7 stream, taken out so a whole-buffer decode can
4310 /// run in its workspace while dictionaries are borrowed from the decoder;
4311 /// the caller puts it back.
4312 #[cfg(feature = "legacy")]
4313 fn take_legacy_stream(&mut self) -> alloc::boxed::Box<decoding::legacy::LegacyStream> {
4314 self.legacy
4315 .take()
4316 .unwrap_or_else(|| alloc::boxed::Box::new(decoding::legacy::LegacyStream::new()))
4317 }
4318
4319 /// Decode the v0.5-v0.7 frame starting `input` into `out`, with the
4320 /// dictionary [`Self::legacy_frame_dict`] picks from `dicts`, in
4321 /// `stream`'s workspace so its tables are reused from frame to frame.
4322 /// Returns the bytes the frame took.
4323 #[cfg(feature = "legacy")]
4324 fn decode_whole_legacy_frame<O: decoding::legacy::Output>(
4325 &self,
4326 stream: &mut decoding::legacy::LegacyStream,
4327 input: &[u8],
4328 dicts: &[DictionaryHandle],
4329 out: &mut O,
4330 ) -> Result<usize, FrameDecoderError> {
4331 let header = decoding::legacy::read_header_whole(input)?;
4332 #[cfg(feature = "lsm")]
4333 self.validate_legacy_expectations(&header, input)?;
4334 // No window ceiling here: the frame decodes into the caller's output
4335 // and holds no window of its own, as for an RFC 8878 frame on this
4336 // path (see `set_max_window_size`); upstream applies the ceiling only
4337 // in `ZSTD_decompressStream` (`zstd_decompress.c:2229-2231`).
4338 let dict = self.legacy_frame_dict(header.dict_id, dicts)?;
4339 Ok(stream.decode_whole(input, dict, self.legacy_frame_options(), out)?)
4340 }
4341
4342 /// Decode the v0.5-v0.7 frame starting `*input`, appending to `output`
4343 /// and advancing `*input` past it. With `keep_dictionary` it takes the
4344 /// dictionary the decoder holds, else the registry's.
4345 #[cfg(feature = "legacy")]
4346 fn decode_legacy_frame_to_vec(
4347 &mut self,
4348 input: &mut &[u8],
4349 output: &mut Vec<u8>,
4350 keep_dictionary: bool,
4351 ) -> Result<(), FrameDecoderError> {
4352 use crate::decoding::legacy::VecOutput;
4353 let mut stream = self.take_legacy_stream();
4354 let held = if keep_dictionary {
4355 self.state
4356 .as_ref()
4357 .and_then(|state| state.active_dict.as_ref())
4358 .or(self.armed_dict.as_ref())
4359 } else {
4360 None
4361 };
4362 let dicts = held.map_or(&[][..], core::slice::from_ref);
4363 let start = output.len();
4364 let mut out = VecOutput { buf: output, start };
4365 let decoded = self.decode_whole_legacy_frame(&mut stream, input, dicts, &mut out);
4366 self.legacy = Some(stream);
4367 match decoded {
4368 Ok(consumed) => {
4369 *input = &input[consumed..];
4370 Ok(())
4371 }
4372 Err(err) => {
4373 // No byte of a frame that failed is handed out.
4374 output.truncate(start);
4375 Err(err)
4376 }
4377 }
4378 }
4379
4380 /// The dictionary a v0.5-v0.7 frame naming `dict_id` (0 for none)
4381 /// decodes with: the one of `dicts` with that ID, else the last, which
4382 /// then fails the frame's ID check; with no `dicts`, the one registered
4383 /// under the ID.
4384 #[cfg(feature = "legacy")]
4385 fn legacy_frame_dict<'s>(
4386 &'s self,
4387 dict_id: u32,
4388 dicts: &'s [DictionaryHandle],
4389 ) -> Result<Option<&'s DictionaryHandle>, FrameDecoderError> {
4390 let named = (dict_id != 0).then_some(dict_id);
4391 Ok(match (named, dicts.is_empty()) {
4392 (_, false) => Some(dictionary_for_frame(dicts, named)),
4393 (Some(dict_id), true) => Some(
4394 self.registered_dict(dict_id)
4395 .ok_or(FrameDecoderError::DictNotProvided { dict_id })?,
4396 ),
4397 (None, true) => None,
4398 })
4399 }
4400
4401 /// Default-feature decode_all_impl: no visitor parameter so the
4402 /// no-lsm build's call surface and codegen are byte-identical to
4403 /// the pre-#172 implementation. Compiles only when `lsm` is OFF.
4404 #[cfg(not(feature = "lsm"))]
4405 fn decode_all_impl(
4406 &mut self,
4407 mut input: &[u8],
4408 mut output: &mut [u8],
4409 mut init_frame: impl FnMut(&mut Self, &mut &[u8]) -> Result<(), FrameDecoderError>,
4410 // The dictionaries a v0.5-v0.7 frame chooses from.
4411 #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle],
4412 ) -> Result<usize, FrameDecoderError> {
4413 let mut total_bytes_written = 0;
4414 while !input.is_empty() {
4415 #[cfg(feature = "legacy")]
4416 if !self.magicless && crate::decoding::LegacyVersion::of(input).is_some() {
4417 let written = self.decode_legacy_frame(&mut input, output, dicts)?;
4418 output = &mut output[written..];
4419 total_bytes_written += written;
4420 continue;
4421 }
4422 match init_frame(self, &mut input) {
4423 Ok(_) => {}
4424 Err(FrameDecoderError::ReadFrameHeaderError(
4425 crate::decoding::errors::ReadFrameHeaderError::SkipFrame { length, .. },
4426 )) => {
4427 input = input
4428 .get(length as usize..)
4429 .ok_or(FrameDecoderError::FailedToSkipFrame)?;
4430 continue;
4431 }
4432 Err(e) => return Err(e),
4433 };
4434 // Per-frame direct-path dispatch. Now safe to route the
4435 // public `decode_all` here because
4436 // `UserSliceBackend::exec_sequence_inline` returns
4437 // `Result<(), ExecuteSequencesError>` instead of
4438 // panicking on capacity overflow; the error propagates
4439 // up as `FrameDecoderError`. Eligibility (FCS > 0,
4440 // remaining `output` slice holds the declared content)
4441 // puts the frame on the fast path that bypasses the
4442 // FlatBuf/Ring -> `read()` drain copy. Ineligible frames
4443 // (no FCS, output too small) fall through to the legacy
4444 // `decode_blocks` + `read` drain loop below. Dictionary
4445 // frames are eligible: `run_direct_decode` hands the
4446 // shared dict handle to its buffer, and beyond-prefix
4447 // offsets resolve through `repeat_from_dict`.
4448 let (content_size, fcs_declared) = {
4449 let state_ref = self.state.as_ref().expect("init populated state");
4450 (
4451 state_ref.frame_header.frame_content_size(),
4452 state_ref.frame_header.fcs_declared(),
4453 )
4454 };
4455 // Direct decode requires only that the caller slice holds the
4456 // declared content; the inline sequence-exec path no longer
4457 // needs `WILDCOPY_OVERLENGTH` trailing slack because the
4458 // trailing sequence(s) take the bounded (non-overshooting)
4459 // copy in `UserSliceBackend::exec_sequence_bounded`. This is
4460 // the universal "decode into an FCS-sized buffer" case (a
4461 // caller sizing `output` to exactly `frame_content_size`),
4462 // so dropping the slack requirement halves its peak alloc.
4463 //
4464 // Per-block checksums collected inside `run_direct_decode`
4465 // post-loop (over recorded (start, end) ranges of `output`)
4466 // so the direct path stays eligible AND keeps the
4467 // window-size cap (`drop_to_window_size`) between blocks
4468 // that the spec relies on for `offset <= window_size`
4469 // validation. Path choice no longer alters checksum
4470 // semantics.
4471 // A frame that declares no size decodes straight into the
4472 // caller's slice too, the slice being its limit, as upstream
4473 // `ZSTD_decompressDCtx` decodes into `dst`. The drain path
4474 // reserved the frame's whole declared window for it, which for a
4475 // streamed producer's small frame is megabytes for kilobytes.
4476 let declared_size = fcs_declared.then_some(content_size);
4477 // A frame declaring more than the slice holds cannot fit it: the
4478 // declared size is checked once the frame is decoded. Stopping
4479 // here is what keeps this path free of a window-sized buffer, as
4480 // upstream `ZSTD_decompressDCtx` writes only into `dst`.
4481 if declared_size.is_some_and(|declared| declared > output.len() as u64) {
4482 return Err(FrameDecoderError::TargetTooSmall);
4483 }
4484 let direct_eligible = declared_size != Some(0);
4485 if direct_eligible {
4486 let written = self.run_direct_decode(&mut input, output, declared_size)?;
4487 output = &mut output[written..];
4488 total_bytes_written += written;
4489 // Per-frame content-checksum verification (no-op unless the
4490 // mode is `Verify` and the frame carries a checksum).
4491 #[cfg(feature = "hash")]
4492 self.verify_content_checksum()?;
4493 continue;
4494 }
4495 // Non-direct fallback: pre-reserve the backing buffer to
4496 // `window_size` in a single allocation before block decode
4497 // starts, so multi-segment frames don't pay repeated
4498 // `reserve_amortized` grow steps as blocks accumulate (each
4499 // block only reserves MAX_BLOCK_SIZE = 128 KiB, so a window
4500 // > 128 KiB otherwise grows through several intermediate
4501 // sizes with `alloc_zeroed + memcpy` each time).
4502 if let Some(state) = self.state.as_mut() {
4503 // FCS-capped via `decoding_buffer_size` — the same cap
4504 // `decode_blocks` applies, so its per-iteration reserve in
4505 // the loop below cannot grow the buffer back to the raw
4506 // frame window.
4507 state.reserve_decoding_buffer();
4508 }
4509 let frame_start_total = total_bytes_written;
4510 loop {
4511 self.decode_blocks(&mut input, BlockDecodingStrategy::UptoBytes(1024 * 1024))?;
4512 let bytes_written = self
4513 .read(output)
4514 .map_err(FrameDecoderError::FailedToDrainDecodebuffer)?;
4515 output = &mut output[bytes_written..];
4516 total_bytes_written += bytes_written;
4517 if self.can_collect() != 0 {
4518 return Err(FrameDecoderError::TargetTooSmall);
4519 }
4520 if self.is_finished() {
4521 break;
4522 }
4523 }
4524 // Per-frame FCS validation on the legacy fallback path.
4525 // Use `fcs_declared()` (NOT `content_size > 0`) so an
4526 // empty frame with explicit FCS=0 on the wire still gets
4527 // validated.
4528 if fcs_declared {
4529 let produced = (total_bytes_written - frame_start_total) as u64;
4530 if produced != content_size {
4531 return Err(FrameDecoderError::FrameContentSizeMismatch {
4532 declared: content_size,
4533 produced,
4534 });
4535 }
4536 }
4537 // Per-frame content-checksum verification on the drain path: the
4538 // frame is fully decoded and drained here (is_finished + nothing
4539 // left to collect), so the running digest and stored value are
4540 // final. No-op unless the mode is `Verify`.
4541 #[cfg(feature = "hash")]
4542 self.verify_content_checksum()?;
4543 }
4544
4545 Ok(total_bytes_written)
4546 }
4547
4548 /// `lsm`-feature decode_all_impl: adds the optional skippable
4549 /// visitor parameter consumed by
4550 /// [`Self::decode_all_with_skippable_visitor`]. Mirrors the no-lsm
4551 /// variant including the direct-path dispatch + FCS-validation
4552 /// rationale comments, so the two functions stay in sync; the only
4553 /// behavioral difference is the SkipFrame arm, which uses
4554 /// `split_at(length)` (single bounds check) instead of two
4555 /// separate `get(..length)` / `get(length..)` slices and invokes
4556 /// the visitor (when `Some`) on the borrowed payload before
4557 /// advancing past it.
4558 #[cfg(feature = "lsm")]
4559 #[allow(clippy::type_complexity)]
4560 fn decode_all_impl(
4561 &mut self,
4562 mut input: &[u8],
4563 mut output: &mut [u8],
4564 mut init_frame: impl FnMut(&mut Self, &mut &[u8]) -> Result<(), FrameDecoderError>,
4565 mut skippable_visitor: Option<&mut dyn FnMut(u8, &[u8])>,
4566 // The dictionaries a v0.5-v0.7 frame chooses from.
4567 #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle],
4568 ) -> Result<usize, FrameDecoderError> {
4569 let mut total_bytes_written = 0;
4570 while !input.is_empty() {
4571 #[cfg(feature = "legacy")]
4572 if !self.magicless && crate::decoding::LegacyVersion::of(input).is_some() {
4573 let written = self.decode_legacy_frame(&mut input, output, dicts)?;
4574 output = &mut output[written..];
4575 total_bytes_written += written;
4576 continue;
4577 }
4578 match init_frame(self, &mut input) {
4579 Ok(_) => {}
4580 Err(FrameDecoderError::ReadFrameHeaderError(
4581 crate::decoding::errors::ReadFrameHeaderError::SkipFrame {
4582 magic_number,
4583 length,
4584 },
4585 )) => {
4586 let length = length as usize;
4587 // Visitor sees the payload slice BEFORE we advance
4588 // past it. Borrowed slice — no allocation. The
4589 // variant is the low nibble of the magic number
4590 // (RFC 8878 §3.1.2). `read_frame_header` only emits
4591 // SkipFrame for magic in 0x184D2A50..=0x184D2A5F, so
4592 // the subtraction fits in 0..=15.
4593 if input.len() < length {
4594 return Err(FrameDecoderError::FailedToSkipFrame);
4595 }
4596 let (payload, rest) = input.split_at(length);
4597 if let Some(visitor) = skippable_visitor.as_mut() {
4598 let variant = (magic_number - 0x184D2A50) as u8;
4599 visitor(variant, payload);
4600 }
4601 input = rest;
4602 continue;
4603 }
4604 Err(e) => return Err(e),
4605 };
4606 // Per-frame direct-path dispatch. Now safe to route the
4607 // public `decode_all` here because
4608 // `UserSliceBackend::exec_sequence_inline` returns
4609 // `Result<(), ExecuteSequencesError>` instead of
4610 // panicking on capacity overflow; the error propagates
4611 // up as `FrameDecoderError`. Eligibility (FCS > 0,
4612 // remaining `output` slice holds the declared content)
4613 // puts the frame on the fast path that bypasses the
4614 // FlatBuf/Ring -> `read()` drain copy. Ineligible frames
4615 // (no FCS, output too small) fall through to the legacy
4616 // `decode_blocks` + `read` drain loop below. Dictionary
4617 // frames are eligible (see the no-lsm path above).
4618 let (content_size, fcs_declared) = {
4619 let state_ref = self.state.as_ref().expect("init populated state");
4620 (
4621 state_ref.frame_header.frame_content_size(),
4622 state_ref.frame_header.fcs_declared(),
4623 )
4624 };
4625 // Only `cap >= frame_content_size` needed; the trailing
4626 // sequence(s) take the bounded copy in
4627 // `UserSliceBackend::exec_sequence_bounded`, so no
4628 // `WILDCOPY_OVERLENGTH` trailing slack is required (see the
4629 // no-lsm path above).
4630 // A frame that declares no size decodes straight into the
4631 // caller's slice too, the slice being its limit, as upstream
4632 // `ZSTD_decompressDCtx` decodes into `dst`. The drain path
4633 // reserved the frame's whole declared window for it, which for a
4634 // streamed producer's small frame is megabytes for kilobytes.
4635 let declared_size = fcs_declared.then_some(content_size);
4636 // Refused before any buffer is taken (see the no-lsm path above).
4637 if declared_size.is_some_and(|declared| declared > output.len() as u64) {
4638 return Err(FrameDecoderError::TargetTooSmall);
4639 }
4640 let direct_eligible = declared_size != Some(0);
4641 if direct_eligible {
4642 let written = self.run_direct_decode(&mut input, output, declared_size)?;
4643 output = &mut output[written..];
4644 total_bytes_written += written;
4645 // Per-frame content-checksum verification (no-op unless the
4646 // mode is `Verify` and the frame carries a checksum).
4647 #[cfg(feature = "hash")]
4648 self.verify_content_checksum()?;
4649 continue;
4650 }
4651 // Non-direct fallback: pre-reserve the backing buffer to
4652 // `window_size` once so the per-block growth cycle is
4653 // skipped (see same comment on the no-lsm path above).
4654 if let Some(state) = self.state.as_mut() {
4655 // FCS-capped via `decoding_buffer_size` — the same cap
4656 // `decode_blocks` applies, so its per-iteration reserve in
4657 // the loop below cannot grow the buffer back to the raw
4658 // frame window.
4659 state.reserve_decoding_buffer();
4660 }
4661 let frame_start_total = total_bytes_written;
4662 loop {
4663 self.decode_blocks(&mut input, BlockDecodingStrategy::UptoBytes(1024 * 1024))?;
4664 let bytes_written = self
4665 .read(output)
4666 .map_err(FrameDecoderError::FailedToDrainDecodebuffer)?;
4667 output = &mut output[bytes_written..];
4668 total_bytes_written += bytes_written;
4669 if self.can_collect() != 0 {
4670 return Err(FrameDecoderError::TargetTooSmall);
4671 }
4672 if self.is_finished() {
4673 break;
4674 }
4675 }
4676 // Per-frame FCS validation on the legacy fallback path.
4677 // Use `fcs_declared()` (NOT `content_size > 0`) so an
4678 // empty frame with explicit FCS=0 on the wire still gets
4679 // validated.
4680 if fcs_declared {
4681 let produced = (total_bytes_written - frame_start_total) as u64;
4682 if produced != content_size {
4683 return Err(FrameDecoderError::FrameContentSizeMismatch {
4684 declared: content_size,
4685 produced,
4686 });
4687 }
4688 }
4689 // Per-frame content-checksum verification on the drain path: the
4690 // frame is fully decoded and drained here (is_finished + nothing
4691 // left to collect), so the running digest and stored value are
4692 // final. No-op unless the mode is `Verify`.
4693 #[cfg(feature = "hash")]
4694 self.verify_content_checksum()?;
4695 }
4696
4697 Ok(total_bytes_written)
4698 }
4699
4700 /// Decode multiple frames into the output slice using a serialized dictionary.
4701 ///
4702 /// # Warning
4703 ///
4704 /// Each decoded frame is initialized with the parsed dictionary, even when a
4705 /// frame header omits the optional dictionary ID. Callers must only use this
4706 /// API when they already know the input frames were encoded with that
4707 /// dictionary; otherwise decoded output can be silently corrupted.
4708 pub fn decode_all_with_dict_bytes(
4709 &mut self,
4710 input: &[u8],
4711 output: &mut [u8],
4712 raw_dictionary: &[u8],
4713 ) -> Result<usize, FrameDecoderError> {
4714 let dict = DictionaryHandle::decode_dict(raw_dictionary)?;
4715 self.decode_all_with_dict_handle(input, output, &dict)
4716 }
4717
4718 /// Decode multiple frames into the extra capacity of the output vector.
4719 ///
4720 /// `input` must contain an exact number of frames.
4721 ///
4722 /// `output` must have enough spare capacity to hold the decompressed
4723 /// data. This adds no extra slack: exact-fit output is now eligible
4724 /// for the direct decode path, so a `Vec::with_capacity(fcs)` is
4725 /// decoded straight into without a growth/reallocation. It will NOT
4726 /// grow the vector to fit the decompressed payload itself; the
4727 /// caller's pre-allocated capacity must already cover the data. If
4728 /// you don't know how large the output will be, use
4729 /// [`FrameDecoder::decode_blocks`] instead.
4730 ///
4731 /// This calls [`FrameDecoder::init`], and all bytes currently in the decoder will be lost.
4732 ///
4733 /// The length of the output vector is updated to include the
4734 /// decompressed data. The length is not changed if an error occurs.
4735 pub fn decode_all_to_vec(
4736 &mut self,
4737 input: &[u8],
4738 output: &mut Vec<u8>,
4739 ) -> Result<(), FrameDecoderError> {
4740 let len = output.len();
4741 let cap = output.capacity();
4742 output.resize(cap, 0);
4743 match self.decode_all(input, &mut output[len..]) {
4744 Ok(bytes_written) => {
4745 let new_len = core::cmp::min(len + bytes_written, cap); // Sanitizes `bytes_written`.
4746 output.resize(new_len, 0);
4747 Ok(())
4748 }
4749 Err(e) => {
4750 output.resize(len, 0);
4751 Err(e)
4752 }
4753 }
4754 }
4755
4756 /// Single-frame direct-decode path. Decodes one zstd frame into
4757 /// `output[..content_size]` via a stack-local
4758 /// `DecodeBuffer<UserSliceBackend>`, bypassing the per-block
4759 /// FlatBuf/Ring -> `read()` drain copy.
4760 ///
4761 /// # Preconditions (caller-enforced)
4762 ///
4763 /// - `self.init` (or `init_with_dict_handle`) was called for
4764 /// this frame so `self.state` is populated.
4765 /// - `declared_size` is the frame's declared content size, `> 0`, with
4766 /// `output.len() >= declared_size` (the eligibility gate), or `None`
4767 /// for a frame that declares none. Then `output` itself is the limit,
4768 /// as upstream `ZSTD_decompressDCtx` decodes into `dst`, and a frame
4769 /// that does not fit is `TargetTooSmall` rather than a size mismatch.
4770 /// No `WILDCOPY_OVERLENGTH` trailing slack is required: the trailing
4771 /// sequence(s) take the bounded (non-overshooting) copy in
4772 /// [`UserSliceBackend::exec_sequence_bounded`](crate::decoding::user_slice_buf::UserSliceBackend::exec_sequence_bounded).
4773 ///
4774 /// Dictionary frames are supported: the scratch buffer's shared
4775 /// dict handle is forwarded to the stack-local `DecodeBuffer`, so
4776 /// offsets reaching past the frame's own output resolve through
4777 /// `repeat_from_dict` (the ext-dict slow path).
4778 ///
4779 /// On return, `input` points at the byte immediately after the
4780 /// frame's checksum (or after the last block, when the frame
4781 /// has `content_checksum_flag = 0`). `self.state.frame_finished`
4782 /// is set so [`Self::is_finished`] reports `true`.
4783 fn run_direct_decode(
4784 &mut self,
4785 input: &mut &[u8],
4786 output: &mut [u8],
4787 declared_size: Option<u64>,
4788 ) -> Result<usize, FrameDecoderError> {
4789 #[cfg(test)]
4790 {
4791 self.direct_frames += 1;
4792 }
4793 use super::block_decoder;
4794 use super::decode_buffer::DecodeBuffer;
4795 use super::scratch::DirectScratch;
4796 use super::user_slice_buf::UserSliceBackend;
4797 use crate::io::Read;
4798 use FrameDecoderError as err;
4799
4800 // The most the frame may write: its declared size, or the caller's
4801 // slice for a frame that declares none.
4802 let limit = declared_size.unwrap_or(output.len() as u64);
4803 // Output past `limit`: the frame lied about its size, or it does not
4804 // fit the caller's slice.
4805 let overflow = |produced: u64| match declared_size {
4806 Some(declared) => err::FrameContentSizeMismatch { declared, produced },
4807 None => err::TargetTooSmall,
4808 };
4809
4810 let kernel = self.kernel;
4811 let state = self
4812 .state
4813 .as_mut()
4814 .expect("caller ensures init populated state");
4815
4816 // Fast path: a frame that is a single RAW block spanning the whole
4817 // declared content. Upstream zstd handles this as one `ZSTD_copyRawBlock`
4818 // (a `memmove`) inside `ZSTD_decompressFrame`; do the same here — a direct
4819 // `copy_from_slice` into the caller's output slice — skipping the
4820 // `DirectScratch` / `DecodeBuffer` / `UserSliceBackend` wrapper
4821 // construction and the general per-block loop. Incompressible payloads
4822 // (random / already-compressed data) emit exactly this shape, so the win
4823 // lands on small high-entropy frames where the per-frame machinery, not
4824 // the 1-block copy, dominates.
4825 {
4826 let mut probe = *input;
4827 let mut header_dec = block_decoder::with_kernel(kernel);
4828 if let Ok((bh, hsize)) = header_dec.read_block_header(&mut probe) {
4829 let n = bh.decompressed_size as usize;
4830 // A frame that declares no size takes the shortcut too: the
4831 // slice is its limit, and holding the block means holding the
4832 // frame. Without this the probe parsed the header that the
4833 // general loop below parses again, on the very path (a small
4834 // frame from a streamed producer) this decode is for. The
4835 // block maximum is checked here as the general path checks it:
4836 // a block past it is malformed, and the shortcut must not be
4837 // the way around that.
4838 if bh.last_block
4839 && matches!(bh.block_type, crate::blocks::block::BlockType::Raw)
4840 && declared_size.is_none_or(|declared| declared == n as u64)
4841 && n <= state.block_maximum()
4842 && probe.len() >= n
4843 && output.len() >= n
4844 {
4845 output[..n].copy_from_slice(&probe[..n]);
4846 *input = &probe[n..];
4847 state.bytes_read_counter += u64::from(hsize) + n as u64;
4848 state.block_counter += 1;
4849 // Consume the trailing 4-byte content checksum UNCONDITIONALLY
4850 // when the frame declares one — exactly like the general
4851 // direct loop and `decode_blocks`. Only the hash SEEDING is
4852 // `hash`-gated; the byte consumption / counter / `check_sum`
4853 // must not be, or a no-`hash` build leaves the 4 bytes in
4854 // `*input` (misparsed as the next frame) and never sets
4855 // `check_sum` (so `is_finished` stays false).
4856 if state.frame_header.descriptor.content_checksum_flag() {
4857 let mut chksum = [0u8; 4];
4858 Read::read_exact(input, &mut chksum).map_err(err::FailedToReadChecksum)?;
4859 state.bytes_read_counter += 4;
4860 state.check_sum = Some(u32::from_le_bytes(chksum));
4861 // Mirror the general path: seed the scratch hash so
4862 // `verify_content_checksum` / `get_calculated_checksum`
4863 // read the digest. Skipped under `ContentChecksum::None`.
4864 #[cfg(feature = "hash")]
4865 if self.content_checksum != ContentChecksum::None {
4866 use core::hash::Hasher;
4867 let mut h = twox_hash::XxHash64::with_seed(0);
4868 h.write(&output[..n]);
4869 match &mut state.decoder_scratch {
4870 DecoderScratchKind::Flat(s) => s.buffer.set_hash(h),
4871 DecoderScratchKind::Ring(s) => s.buffer.set_hash(h),
4872 }
4873 }
4874 }
4875 #[cfg(all(feature = "lsm", feature = "hash"))]
4876 if self.per_block_checksums_enabled {
4877 use core::hash::Hasher;
4878 let mut h = twox_hash::XxHash64::with_seed(0);
4879 h.write(&output[..n]);
4880 self.computed_block_checksums.push(h.finish() as u32);
4881 }
4882 state.decoder_scratch.set_total_output(n as u64);
4883 state.frame_finished = true;
4884 return Ok(n);
4885 }
4886 }
4887 }
4888
4889 // Borrow persistent fields out of whichever scratch variant
4890 // `init` produced (Flat for single_segment, Ring for
4891 // multi-segment) — both expose the same HUF/FSE/Vec
4892 // fields; only `buffer` differs and we don't use that here.
4893 // Macro-style binding avoids the closure / generic
4894 // gymnastics of returning multiple `&mut` from a match arm.
4895 // Resolve the dictionary borrow for this frame BEFORE taking the
4896 // `&mut` field borrows below — `active_dict` is a disjoint field, so
4897 // the shared borrow coexists with the mutable scratch borrows. It is
4898 // threaded as a call-scoped argument into every `Dict`-sourced read
4899 // (the direct path's `repeat_from_dict` ext-dict slow path), mirroring
4900 // C's per-frame pointer hand-off with zero refcount churn.
4901 // Only expose the held dictionary while THIS frame is dict-backed
4902 // (`using_dict` is set per dict-apply, cleared on reset). A reused
4903 // decoder keeps `active_dict` across a no-dict frame for the
4904 // `ptr::eq` reuse-skip, so it must be gated here or a stray
4905 // out-of-window offset on a dictless frame would resolve against the
4906 // stale dictionary content instead of erroring.
4907 let dict_ref = if state.using_dict.is_some() {
4908 state.active_dict.as_ref().map(|h| h.as_dict())
4909 } else {
4910 None
4911 };
4912 let block_limit = state.block_limit;
4913 let (huf, fse, offset_hist, literals_buffer, block_content_buffer, window_size) =
4914 match &mut state.decoder_scratch {
4915 DecoderScratchKind::Flat(s) => (
4916 &mut s.huf,
4917 &mut s.fse,
4918 &mut s.offset_hist,
4919 &mut s.literals_buffer,
4920 &mut s.block_content_buffer,
4921 s.buffer.window_size,
4922 ),
4923 DecoderScratchKind::Ring(s) => (
4924 &mut s.huf,
4925 &mut s.fse,
4926 &mut s.offset_hist,
4927 &mut s.literals_buffer,
4928 &mut s.block_content_buffer,
4929 s.buffer.window_size,
4930 ),
4931 };
4932 let backend = UserSliceBackend::from_slice(output);
4933 let mut buffer = DecodeBuffer::from_backend(backend, window_size);
4934 buffer.set_block_limit(block_limit);
4935 let mut direct = DirectScratch {
4936 huf,
4937 fse,
4938 offset_hist,
4939 literals_buffer,
4940 block_content_buffer,
4941 buffer,
4942 };
4943
4944 // Block loop. Mirrors `decode_blocks` (without the
4945 // strategy-bounded early exit — we always decode the whole
4946 // frame in one shot for the direct path). Keeps
4947 // `state.bytes_read_counter` / `state.block_counter` in
4948 // sync with `decode_blocks` so post-call accessors
4949 // (`bytes_read_from_source`, `blocks_decoded`) return
4950 // accurate values.
4951 let mut block_dec = block_decoder::with_kernel(kernel);
4952 // Track total output bytes against the declared
4953 // `frame_content_size` via the buffer's actual write
4954 // counter — `BlockHeader.decompressed_size` is 0 for
4955 // Compressed blocks (the header parser can't know the
4956 // expanded size before decoding the body), so per-header
4957 // tracking would always count 0 for those blocks and
4958 // miscount frames that aren't pure Raw/RLE.
4959 let mut produced: u64 = 0;
4960 // Per-block output ranges captured during the direct-path
4961 // loop. After the loop we re-borrow `output` (post-drop of
4962 // `direct`) and XXH64 each range into
4963 // `self.computed_block_checksums`, so the digests vector
4964 // stays consistent with the legacy `decode_blocks` path
4965 // regardless of which dispatch the frame took.
4966 // `Vec::new()` does not allocate, so this stays free when
4967 // `per_block_checksums_enabled` is false: the `push` and the
4968 // post-loop hashing loop are both gated by the same flag.
4969 #[cfg(all(feature = "lsm", feature = "hash"))]
4970 let mut block_ranges: alloc::vec::Vec<(usize, usize)> = alloc::vec::Vec::new();
4971 // Frame-level XXH64, accumulated PER BLOCK right after each block
4972 // decodes — the bytes are still cache-resident then. The previous
4973 // shape hashed the whole output once after the loop, which re-read
4974 // the entire frame cold: a full extra memory pass that the
4975 // reference implementation does not make (it hashes incrementally
4976 // per block). Invisible on outputs that fit L3, ~1.14x wall on a
4977 // 100 MiB all-raw decode and the dominant CI gap on
4978 // bandwidth-limited hosts.
4979 #[cfg(feature = "hash")]
4980 let mut running_hash: Option<twox_hash::XxHash64> =
4981 if state.frame_header.descriptor.content_checksum_flag()
4982 && self.content_checksum != ContentChecksum::None
4983 {
4984 Some(twox_hash::XxHash64::with_seed(0))
4985 } else {
4986 None
4987 };
4988 loop {
4989 #[cfg(all(feature = "lsm", feature = "hash"))]
4990 let produced_before: Option<usize> = if self.per_block_checksums_enabled {
4991 Some(produced as usize)
4992 } else {
4993 None
4994 };
4995 // Failing-block coordinates captured before the header read (see
4996 // the `decode_blocks` loop for the rationale).
4997 let block_index = state.block_counter as u32;
4998 let block_frame_offset = state.bytes_read_counter as u32;
4999 let (block_header, hsize) =
5000 block_dec.read_block_header(&mut *input).map_err(|source| {
5001 block_header_decode_error(source, block_index, block_frame_offset)
5002 })?;
5003 state.bytes_read_counter += u64::from(hsize);
5004 // Pre-flight FCS check ONLY for Raw / RLE blocks where
5005 // `decompressed_size` is the actual block output size.
5006 // For Compressed blocks the header field is 0; the
5007 // post-decode check below catches overflow via the
5008 // backend's actual write counter delta.
5009 let block_upper = u64::from(block_header.decompressed_size);
5010 if block_upper > 0 && produced + block_upper > limit {
5011 // Raw/RLE block headers claim more output than the FCS
5012 // allows (a corrupt frame) or the caller's slice holds.
5013 return Err(overflow(produced + block_upper));
5014 }
5015 // Slice-source fast path: consume the block body
5016 // straight from `input` without copying into the
5017 // persistent `block_content_buffer`.
5018 let body_consumed = match block_dec.decode_block_content_from_slice(
5019 &block_header,
5020 &mut direct,
5021 dict_ref,
5022 &mut *input,
5023 ) {
5024 Ok(n) => n,
5025 // Defense-in-depth: RLE / Raw block whose declared
5026 // `decompressed_size` slipped past the per-block
5027 // pre-flight above and tripped the backend's
5028 // fallible write surface.
5029 Err(crate::decoding::errors::DecodeBlockContentError::BackendOverflow {
5030 ..
5031 }) => {
5032 // Use saturating_add on the
5033 // `produced + decompressed_size` sum. Each block
5034 // is bounded by 128 KiB (MAX_BLOCK_SIZE), but
5035 // accumulated `produced` can grow toward
5036 // u64::MAX across adversarial frames. Saturating
5037 // avoids a panic on the error path itself.
5038 return Err(overflow(
5039 produced.saturating_add(u64::from(block_header.decompressed_size)),
5040 ));
5041 }
5042 // Compressed-block in-block overshoot: the sequence
5043 // executor (upstream zstd-inline path) or the match-repeat
5044 // fallback tripped the fixed-capacity backend's per-write
5045 // check. Unlike Raw/RLE, a Compressed block carries no
5046 // header-declared output size, so `produced` is computed
5047 // from the partial fill: `tail` bytes were written before
5048 // the failing op, and `requested` is what overflowed —
5049 // their sum is a strict lower bound on the frame's true
5050 // expanded size and is always > `limit`. With a declared
5051 // size the slice holds at least that much, so any overflow
5052 // means the frame exceeded its FCS, never a caller-undersized
5053 // buffer, and folds into the same `FrameContentSizeMismatch`
5054 // contract as Raw/RLE; without one the slice is the limit.
5055 // An overflow that stays within `limit` was refused by the
5056 // per-block output ceiling instead: a malformed block, which
5057 // takes the generic arm below.
5058 Err(crate::decoding::errors::DecodeBlockContentError::DecompressBlockError(
5059 crate::decoding::errors::DecompressBlockError::ExecuteSequencesError(ref e),
5060 )) if e.output_overflow_requested().is_some_and(|requested| {
5061 (direct.buffer.buffer_ref().tail() as u64).saturating_add(requested as u64)
5062 > limit
5063 }) =>
5064 {
5065 let requested = e
5066 .output_overflow_requested()
5067 .expect("guard guarantees Some") as u64;
5068 let tail = direct.buffer.buffer_ref().tail() as u64;
5069 return Err(overflow(tail.saturating_add(requested)));
5070 }
5071 // A no-sequence block's literals did not fit the slice. Every
5072 // direct-path entry holds `output.len() >= limit` (a declared
5073 // size is checked against the slice before the path is chosen,
5074 // and an undeclared frame's limit IS the slice), so a write
5075 // past the slice is a write past `limit`: the frame outgrew its
5076 // declared size, or the caller's target is short.
5077 Err(crate::decoding::errors::DecodeBlockContentError::DecompressBlockError(
5078 crate::decoding::errors::DecompressBlockError::LiteralsOutputOverflow {
5079 tail,
5080 requested,
5081 capacity,
5082 },
5083 )) => {
5084 debug_assert!(
5085 capacity as u64 >= limit,
5086 "direct path entered with a short slice"
5087 );
5088 return Err(overflow((tail as u64).saturating_add(requested as u64)));
5089 }
5090 Err(e) => {
5091 return Err(block_body_decode_error(
5092 e,
5093 block_index,
5094 block_frame_offset,
5095 &block_header,
5096 hsize,
5097 ));
5098 }
5099 };
5100 // Hash this block's freshly-written bytes while they are hot
5101 // (see `running_hash` above). `tail()` is the physical write
5102 // cursor: `drop_to_window_size` below only advances the head,
5103 // so `[prev_tail, tail)` is exactly this block's output.
5104 #[cfg(feature = "hash")]
5105 if let Some(hasher) = running_hash.as_mut() {
5106 use core::hash::Hasher;
5107 hasher.write(direct.buffer.buffer_ref().written_since(produced as usize));
5108 }
5109 produced = direct.buffer.buffer_ref().tail() as u64;
5110 // Post-decode FCS overflow check.
5111 if produced > limit {
5112 return Err(overflow(produced));
5113 }
5114 state.bytes_read_counter += body_consumed;
5115 state.block_counter += 1;
5116 #[cfg(all(feature = "lsm", feature = "hash"))]
5117 if let Some(produced_before) = produced_before {
5118 block_ranges.push((produced_before, produced as usize));
5119 }
5120 // Cap the visible buffer at window_size between blocks
5121 // so the next block's match-offset validation matches
5122 // the spec's `offset <= window_size` rule.
5123 direct.buffer.drop_to_window_size();
5124 if block_header.last_block {
5125 if state.frame_header.descriptor.content_checksum_flag() {
5126 let mut chksum = [0u8; 4];
5127 input
5128 .read_exact(&mut chksum)
5129 .map_err(err::FailedToReadChecksum)?;
5130 state.bytes_read_counter += 4;
5131 state.check_sum = Some(u32::from_le_bytes(chksum));
5132 }
5133 break;
5134 }
5135 }
5136 // Final sanity: blocks summed to exactly the declared size.
5137 if let Some(declared) = declared_size
5138 && produced != declared
5139 {
5140 return Err(err::FrameContentSizeMismatch { declared, produced });
5141 }
5142
5143 let written = produced as usize;
5144 state.decoder_scratch.set_total_output(produced);
5145 state.frame_finished = true;
5146 // `direct`'s last use is in the decode loop above; NLL therefore
5147 // releases its `&mut output` borrow before here, freeing `output` for
5148 // the hash re-borrow below. No explicit `drop(direct)` is needed:
5149 // `DirectScratch` now holds only borrowed dict POINTERS (not an owned
5150 // `Arc`), so it is not a `Drop` type whose glue would hold the borrow
5151 // to end-of-scope.
5152 // Per-block XXH64 (low 32 bits) over the captured ranges.
5153 // Mirrors `decode_blocks`' per-block hashing so the digests
5154 // vector stays identical regardless of which dispatch path
5155 // the frame took. Ranges were recorded inside the loop while
5156 // `direct` held a mutable borrow on `output`; now that the
5157 // borrow is dropped we can read the slices directly.
5158 #[cfg(all(feature = "lsm", feature = "hash"))]
5159 if self.per_block_checksums_enabled {
5160 use core::hash::Hasher;
5161 for (start, end) in &block_ranges {
5162 let mut h = twox_hash::XxHash64::with_seed(0);
5163 h.write(&output[*start..*end]);
5164 self.computed_block_checksums.push(h.finish() as u32);
5165 }
5166 }
5167 #[cfg(feature = "hash")]
5168 if let Some(hasher) = running_hash {
5169 // Propagate the per-block-accumulated hasher state (see the
5170 // `running_hash` rationale above the loop) so the frame-tail
5171 // XXH64 check and `get_calculated_checksum()` read the digest.
5172 // `running_hash` is `None` for flag-off frames or
5173 // `ContentChecksum::None` — nothing to verify there, and
5174 // `get_calculated_checksum()` returns `None`, matching the skip.
5175 match &mut state.decoder_scratch {
5176 DecoderScratchKind::Flat(s) => s.buffer.set_hash(hasher),
5177 DecoderScratchKind::Ring(s) => s.buffer.set_hash(hasher),
5178 }
5179 }
5180 Ok(written)
5181 }
5182}
5183
5184/// Read bytes from the decode_buffer that are no longer needed. While the frame is not yet finished
5185/// this will retain window_size bytes, else it will drain it completely
5186impl Read for FrameDecoder {
5187 fn read(&mut self, target: &mut [u8]) -> Result<usize, Error> {
5188 #[cfg(feature = "legacy")]
5189 if let Some(stream) = self.legacy_frame_mut() {
5190 return Ok(stream.hand_out(target));
5191 }
5192 let state = match &mut self.state {
5193 None => return Ok(0),
5194 Some(s) => s,
5195 };
5196 if state.frame_finished {
5197 state.decoder_scratch.buffer_read_all(target)
5198 } else {
5199 state.decoder_scratch.buffer_read(target)
5200 }
5201 }
5202}
5203
5204#[cfg(test)]
5205mod tests;