Skip to main content

cadmpeg_core/decode/
zstd.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Window-admitted Zstandard streams.
3//!
4//! The `open_zstd` / `ZstdDecoder` interface, built on the pure-Rust `ruzstd` crate rather
5//! than on `zstd::zstd_safe` (libzstd through `zstd-sys` and `cc`), so the workspace builds
6//! with no C code.
7//!
8//! `ruzstd` is a block decoder with a ring buffer, not a port of libzstd, so everything
9//! libzstd checks and `ruzstd` does not is checked HERE, from this module's own reading of
10//! the frame, and each was measured against the Zstandard CLI v1.5.5 on 2026-10-12:
11//!
12//! | rule | libzstd | bare `ruzstd` 0.9 | this module |
13//! |---|---|---|---|
14//! | reserved frame-descriptor bit | refused | accepted | refused at open |
15//! | content checksum compared | refused on mismatch | read, never compared | compared |
16//! | `Frame_Content_Size` equals output | refused on mismatch | ignored | compared |
17//! | block header size above `Block_Maximum_Size` | refused | refused since 0.9.1 | refused |
18//! | sequences regenerating past `Block_Maximum_Size` | refused | refused since 0.9.1 | (`ruzstd`'s) |
19//! | several frames, skippable frames | decoded / skipped | first frame only | decoded / skipped |
20//! | bytes after the last frame | refused | ignored | refused at open |
21//!
22//! One thing `ruzstd` does not reproduce, stated rather than hidden: **a compressed block's
23//! LITERALS are not held to `Block_Maximum_Size`.** `ruzstd` 0.9.1 refuses a block whose
24//! sequences regenerate past the maximum, as it executes them: a 227-byte frame of a hundred
25//! maximal matches is refused with 0.27 MB of heap, where 0.9.0 built its 13 MiB first (29.9
26//! MB; both measured, 2026-10-13). That is why the requirement is `0.9.1` and not `0.9`. What
27//! it still does not check is the literals: a block with no sequences, and the literals after
28//! a block's last sequence. A literals section states at most 2^20 - 1 bytes, so the excess is
29//! bounded by 1 MiB a block, is ALLOCATED before anything can refuse it, and is refused
30//! afterwards by this module whenever the block's size is determinable (see `decode_block`).
31//! It is not determinable for a compressed block decoded while the buffer is still under its
32//! window and its length unknown; the test `zstd_known_gap_...` pins the frame that gets
33//! through.
34//!
35//! Two differences in memory behaviour, also stated rather than hidden:
36//!
37//! - **The admission bounds the window, not peak heap.** libzstd allocates one window per
38//!   frame. `ruzstd`'s ring buffer grows by reallocation and holds the old and new buffers
39//!   while copying, so a frame that fills its window peaks near two windows plus three
40//!   blocks: about twice the window the admission charged.
41//! - **A failed allocation is not returned.** libzstd reported an allocation failure as an
42//!   error, which this module turned into `retained_allocation_failed`; `ruzstd` panics on a
43//!   null from the allocator for its ring buffer and aborts through the global allocation
44//!   error handler elsewhere, so an allocation that fails inside the decoder now panics or
45//!   aborts instead of returning an error.
46
47use std::io::Read as _;
48
49use ruzstd::decoding::errors::FrameDecoderError;
50use ruzstd::decoding::{BlockDecodingStrategy, FrameDecoder};
51
52use super::{u64_from_index, DecodeContext, View};
53use crate::CodecError;
54
55const BLOCK_BYTES: u64 = 128 * 1024;
56/// The largest window the decoder opens on this target. A larger frame is
57/// outside the reader, not over a session ceiling.
58const MAX_DECODER_WINDOW: u64 = 1_u64 << if usize::BITS == 32 { 30 } else { 31 };
59const OUTPUT_STEP_BYTES: usize = 16 * 1024;
60const FRAME_MAGIC: u32 = 0xfd2f_b528;
61
62/// One Zstandard decoder over an admitted input.
63pub struct ZstdDecoder<'ctx, 'input> {
64    frame: Option<ActiveFrame<'input>>,
65    /// The decoder is opened under its context's admission and does not outlive it.
66    _ctx: std::marker::PhantomData<&'ctx DecodeContext<'input>>,
67    /// Frames not yet opened. Every byte was framed by `open_zstd`.
68    input: View<'input>,
69    finished: bool,
70}
71
72/// The frame being decoded, with this module's own account of it beside `ruzstd`'s.
73struct ActiveFrame<'input> {
74    decoder: FrameDecoder,
75    /// Exactly this frame's bytes, as measured by `inspect_frame`; `ruzstd` reads from here
76    /// and must leave it empty.
77    source: &'input [u8],
78    window: u64,
79    block_maximum: u64,
80    content_size: Option<u64>,
81    produced: u64,
82    /// Bytes held in `ruzstd`'s ring buffer, when that is determinable.
83    buffered: Option<u64>,
84}
85
86/// What `open_zstd` and `read_chunk` need from one frame's framing.
87struct Inspected {
88    /// Whole frame, header to checksum, in bytes.
89    size: usize,
90    /// `None` for a skippable frame, which has no decoder state.
91    data: Option<FrameFacts>,
92}
93
94#[derive(Clone, Copy)]
95struct FrameFacts {
96    /// `Window_Size`, or `Frame_Content_Size` for a single-segment frame.
97    window: u64,
98    content_size: Option<u64>,
99}
100
101impl<'input> DecodeContext<'input> {
102    /// Inspects all frame windows before allocating a decoder. The output
103    /// each frame's decoder buffers, its window or its declared content size
104    /// when that is smaller, must fit the per-expansion decompression
105    /// allowance, and the declared window must fit the decoder's. Either
106    /// excess is [`CodecError::NotImplemented`] for this stream and leaves the
107    /// session open, since nothing was allocated.
108    pub fn open_zstd<'ctx>(
109        &'ctx self,
110        source: View<'input>,
111    ) -> Result<ZstdDecoder<'ctx, 'input>, CodecError> {
112        let mut offset = 0;
113        let allowance = self.per_expand_allowance();
114        while offset < source.window().len() {
115            let frame = source
116                .child(source.start() + offset, source.end())
117                .ok_or_else(|| {
118                    CodecError::Malformed("Zstandard frame offset escapes input".into())
119                })?;
120            if let Some(facts) = frame_header(frame)?.1 {
121                let buffered = facts
122                    .content_size
123                    .map_or(facts.window, |size| size.min(facts.window));
124                if buffered > allowance {
125                    return Err(CodecError::NotImplemented(format!(
126                        "Zstandard window of {buffered} bytes exceeds the \
127                         {allowance}-byte per-expansion allowance"
128                    )));
129                }
130                if facts.window > MAX_DECODER_WINDOW {
131                    return Err(window_unsupported(facts.window));
132                }
133            }
134            let Inspected { size, .. } = inspect_frame(frame)?;
135            if size == 0 || size > frame.window().len() {
136                return Err(CodecError::Malformed("invalid Zstandard frame size".into()));
137            }
138            offset += size;
139        }
140        if offset == 0 {
141            return Err(CodecError::Malformed("empty Zstandard stream".into()));
142        }
143        // No decoder exists yet: `ruzstd` allocates per frame, on `FrameDecoder::init`, and
144        // each frame is opened with its own inspected window as the decoder's limit.
145        Ok(ZstdDecoder {
146            frame: None,
147            _ctx: std::marker::PhantomData,
148            input: source
149                .child(source.start(), source.end())
150                .ok_or_else(|| CodecError::Malformed("Zstandard input escapes its view".into()))?,
151            finished: false,
152        })
153    }
154}
155
156impl ZstdDecoder<'_, '_> {
157    /// Reads decoded output into caller-owned storage.
158    pub fn read_chunk(&mut self, bytes: &mut [u8]) -> Result<usize, CodecError> {
159        if self.finished || bytes.is_empty() {
160            return Ok(0);
161        }
162        let capacity = bytes.len().min(OUTPUT_STEP_BYTES);
163        // One step is at most: open a frame, decode one block, collect one chunk, close the
164        // frame.
165        loop {
166            if self.frame.is_none() {
167                if self.input.is_empty() {
168                    self.finished = true;
169                    return Ok(0);
170                }
171                self.open_next_frame()?;
172            }
173            let Some(frame) = self.frame.as_mut() else {
174                // A skippable frame was passed over.
175                continue;
176            };
177            if frame.decoder.can_collect() == 0 && !frame.decoder.is_finished() {
178                frame.decode_block()?;
179            }
180            let produced = frame
181                .decoder
182                .read(&mut bytes[..capacity])
183                .map_err(|error| malformed(&error))?;
184            frame.produced += u64_from_index(produced);
185            if frame
186                .content_size
187                .is_some_and(|declared| frame.produced > declared)
188            {
189                return Err(CodecError::Malformed(
190                    "Zstandard frame regenerates more than its declared content size".into(),
191                ));
192            }
193            // The frame's own verdict travels with its last bytes: a caller that stops
194            // reading once its buffer is full must not be able to skip the checksum.
195            if frame.decoder.is_finished() && frame.decoder.can_collect() == 0 {
196                self.close_frame()?;
197                if self.input.is_empty() {
198                    self.finished = true;
199                }
200            }
201            if produced != 0 || self.finished {
202                return Ok(produced);
203            }
204        }
205    }
206
207    /// Skips a skippable frame, or creates the decoder for the next data frame.
208    fn open_next_frame(&mut self) -> Result<(), CodecError> {
209        let inspected = inspect_frame(self.input)?;
210        let source = self
211            .input
212            .take(inspected.size)
213            .ok_or_else(|| CodecError::Malformed("truncated Zstandard stream".into()))?;
214        let Some(facts) = inspected.data else {
215            return Ok(());
216        };
217        let mut decoder = FrameDecoder::new();
218        // The limit is this frame's own admitted window, so `ruzstd` cannot size a buffer
219        // from a header this module read differently.
220        decoder.set_max_window_size(facts.window);
221        let mut frame = ActiveFrame {
222            decoder,
223            source,
224            window: facts.window,
225            block_maximum: facts.window.min(BLOCK_BYTES),
226            content_size: facts.content_size,
227            produced: 0,
228            buffered: Some(0),
229        };
230        frame
231            .decoder
232            .init(&mut frame.source)
233            .map_err(|error| zstd_error(&error, facts.window))?;
234        self.frame = Some(frame);
235        Ok(())
236    }
237
238    /// The checks libzstd makes at the end of a frame and `ruzstd` leaves to its caller.
239    fn close_frame(&mut self) -> Result<(), CodecError> {
240        let Some(frame) = self.frame.take() else {
241            return Ok(());
242        };
243        // `ruzstd` reads the frame's content checksum and never compares it. `None` on the
244        // declared side is a frame without one, which is legal and is what most encoders write.
245        if let (Some(declared), Some(computed)) = (
246            frame.decoder.get_checksum_from_data(),
247            frame.decoder.get_calculated_checksum(),
248        ) {
249            if declared != computed {
250                return Err(CodecError::Malformed(
251                    "Zstandard content checksum mismatch".into(),
252                ));
253            }
254        }
255        if frame
256            .content_size
257            .is_some_and(|declared| declared != frame.produced)
258        {
259            return Err(CodecError::Malformed(
260                "Zstandard frame does not regenerate its declared content size".into(),
261            ));
262        }
263        // `inspect_frame` and `ruzstd` each walked this frame's blocks; they must agree on
264        // where it ends, or the bytes between are neither decoded nor refused. An invariant
265        // check: no input is known that separates the two walks, so no test can fail on it.
266        if !frame.source.is_empty() {
267            return Err(CodecError::Malformed(
268                "Zstandard decoder stopped before the end of its frame".into(),
269            ));
270        }
271        Ok(())
272    }
273}
274
275impl ActiveFrame<'_> {
276    /// Decodes exactly one block, holding it to `Block_Maximum_Size` on both sides.
277    fn decode_block(&mut self) -> Result<(), CodecError> {
278        let header = View::u24_le_at(self.source, 0)
279            .ok_or_else(|| CodecError::Malformed("truncated Zstandard stream".into()))?;
280        let kind = (header >> 1) & 3;
281        let declared = u64::from(header >> 3);
282        // libzstd holds the header's size field to min(window, 128 KiB) for every block
283        // type; `ruzstd` holds it to 128 KiB alone.
284        if declared > self.block_maximum {
285            return Err(CodecError::Malformed(
286                "Zstandard block size exceeds its frame's maximum".into(),
287            ));
288        }
289        self.decoder
290            .decode_blocks(&mut self.source, BlockDecodingStrategy::UptoBlocks(1))
291            .map_err(|error| zstd_error(&error, self.window))?;
292        // What the block regenerated. For a raw or RLE block the header says; for a
293        // compressed block `ruzstd` exposes only what is collectable, which gives the buffer
294        // length exactly once the frame is finished or the buffer is past its window, and
295        // not otherwise.
296        let collectable = u64_from_index(self.decoder.can_collect());
297        let length = if self.decoder.is_finished() {
298            Some(collectable)
299        } else if collectable != 0 {
300            self.window.checked_add(collectable)
301        } else if kind == 2 {
302            None
303        } else {
304            self.buffered
305                .and_then(|buffered| buffered.checked_add(declared))
306        };
307        if let Some(length) = length {
308            // Without the earlier length, the buffer held at most one window.
309            let before = self.buffered.unwrap_or(self.window);
310            if length
311                .checked_sub(before)
312                .is_some_and(|grown| grown > self.block_maximum)
313            {
314                return Err(CodecError::Malformed(
315                    "Zstandard block regenerates more than its frame's maximum".into(),
316                ));
317            }
318        }
319        // Everything past the window is collected before the next block is decoded.
320        self.buffered = length.map(|length| length.min(self.window));
321        Ok(())
322    }
323}
324
325fn window_unsupported(window: u64) -> CodecError {
326    CodecError::NotImplemented(format!(
327        "Zstandard window of {window} bytes exceeds the {MAX_DECODER_WINDOW}-byte decoder window"
328    ))
329}
330
331fn malformed(error: &dyn std::fmt::Display) -> CodecError {
332    CodecError::malformed(format_args!("invalid Zstandard stream: {error}"))
333}
334
335fn zstd_error(error: &FrameDecoderError, window: u64) -> CodecError {
336    match error {
337        FrameDecoderError::WindowSizeTooBig { .. } => window_unsupported(window),
338        _ => malformed(error),
339    }
340}
341
342/// Reads one frame header, returning the view past it and the frame's facts. A skippable
343/// frame has no facts, and its view is left before its four-byte length.
344fn frame_header(mut frame: View<'_>) -> Result<(View<'_>, Option<FrameFacts>), CodecError> {
345    let magic = frame.req_u32_le()?;
346    if (0x184d_2a50..=0x184d_2a5f).contains(&magic) {
347        return Ok((frame, None));
348    }
349    if magic != FRAME_MAGIC {
350        return Err(CodecError::Malformed("invalid Zstandard magic".into()));
351    }
352    let flags = frame.req_u8()?;
353    if flags & 8 != 0 {
354        return Err(CodecError::Malformed(
355            "reserved Zstandard frame flag".into(),
356        ));
357    }
358    let single_segment = flags & 32 != 0;
359    let window = if single_segment {
360        0
361    } else {
362        let descriptor = frame.req_u8()?;
363        let base = 1_u64 << (10 + u32::from(descriptor >> 3));
364        base + (base / 8) * u64::from(descriptor & 7)
365    };
366    let dictionary_bytes = match flags & 3 {
367        0 => 0,
368        1 => 1,
369        2 => 2,
370        _ => 4,
371    };
372    frame.req_take(dictionary_bytes)?;
373    let content_size = match flags >> 6 {
374        0 if single_segment => Some(u64::from(frame.req_u8()?)),
375        0 => None,
376        1 => Some(u64::from(frame.req_u16_le()?) + 256),
377        2 => Some(u64::from(frame.req_u32_le()?)),
378        _ => Some(frame.req_u64_le()?),
379    };
380    Ok((
381        frame,
382        Some(FrameFacts {
383            window: if single_segment {
384                content_size.unwrap_or(0)
385            } else {
386                window
387            },
388            content_size,
389        }),
390    ))
391}
392
393/// Measures one frame without decoding it: header, every block header, optional checksum.
394///
395/// This is `ZSTD_findFrameCompressedSize`, which the libzstd decoder called and `ruzstd` has no
396/// equivalent of. It is what lets every frame's window be admitted before any decoder
397/// exists, and what refuses bytes that follow the last frame.
398fn inspect_frame(frame: View<'_>) -> Result<Inspected, CodecError> {
399    let truncated = || CodecError::Malformed("truncated Zstandard frame".into());
400    let start = frame.read_len();
401    let checksum = frame.unread().get(4).is_some_and(|flags| flags & 4 != 0);
402    let (mut body, facts) = frame_header(frame)?;
403    let Some(facts) = facts else {
404        let length = usize::try_from(body.req_u32_le()?).map_err(|_| truncated())?;
405        body.skip(length).ok_or_else(truncated)?;
406        return Ok(Inspected {
407            size: body.read_len() - start,
408            data: None,
409        });
410    };
411    loop {
412        let header = body.take(3).ok_or_else(truncated)?;
413        let header = u32::from(header[0]) | u32::from(header[1]) << 8 | u32::from(header[2]) << 16;
414        let declared = header >> 3;
415        let kind = (header >> 1) & 3;
416        let stored = match kind {
417            1 => 1,
418            3 => {
419                return Err(CodecError::Malformed(
420                    "reserved Zstandard block type".into(),
421                ))
422            }
423            _ => usize::try_from(declared).map_err(|_| truncated())?,
424        };
425        body.skip(stored).ok_or_else(truncated)?;
426        if header & 1 != 0 {
427            break;
428        }
429    }
430    if checksum {
431        body.skip(4).ok_or_else(truncated)?;
432    }
433    Ok(Inspected {
434        size: body.read_len() - start,
435        data: Some(facts),
436    })
437}
438
439#[cfg(test)]
440mod tests {
441    use super::{BLOCK_BYTES, MAX_DECODER_WINDOW};
442    use crate::decode::{DecodeArena, DecodeContext, DecodePolicy, ExpandSpec};
443    use crate::CodecError;
444
445    const MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
446
447    // Frames are laid out by hand rather than with `zstd::bulk::compress`, which is the
448    // libzstd binding this crate no longer links; the frames written by the reference
449    // encoder live in `cadmpeg-test-support::zip_fixture` and are decoded through this module
450    // by `cadmpeg-container`'s tests, which this crate cannot depend on.
451
452    fn block(last: bool, kind: u32, size: usize, stored: &[u8]) -> Vec<u8> {
453        let header = u32::try_from(size).expect("block size") << 3 | kind << 1 | u32::from(last);
454        let mut out = header.to_le_bytes()[..3].to_vec();
455        out.extend_from_slice(stored);
456        out
457    }
458
459    fn raw(last: bool, payload: &[u8]) -> Vec<u8> {
460        block(last, 0, payload.len(), payload)
461    }
462
463    /// A single-segment frame declaring its content size, one raw block.
464    fn single_segment(payload: &[u8]) -> Vec<u8> {
465        let mut out = MAGIC.to_vec();
466        out.extend_from_slice(&[0x20, u8::try_from(payload.len()).expect("one-byte size")]);
467        out.extend(raw(true, payload));
468        out
469    }
470
471    /// A frame declaring `window_log` and no content size, from ready-made blocks.
472    fn windowed(window_log: u8, blocks: &[Vec<u8>]) -> Vec<u8> {
473        let mut out = MAGIC.to_vec();
474        out.extend_from_slice(&[0x00, (window_log - 10) << 3]);
475        for block in blocks {
476            out.extend_from_slice(block);
477        }
478        out
479    }
480
481    fn decode_with(bytes: &[u8], policy: &DecodePolicy) -> Result<Vec<u8>, CodecError> {
482        let arena = DecodeArena::new();
483        let (ctx, root) = DecodeContext::from_root_bytes(bytes, &arena, policy).expect("root");
484        let mut decoder = ctx.open_zstd(root)?;
485        let mut output = Vec::new();
486        let mut chunk = [0_u8; 16 * 1024];
487        loop {
488            let read = decoder.read_chunk(&mut chunk)?;
489            if read == 0 {
490                return Ok(output);
491            }
492            output.extend_from_slice(&chunk[..read]);
493        }
494    }
495
496    fn decode(bytes: &[u8]) -> Result<Vec<u8>, CodecError> {
497        decode_with(bytes, &DecodePolicy::service())
498    }
499
500    fn assert_malformed(bytes: &[u8], what: &str) {
501        let result = decode(bytes);
502        assert!(
503            matches!(result, Err(CodecError::Malformed(_))),
504            "{what}: {result:?}"
505        );
506    }
507
508    /// A frame cut short is refused as `Truncated` when the cut falls inside a frame header
509    /// (the bounded reader's own refusal, as with libzstd) and as `Malformed` anywhere after it.
510    fn assert_cut_short(bytes: &[u8], what: &str) {
511        let result = decode(bytes);
512        assert!(
513            matches!(
514                result,
515                Err(CodecError::Malformed(_) | CodecError::Truncated { .. })
516            ),
517            "{what}: {result:?}"
518        );
519    }
520
521    /// A frame header with a non-single-segment window of `window` bytes,
522    /// followed by one empty raw block.
523    fn frame_with_window(window: u64) -> Vec<u8> {
524        let exponent = u8::try_from(window.trailing_zeros() - 10).expect("window exponent");
525        vec![0x28, 0xb5, 0x2f, 0xfd, 0, exponent << 3, 1, 0, 0]
526    }
527
528    #[test]
529    fn zstd_window_past_the_per_expand_allowance_is_unsupported_and_leaves_the_session_open() {
530        let window = 32 << 20;
531        let bytes = frame_with_window(window);
532        let policy = DecodePolicy::service();
533        let arena = DecodeArena::new();
534        let (ctx, root) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).expect("root");
535        let allowance = ctx.per_expand_allowance();
536        assert!(allowance < window);
537        assert!(window < policy.limits.max_decompressed_bytes_per_expand);
538        let Err(CodecError::NotImplemented(message)) = ctx.open_zstd(root) else {
539            panic!("a window above the allowance is unsupported");
540        };
541        assert_eq!(
542            message,
543            format!(
544                "Zstandard window of {window} bytes exceeds the \
545                 {allowance}-byte per-expansion allowance"
546            )
547        );
548        assert_eq!(ctx.resource_refusal(), None);
549        let mut writer = ctx
550            .begin_expand(ExpandSpec::Exact(1))
551            .expect("later expansions are admitted");
552        writer.write(b"x").expect("write");
553        writer.finalize().expect("finalize");
554        assert!(ctx.finish_session().is_ok());
555    }
556
557    #[test]
558    fn zstd_window_is_admitted_by_a_smaller_declared_content_size() {
559        let window = 32 << 20;
560        let content = [7_u8; 256];
561        // A 32 MiB window descriptor, a two-byte content size of 256 bytes,
562        // and one raw last block holding the content.
563        let mut bytes = vec![
564            0x28,
565            0xb5,
566            0x2f,
567            0xfd,
568            0x40,
569            15 << 3,
570            0,
571            0,
572            0x01,
573            0x08,
574            0x00,
575        ];
576        bytes.extend_from_slice(&content);
577        let policy = DecodePolicy::service();
578        let arena = DecodeArena::new();
579        let (ctx, root) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).expect("root");
580        assert!(ctx.per_expand_allowance() < window);
581        let mut decoder = ctx.open_zstd(root).expect("the content size fits");
582        let mut output = Vec::new();
583        let mut chunk = [0; 64];
584        loop {
585            let read = decoder.read_chunk(&mut chunk).expect("read");
586            if read == 0 {
587                break;
588            }
589            output.extend_from_slice(&chunk[..read]);
590        }
591        assert_eq!(output, content);
592        assert_eq!(ctx.resource_refusal(), None);
593    }
594
595    #[test]
596    fn zstd_window_above_the_decoder_is_unsupported_and_leaves_the_session_open() {
597        let window = MAX_DECODER_WINDOW * 2;
598        let mut bytes = frame_with_window(window);
599        // Each input byte raises the per-expand allowance by 256 bytes.
600        bytes.resize(usize::try_from(window / 256).expect("padding length"), 0);
601        let mut policy = DecodePolicy::service();
602        policy.limits.max_decompressed_bytes_per_expand = window;
603        let arena = DecodeArena::new();
604        let (ctx, root) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).expect("root");
605        assert!(ctx.per_expand_allowance() >= window);
606        assert!(matches!(
607            ctx.open_zstd(root),
608            Err(CodecError::NotImplemented(message)) if message.contains("decoder window")
609        ));
610        assert_eq!(ctx.resource_refusal(), None);
611        assert!(ctx.finish_session().is_ok());
612    }
613
614    #[test]
615    fn zstd_checks_each_window_before_decoder_allocation() {
616        let mut bytes = single_segment(b"prefix");
617        bytes.extend_from_slice(&[0x28, 0xb5, 0x2f, 0xfd, 0, 0xa0, 1, 0, 0]);
618        let arena = DecodeArena::new();
619        let (ctx, root) =
620            DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service()).expect("root");
621        assert!(
622            matches!(ctx.open_zstd(root), Err(CodecError::NotImplemented(message)) if message.starts_with(&format!("Zstandard window of {} bytes", 1_u64 << 30)) && message.ends_with("per-expansion allowance"))
623        );
624        assert_eq!(ctx.resource_refusal(), None);
625    }
626
627    #[test]
628    fn zstd_decodes_a_frame() {
629        let bytes = single_segment(b"payload");
630        let arena = DecodeArena::new();
631        let (ctx, root) =
632            DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service()).expect("root");
633        let mut decoder = ctx.open_zstd(root).expect("decoder");
634        let mut output = [0; 7];
635        assert_eq!(decoder.read_chunk(&mut output).expect("read"), 7);
636        assert_eq!(&output, b"payload");
637    }
638
639    /// A frame that fills its window decodes: twenty 128 KiB blocks under a 1 MiB window,
640    /// whose ring buffer holds a window and a block.
641    #[test]
642    fn zstd_decodes_a_frame_that_fills_its_window() {
643        let block_bytes = usize::try_from(BLOCK_BYTES).expect("block size");
644        let mut blocks = vec![block(false, 1, block_bytes, b"x"); 19];
645        blocks.push(block(true, 1, block_bytes, b"x"));
646        let bytes = windowed(20, &blocks);
647        assert_eq!(decode(&bytes).expect("fits"), vec![b'x'; 20 * block_bytes]);
648    }
649
650    /// A short frame declaring a window above the per-expansion allowance and no content size
651    /// is unsupported, though `ruzstd` would never allocate that window for it; a declared
652    /// content size is what admits it. The admitted frame's 256 MiB window is above `ruzstd`'s
653    /// own default limit (128 MiB in 0.9.1), so it decodes only because the admitted window is
654    /// passed to `ruzstd` as that frame's limit.
655    #[test]
656    fn zstd_short_frame_under_a_large_window_needs_its_content_size() {
657        let bytes = windowed(28, &[raw(true, b"abcd")]);
658        assert!(matches!(
659            decode(&bytes),
660            Err(CodecError::NotImplemented(message)) if message.ends_with("per-expansion allowance")
661        ));
662        let mut sized = MAGIC.to_vec();
663        sized.extend_from_slice(&[0x80, 18 << 3, 4, 0, 0, 0]); // 256 MiB window, content size 4
664        sized.extend(raw(true, b"abcd"));
665        assert_eq!(decode(&sized).expect("frame"), b"abcd");
666    }
667
668    #[test]
669    fn zstd_decodes_every_frame_and_skips_skippable_ones() {
670        let first = single_segment(b"first ");
671        let second = windowed(10, &[raw(false, b"sec"), raw(true, b"ond")]);
672        let skippable = [&[0x53, 0x2a, 0x4d, 0x18, 3, 0, 0, 0][..], b"zzz"].concat();
673
674        assert_eq!(
675            decode(&[first.clone(), second.clone()].concat()).expect("two frames"),
676            b"first second"
677        );
678        assert_eq!(
679            decode(&[skippable.clone(), first.clone(), skippable.clone(), second].concat())
680                .expect("skippable frames between data frames"),
681            b"first second"
682        );
683        assert_eq!(
684            decode(&skippable).expect("only a skippable frame"),
685            b"",
686            "a stream of skippable frames is an empty payload"
687        );
688        assert_malformed(b"", "an empty stream");
689        // Bytes after the last frame belong to no frame, and are refused before any decoding.
690        for tail in [&b"junk"[..], &b"\0"[..], &skippable[..9]] {
691            let bytes = [&first[..], tail].concat();
692            let arena = DecodeArena::new();
693            let (ctx, root) =
694                DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service())
695                    .expect("root");
696            assert!(
697                matches!(
698                    ctx.open_zstd(root),
699                    Err(CodecError::Malformed(_) | CodecError::Truncated { .. })
700                ),
701                "a {}-byte tail was admitted",
702                tail.len()
703            );
704        }
705    }
706
707    #[test]
708    fn zstd_rle_blocks_and_block_boundaries_decode() {
709        let frame = windowed(
710            10,
711            &[
712                raw(false, b"ab"),
713                block(false, 1, 1000, b"x"),
714                block(false, 0, 0, b""),
715                block(true, 1, 600, b"y"),
716            ],
717        );
718        let mut expected = b"ab".to_vec();
719        expected.extend(std::iter::repeat_n(b'x', 1000));
720        expected.extend(std::iter::repeat_n(b'y', 600));
721        assert_eq!(decode(&frame).expect("frame"), expected);
722        for keep in 0..frame.len() {
723            assert_cut_short(&frame[..keep], "a truncated frame");
724        }
725    }
726
727    /// The content checksum is compared here; `ruzstd` only reads it.
728    #[test]
729    fn zstd_content_checksum_is_compared() {
730        // XXH64("abcd", seed 0) = 0xde0327b0d25d92cc; the frame carries its low 32 bits.
731        let mut frame = MAGIC.to_vec();
732        frame.extend_from_slice(&[0x04, 0x00]);
733        frame.extend(raw(true, b"abcd"));
734        frame.extend_from_slice(&0xd25d_92cc_u32.to_le_bytes());
735        assert_eq!(decode(&frame).expect("matching checksum"), b"abcd");
736        for index in frame.len() - 4..frame.len() {
737            let mut corrupt = frame.clone();
738            corrupt[index] ^= 1;
739            assert_malformed(&corrupt, "a wrong content checksum");
740        }
741        let payload = frame.len() - 5;
742        frame[payload] ^= 1;
743        assert_malformed(&frame, "a payload its checksum does not cover");
744    }
745
746    /// `Frame_Content_Size` is compared with what the frame regenerated, in both directions.
747    #[test]
748    fn zstd_declared_content_size_is_compared() {
749        let mut frame = single_segment(b"abcd");
750        assert_eq!(decode(&frame).expect("exact"), b"abcd");
751        frame[5] = 8;
752        assert_malformed(&frame, "a frame shorter than its declared size");
753        // Longer than declared, with a window so that the block itself is legal.
754        let mut frame = MAGIC.to_vec();
755        frame.extend_from_slice(&[0x40, 0x00, 0x00, 0x00]); // window 1 KiB, content size 256
756        frame.extend(raw(true, &[7; 300]));
757        assert_malformed(&frame, "a frame longer than its declared size");
758        frame[6] = 44; // 256 + 44
759        assert_eq!(decode(&frame).expect("exact"), [7; 300]);
760    }
761
762    /// A frame that outruns its declared content size is stopped there, not at its end.
763    #[test]
764    fn zstd_stops_at_the_declared_content_size() {
765        let block_bytes = usize::try_from(BLOCK_BYTES).expect("block size");
766        let mut frame = MAGIC.to_vec();
767        frame.extend_from_slice(&[0x40, 0x50, 0x00, 0x00]); // window 1 MiB, content size 256
768        for index in 0..10 {
769            frame.extend(block(index == 9, 1, block_bytes, b"x"));
770        }
771        let arena = DecodeArena::new();
772        let (ctx, root) =
773            DecodeContext::from_root_bytes(&frame, &arena, &DecodePolicy::service()).expect("root");
774        let mut decoder = ctx.open_zstd(root).expect("decoder");
775        let mut chunk = [0_u8; 16 * 1024];
776        let mut delivered = 0;
777        let error = loop {
778            match decoder.read_chunk(&mut chunk) {
779                Ok(0) => panic!("the frame was accepted"),
780                Ok(read) => delivered += read,
781                Err(error) => break error,
782            }
783        };
784        assert!(matches!(error, CodecError::Malformed(_)), "{error:?}");
785        assert!(
786            delivered <= 256,
787            "{delivered} bytes were delivered past a declared size of 256"
788        );
789    }
790
791    /// `Block_Maximum_Size` is min(window, 128 KiB), held on the header and on the output.
792    #[test]
793    fn zstd_blocks_are_held_to_their_frames_maximum() {
794        assert_eq!(
795            decode(&windowed(10, &[raw(true, &[1; 1024])])).expect("at the maximum"),
796            [1; 1024]
797        );
798        assert_malformed(
799            &windowed(10, &[raw(true, &[1; 1025])]),
800            "a raw block over its window",
801        );
802        assert_malformed(
803            &windowed(10, &[block(true, 1, 1025, b"x")]),
804            "an RLE block over its window",
805        );
806        let compressed = rle_literals;
807        assert_eq!(
808            decode(&windowed(10, &[compressed(true, 1000)])).expect("under the maximum"),
809            [b'A'; 1000]
810        );
811        assert_malformed(
812            &windowed(10, &[compressed(true, 2000)]),
813            "a compressed block regenerating more than its window, last",
814        );
815        assert_malformed(
816            &windowed(10, &[compressed(false, 2000), raw(true, b"z")]),
817            "a compressed block regenerating more than its window, not last",
818        );
819        assert_malformed(
820            &windowed(10, &[raw(false, &[1; 600]), compressed(true, 1025)]),
821            "a compressed block regenerating more than its window after a raw one",
822        );
823    }
824
825    /// One compressed block regenerating 13 MiB -- a hundred times any conforming block --
826    /// is refused, and by `ruzstd` 0.9.1 itself, at the first sequence past the maximum.
827    /// 0.9.0 built all of it first; see the module documentation.
828    #[test]
829    fn zstd_block_regenerating_past_128_kib_is_refused() {
830        const SEQUENCES: u8 = 100;
831        // No literals; every sequence is literal length 0, repeat offset, match length code
832        // 52 with sixteen extra bits set: 131 074 bytes each.
833        let mut content = vec![0x00, SEQUENCES, 0x54, 0, 0, 52];
834        content.extend(std::iter::repeat_n(0xff, 2 * usize::from(SEQUENCES)));
835        content.push(0x01);
836        let frame = windowed(
837            21,
838            &[
839                raw(false, b"abcdefgh"),
840                block(true, 2, content.len(), &content),
841            ],
842        );
843        assert_malformed(&frame, "a block regenerating 13 MiB");
844    }
845
846    /// One compressed block WITH sequences, written by the reference encoder: the block the
847    /// Zstandard CLI v1.5.5 (`zstd -19 --no-check`) emits for `reference_block_plaintext`,
848    /// lifted out of its frame. Everything else in this module's tests is laid out by hand;
849    /// this is the one block whose regenerated size `ruzstd` does not disclose until the
850    /// buffer passes its window.
851    const REFERENCE_BLOCK: [u8; 77] = [
852        0x42, 0x43, 0x0c, 0x11, 0xa0, 0x6f, 0x20, 0xe9, 0x9f, 0x94, 0x37, 0x01, 0x80, 0xf3, 0xc9,
853        0x9f, 0x90, 0xaa, 0xdf, 0x42, 0x1e, 0xc7, 0x14, 0x32, 0x84, 0xb7, 0x27, 0x74, 0x4c, 0x21,
854        0x43, 0x38, 0xf8, 0x69, 0xeb, 0x75, 0x8c, 0x9f, 0xaa, 0x15, 0x28, 0x75, 0x90, 0xbf, 0x55,
855        0x66, 0x92, 0xbb, 0x9b, 0x3f, 0xba, 0x17, 0x12, 0xa8, 0x10, 0xe8, 0xeb, 0xff, 0x19, 0xf0,
856        0x35, 0x03, 0x10, 0x66, 0x84, 0x1d, 0xf1, 0x7f, 0xe3, 0x03, 0xac, 0x7c, 0x2b, 0xad, 0x02,
857        0x92, 0x0c,
858    ];
859
860    /// The 600 bytes `REFERENCE_BLOCK` regenerates, generated rather than stored.
861    fn reference_block_plaintext() -> Vec<u8> {
862        let mut out = Vec::new();
863        for line in 0..20 {
864            out.extend_from_slice(format!("line {line:03} of the miss-case fixture\n").as_bytes());
865        }
866        out.truncate(600);
867        out
868    }
869
870    /// RLE literals regenerating `size` bytes of `A`, no sequences: a compressed block whose
871    /// regenerated size is readable from its four bytes.
872    fn rle_literals(last: bool, size: u16) -> Vec<u8> {
873        let header = 1 | 1 << 2 | size << 4;
874        block(
875            last,
876            2,
877            4,
878            &[&header.to_le_bytes()[..], b"A", &[0]].concat(),
879        )
880    }
881
882    #[test]
883    fn zstd_decodes_a_reference_encoded_block_with_sequences() {
884        let plain = reference_block_plaintext();
885        let reference = |last| block(last, 2, REFERENCE_BLOCK.len(), &REFERENCE_BLOCK);
886        assert_eq!(
887            decode(&windowed(10, &[reference(true)])).expect("block"),
888            plain
889        );
890        // Followed by blocks that take the buffer past its 1 KiB window.
891        let expected = [&plain[..], &[b'A'; 1000], b"end"].concat();
892        assert_eq!(
893            decode(&windowed(
894                10,
895                &[
896                    reference(false),
897                    rle_literals(false, 1000),
898                    raw(true, b"end")
899                ]
900            ))
901            .expect("three blocks"),
902            expected
903        );
904    }
905
906    /// The header's size field is held to the block maximum even when the buffer's length is
907    /// not known -- the case the regenerated-size check below cannot see.
908    ///
909    /// `ruzstd` 0.9.1 refuses the raw and the RLE block itself. The compressed one only this
910    /// module refuses: its 1 028 stored bytes are over the maximum, and the 1 025 raw literals
911    /// they hold are the literals `ruzstd` does not check.
912    #[test]
913    fn zstd_block_header_maximum_holds_when_the_buffer_length_is_unknown() {
914        let reference = block(false, 2, REFERENCE_BLOCK.len(), &REFERENCE_BLOCK);
915        let raw_literals = |size: u16| {
916            let header = 1_u16 << 2 | size << 4;
917            let stored = [
918                &header.to_le_bytes()[..],
919                &vec![b'A'; usize::from(size)],
920                &[0],
921            ]
922            .concat();
923            block(true, 2, stored.len(), &stored)
924        };
925        assert_eq!(
926            decode(&windowed(10, &[reference.clone(), raw_literals(400)]))
927                .expect("control: a compressed block of raw literals under the maximum")
928                .len(),
929            reference_block_plaintext().len() + 400
930        );
931        assert_malformed(
932            &windowed(10, &[reference.clone(), raw_literals(1025)]),
933            "a compressed block storing more than its window after a compressed one",
934        );
935        assert_malformed(
936            &windowed(10, &[reference.clone(), raw(true, &[1; 1025])]),
937            "a raw block over its window after a compressed one",
938        );
939        assert_malformed(
940            &windowed(10, &[reference, block(true, 1, 1025, b"x")]),
941            "an RLE block over its window after a compressed one",
942        );
943    }
944
945    /// THE KNOWN GAP, pinned so that it is a decision and not a surprise.
946    ///
947    /// libzstd refuses this frame ("Data corruption detected", Zstandard CLI v1.5.5): its
948    /// second block is a compressed block regenerating 1 100 bytes under a 1 024-byte window.
949    /// This module accepts it. `ruzstd` reports how much is collectable, not how much a block
950    /// produced: after the first block (600 bytes, under the window) nothing is collectable, so
951    /// the buffer's length is unknown, and after the second only `1 700 - 1 024` is, which is
952    /// what a legal 676-byte block after a full window would also show.
953    ///
954    /// The output is still exactly what the blocks encode; what is lost is the refusal of a
955    /// non-conforming frame. If this test goes red, `ruzstd` or this module got stricter:
956    /// delete it and the paragraph in the module documentation.
957    #[test]
958    fn zstd_known_gap_a_compressed_block_over_its_maximum_under_an_unfilled_window() {
959        let reference = block(false, 2, REFERENCE_BLOCK.len(), &REFERENCE_BLOCK);
960        let expected = [&reference_block_plaintext()[..], &[b'A'; 1100]].concat();
961        assert_eq!(
962            decode(&windowed(10, &[reference, rle_literals(true, 1100)]))
963                .expect("accepted, where libzstd refuses"),
964            expected
965        );
966        // The same second block is refused wherever its size is determinable.
967        assert_malformed(
968            &windowed(
969                10,
970                &[
971                    raw(false, &reference_block_plaintext()),
972                    rle_literals(true, 1100),
973                ],
974            ),
975            "the same block after a raw one",
976        );
977    }
978
979    #[test]
980    fn zstd_refuses_what_the_frame_format_reserves() {
981        let mut frame = single_segment(b"abcd");
982        frame[4] |= 8;
983        assert_malformed(&frame, "the reserved frame-descriptor bit");
984        assert_malformed(
985            &windowed(10, &[block(true, 3, 0, b"")]),
986            "the reserved block type",
987        );
988        // A dictionary this decoder was never given. Dictionary ID 0 means "none".
989        let mut frame = MAGIC.to_vec();
990        frame.extend_from_slice(&[0x01, 0x00, 0x05]);
991        frame.extend(raw(true, b"abcd"));
992        assert_malformed(&frame, "a frame naming a dictionary");
993        frame[6] = 0;
994        assert_eq!(decode(&frame).expect("dictionary ID 0"), b"abcd");
995    }
996
997    /// A caller that stops reading when its buffer is full still gets the frame's verdict.
998    #[test]
999    fn zstd_last_bytes_carry_the_frames_verdict() {
1000        let mut frame = MAGIC.to_vec();
1001        frame.extend_from_slice(&[0x04, 0x00]);
1002        frame.extend(raw(true, b"abcd"));
1003        frame.extend_from_slice(&0xd25d_92cd_u32.to_le_bytes());
1004        let arena = DecodeArena::new();
1005        let (ctx, root) =
1006            DecodeContext::from_root_bytes(&frame, &arena, &DecodePolicy::service()).expect("root");
1007        let mut decoder = ctx.open_zstd(root).expect("decoder");
1008        let mut output = [0; 4];
1009        assert!(matches!(
1010            decoder.read_chunk(&mut output),
1011            Err(CodecError::Malformed(_))
1012        ));
1013    }
1014}