Skip to main content

zstd_zero/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![forbid(unsafe_code)]
4
5#[cfg(test)]
6extern crate std;
7
8mod bitstream;
9mod error;
10mod fse;
11mod huffman;
12mod xxhash;
13
14use bitstream::BackwardBits;
15pub use error::DecodeError;
16use fse::Table as FseTable;
17use huffman::Table as HuffmanTable;
18use xxhash::XxHash64;
19
20pub use fse::Entry as FseEntry;
21pub use huffman::Entry as HuffmanEntry;
22/// FSE slots required for three sequence tables and Huffman weight scratch.
23pub const FSE_ENTRIES: usize = 4 * 512;
24/// Number of scratch counters shared by all FSE table constructors.
25/// Contents may be arbitrary on entry and are not retained across tables.
26pub const FSE_SCRATCH_LEN: usize = fse::MAX_SYMBOLS;
27/// Huffman slots required for literal decoding.
28pub const HUFFMAN_ENTRIES: usize = 2048;
29
30pub const MAX_BLOCK_SIZE: usize = 128 * 1024;
31pub const MAX_FRAME_HEADER_SIZE: usize = 18;
32
33const ZSTD_MAGIC: u32 = 0xfd2f_b528;
34const SKIPPABLE_MAGIC_MIN: u32 = 0x184d_2a50;
35const SKIPPABLE_MAGIC_MAX: u32 = 0x184d_2a5f;
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub struct FrameHeader {
39    pub window_size: u64,
40    pub content_size: Option<u64>,
41    pub dictionary_id: u32,
42    pub has_checksum: bool,
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub enum StreamHeader {
47    Zstandard(FrameHeader),
48    Skippable { magic: u32, size: u32 },
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum FrameKind {
53    Zstandard,
54    Skippable { magic: u32, size: u32 },
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub enum HeaderStatus {
59    NeedMore { minimum: usize },
60    Complete { header: StreamHeader, size: usize },
61}
62
63#[derive(Debug, Eq, PartialEq)]
64pub enum DecodeStep<'a> {
65    NeedInput {
66        consumed: usize,
67    },
68    FrameStarted {
69        consumed: usize,
70        header: StreamHeader,
71    },
72    Output {
73        consumed: usize,
74        bytes: &'a [u8],
75    },
76    FrameFinished {
77        consumed: usize,
78        kind: FrameKind,
79    },
80}
81
82impl DecodeStep<'_> {
83    pub const fn consumed(&self) -> usize {
84        match self {
85            Self::NeedInput { consumed }
86            | Self::FrameStarted { consumed, .. }
87            | Self::Output { consumed, .. }
88            | Self::FrameFinished { consumed, .. } => *consumed,
89        }
90    }
91}
92
93/// A failure while driving a [`Decoder`] through its callback API.
94#[derive(Debug, Eq, PartialEq)]
95pub enum StreamError<E> {
96    Decode(DecodeError),
97    Output(E),
98    DecoderStalled,
99}
100
101impl<E: core::fmt::Display> core::fmt::Display for StreamError<E> {
102    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
103        match self {
104            Self::Decode(error) => write!(formatter, "Zstandard decode failed: {error}"),
105            Self::Output(error) => write!(formatter, "Zstandard output failed: {error}"),
106            Self::DecoderStalled => {
107                formatter.write_str("Zstandard decoder stopped making progress")
108            }
109        }
110    }
111}
112
113enum InternalStep {
114    NeedInput {
115        consumed: usize,
116    },
117    FrameStarted {
118        consumed: usize,
119        header: StreamHeader,
120    },
121    Output {
122        consumed: usize,
123        start: usize,
124        length: usize,
125    },
126    FrameFinished {
127        consumed: usize,
128        kind: FrameKind,
129    },
130}
131
132/// Initialized caller-owned storage, retained for the decoder's lifetime.
133pub struct DecoderBuffers<'a> {
134    /// At least [`FSE_SCRATCH_LEN`] elements for probabilities and next states.
135    pub fse_scratch: &'a mut [i16],
136    /// At least [`FSE_ENTRIES`] entries, including Huffman weight scratch.
137    pub fse: &'a mut [FseEntry],
138    /// At least [`HUFFMAN_ENTRIES`] entries.
139    pub huffman: &'a mut [HuffmanEntry],
140    pub history: &'a mut [u8],
141    pub block: &'a mut [u8],
142    pub literals: &'a mut [u8],
143}
144
145/// Tuning knobs for a [`Decoder`].
146///
147/// [`Default`] enables every validation check; prefer it unless you have a
148/// specific reason not to.
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub struct DecoderOptions {
151    /// Reject Huffman literal streams that do not end exactly on their final bit.
152    ///
153    /// Enabled by default. Disabling it makes the decoder tolerate a truncated
154    /// literal bitstream by padding the remaining symbols instead of failing,
155    /// which is only useful for best-effort salvage of damaged data.
156    ///
157    /// # Warning
158    ///
159    /// With this disabled, a corrupt frame can decode "successfully" to bytes
160    /// that differ from what other zstd implementations produce, with no error
161    /// reported. Never disable it for content-addressed or otherwise
162    /// security-sensitive input.
163    pub strict_literal_bitstream: bool,
164}
165
166impl Default for DecoderOptions {
167    fn default() -> Self {
168        Self {
169            strict_literal_bitstream: true,
170        }
171    }
172}
173
174#[derive(Clone, Copy)]
175enum State {
176    Header,
177    Skippable {
178        magic: u32,
179        size: u32,
180        remaining: usize,
181    },
182    BlockHeader,
183    BlockPayload {
184        header: BlockHeader,
185        filled: usize,
186    },
187    Checksum {
188        filled: usize,
189    },
190    FrameDone(FrameKind),
191}
192
193#[derive(Clone, Copy)]
194struct BlockHeader {
195    last: bool,
196    kind: BlockKind,
197    size: usize,
198}
199
200#[derive(Clone, Copy, Eq, PartialEq)]
201enum BlockKind {
202    Raw,
203    Rle,
204    Compressed,
205}
206
207pub struct Decoder<'a> {
208    fse_scratch: &'a mut [i16],
209    options: DecoderOptions,
210    history: &'a mut [u8],
211    block: &'a mut [u8],
212    literals: &'a mut [u8],
213    state: State,
214    poisoned: bool,
215    completed_frames: u64,
216    header_buffer: [u8; MAX_FRAME_HEADER_SIZE],
217    header_len: usize,
218    block_header_buffer: [u8; 3],
219    block_header_len: usize,
220    checksum_buffer: [u8; 4],
221    current_header: Option<FrameHeader>,
222    block_limit: usize,
223    history_position: usize,
224    frame_output: u64,
225    pending_start: usize,
226    pending_len: usize,
227    offsets: [u32; 3],
228    checksum: XxHash64,
229    huffman: HuffmanTable<'a>,
230    literal_lengths: FseTable<'a>,
231    offsets_table: FseTable<'a>,
232    match_lengths: FseTable<'a>,
233}
234
235impl<'a> Decoder<'a> {
236    /// Create a decoder with every validation check enabled.
237    ///
238    /// Returns [`DecodeError::InvalidEntropyTable`] if entropy storage is too short.
239    pub fn new(buffers: DecoderBuffers<'a>) -> Result<Self, DecodeError> {
240        Self::with_options(buffers, DecoderOptions::default())
241    }
242
243    /// Create a decoder with explicit [`DecoderOptions`].
244    ///
245    /// Returns [`DecodeError::InvalidEntropyTable`] if entropy storage is too short.
246    pub fn with_options(
247        buffers: DecoderBuffers<'a>,
248        options: DecoderOptions,
249    ) -> Result<Self, DecodeError> {
250        if buffers.fse.len() < FSE_ENTRIES
251            || buffers.huffman.len() < HUFFMAN_ENTRIES
252            || buffers.fse_scratch.len() < FSE_SCRATCH_LEN
253        {
254            return Err(DecodeError::InvalidEntropyTable);
255        }
256        let (scratch, rest) = buffers.fse[..FSE_ENTRIES].split_at_mut(512);
257        let (literal, rest) = rest.split_at_mut(512);
258        let (offsets, matches) = rest.split_at_mut(512);
259        Ok(Self {
260            fse_scratch: &mut buffers.fse_scratch[..FSE_SCRATCH_LEN],
261            options,
262            history: buffers.history,
263            block: buffers.block,
264            literals: buffers.literals,
265            state: State::Header,
266            poisoned: false,
267            completed_frames: 0,
268            header_buffer: [0; MAX_FRAME_HEADER_SIZE],
269            header_len: 0,
270            block_header_buffer: [0; 3],
271            block_header_len: 0,
272            checksum_buffer: [0; 4],
273            current_header: None,
274            block_limit: 0,
275            history_position: 0,
276            frame_output: 0,
277            pending_start: 0,
278            pending_len: 0,
279            offsets: [1, 4, 8],
280            checksum: XxHash64::new(),
281            huffman: HuffmanTable::new(
282                &mut buffers.huffman[..HUFFMAN_ENTRIES],
283                FseTable::new(scratch),
284            ),
285            literal_lengths: FseTable::new(literal),
286            offsets_table: FseTable::new(offsets),
287            match_lengths: FseTable::new(matches),
288        })
289    }
290
291    pub fn decode<'decoder>(
292        &'decoder mut self,
293        input: &[u8],
294    ) -> Result<DecodeStep<'decoder>, DecodeError> {
295        if self.poisoned {
296            return Err(DecodeError::DecoderPoisoned);
297        }
298        let step = match self.decode_inner(input) {
299            Ok(step) => step,
300            Err(error) => {
301                self.poisoned = true;
302                return Err(error);
303            }
304        };
305        Ok(match step {
306            InternalStep::NeedInput { consumed } => DecodeStep::NeedInput { consumed },
307            InternalStep::FrameStarted { consumed, header } => {
308                DecodeStep::FrameStarted { consumed, header }
309            }
310            InternalStep::Output {
311                consumed,
312                start,
313                length,
314            } => DecodeStep::Output {
315                consumed,
316                bytes: &self.history[start..start + length],
317            },
318            InternalStep::FrameFinished { consumed, kind } => {
319                DecodeStep::FrameFinished { consumed, kind }
320            }
321        })
322    }
323
324    /// Consumes an input fragment and sends decoded bytes to `output`.
325    ///
326    /// This is the convenient callback-driven counterpart to [`decode`](Self::decode).
327    /// Frame events are handled internally while output errors remain distinct
328    /// from compressed-stream errors.
329    pub fn push<E>(
330        &mut self,
331        mut input: &[u8],
332        mut output: impl FnMut(&[u8]) -> Result<(), E>,
333    ) -> Result<(), StreamError<E>> {
334        let mut idle_steps = 0;
335        while !input.is_empty() {
336            let step = self.decode(input).map_err(StreamError::Decode)?;
337            let consumed = step.consumed();
338            if consumed > input.len() {
339                return Err(StreamError::DecoderStalled);
340            }
341            let needs_input = matches!(step, DecodeStep::NeedInput { .. });
342            let produced = match step {
343                DecodeStep::Output { bytes, .. } => {
344                    if bytes.is_empty() {
345                        false
346                    } else {
347                        output(bytes).map_err(StreamError::Output)?;
348                        true
349                    }
350                }
351                _ => false,
352            };
353            input = &input[consumed..];
354            if needs_input && !input.is_empty() {
355                return Err(StreamError::DecoderStalled);
356            }
357            if consumed != 0 || produced {
358                idle_steps = 0;
359            } else {
360                idle_steps += 1;
361                if idle_steps > 16 {
362                    return Err(StreamError::DecoderStalled);
363                }
364            }
365        }
366        Ok(())
367    }
368
369    /// Drains buffered output and validates the end of the stream.
370    pub fn finish_with<E>(
371        &mut self,
372        mut output: impl FnMut(&[u8]) -> Result<(), E>,
373    ) -> Result<(), StreamError<E>> {
374        let mut idle_steps = 0;
375        loop {
376            match self.decode(&[]).map_err(StreamError::Decode)? {
377                DecodeStep::NeedInput { .. } => break,
378                DecodeStep::Output { bytes, .. } if !bytes.is_empty() => {
379                    output(bytes).map_err(StreamError::Output)?;
380                    idle_steps = 0;
381                }
382                _ => {
383                    idle_steps += 1;
384                    if idle_steps > 16 {
385                        return Err(StreamError::DecoderStalled);
386                    }
387                }
388            }
389        }
390        self.finish().map_err(StreamError::Decode)
391    }
392
393    pub fn finish(&self) -> Result<(), DecodeError> {
394        if self.poisoned {
395            return Err(DecodeError::DecoderPoisoned);
396        }
397        if self.pending_len != 0 || self.header_len != 0 || self.completed_frames == 0 {
398            return Err(DecodeError::UnexpectedEof);
399        }
400        match self.state {
401            State::Header | State::FrameDone(_) => Ok(()),
402            _ => Err(DecodeError::UnexpectedEof),
403        }
404    }
405
406    pub fn reset(&mut self) {
407        self.state = State::Header;
408        self.poisoned = false;
409        self.completed_frames = 0;
410        self.header_len = 0;
411        self.block_header_len = 0;
412        self.current_header = None;
413        self.frame_output = 0;
414        self.pending_len = 0;
415        self.offsets = [1, 4, 8];
416        self.checksum = XxHash64::new();
417        self.huffman.reset();
418        self.literal_lengths.reset();
419        self.offsets_table.reset();
420        self.match_lengths.reset();
421    }
422
423    fn decode_inner(&mut self, input: &[u8]) -> Result<InternalStep, DecodeError> {
424        if self.pending_len != 0 {
425            let start = self.pending_start;
426            let length = core::cmp::min(self.pending_len, self.history.len() - start);
427            self.pending_start = (start + length) % self.history.len();
428            self.pending_len -= length;
429            return Ok(InternalStep::Output {
430                consumed: 0,
431                start,
432                length,
433            });
434        }
435
436        let mut consumed = 0usize;
437        loop {
438            match self.state {
439                State::Header => match inspect_frame(&self.header_buffer[..self.header_len])? {
440                    HeaderStatus::NeedMore { minimum } => {
441                        if consumed == input.len() {
442                            return Ok(InternalStep::NeedInput { consumed });
443                        }
444                        let amount =
445                            core::cmp::min(minimum - self.header_len, input.len() - consumed);
446                        self.header_buffer[self.header_len..self.header_len + amount]
447                            .copy_from_slice(&input[consumed..consumed + amount]);
448                        self.header_len += amount;
449                        consumed += amount;
450                    }
451                    HeaderStatus::Complete { header, .. } => {
452                        self.header_len = 0;
453                        match header {
454                            StreamHeader::Zstandard(frame) => {
455                                self.start_frame(frame)?;
456                                self.state = State::BlockHeader;
457                            }
458                            StreamHeader::Skippable { magic, size } => {
459                                self.state = State::Skippable {
460                                    magic,
461                                    size,
462                                    remaining: size as usize,
463                                };
464                            }
465                        }
466                        return Ok(InternalStep::FrameStarted { consumed, header });
467                    }
468                },
469                State::Skippable {
470                    magic,
471                    size,
472                    remaining,
473                } => {
474                    let amount = core::cmp::min(remaining, input.len() - consumed);
475                    consumed += amount;
476                    let remaining = remaining - amount;
477                    if remaining == 0 {
478                        self.state = State::FrameDone(FrameKind::Skippable { magic, size });
479                    } else {
480                        self.state = State::Skippable {
481                            magic,
482                            size,
483                            remaining,
484                        };
485                        return Ok(InternalStep::NeedInput { consumed });
486                    }
487                }
488                State::BlockHeader => {
489                    let amount = core::cmp::min(3 - self.block_header_len, input.len() - consumed);
490                    self.block_header_buffer[self.block_header_len..self.block_header_len + amount]
491                        .copy_from_slice(&input[consumed..consumed + amount]);
492                    self.block_header_len += amount;
493                    consumed += amount;
494                    if self.block_header_len != 3 {
495                        return Ok(InternalStep::NeedInput { consumed });
496                    }
497                    self.block_header_len = 0;
498                    let header = parse_block_header(self.block_header_buffer, self.block_limit)?;
499                    let payload_size = if header.kind == BlockKind::Rle {
500                        1
501                    } else {
502                        header.size
503                    };
504                    if payload_size > self.block.len() {
505                        return Err(DecodeError::BlockScratchTooSmall {
506                            required: payload_size,
507                            provided: self.block.len(),
508                        });
509                    }
510                    self.state = State::BlockPayload { header, filled: 0 };
511                }
512                State::BlockPayload { header, filled } => {
513                    let payload_size = if header.kind == BlockKind::Rle {
514                        1
515                    } else {
516                        header.size
517                    };
518                    let amount = core::cmp::min(payload_size - filled, input.len() - consumed);
519                    self.block[filled..filled + amount]
520                        .copy_from_slice(&input[consumed..consumed + amount]);
521                    consumed += amount;
522                    let filled = filled + amount;
523                    if filled != payload_size {
524                        self.state = State::BlockPayload { header, filled };
525                        return Ok(InternalStep::NeedInput { consumed });
526                    }
527                    self.process_block(header)?;
528                    self.state = if header.last {
529                        if self.current_header()?.has_checksum {
530                            self.checksum_buffer = [0; 4];
531                            State::Checksum { filled: 0 }
532                        } else {
533                            self.validate_frame_end()?;
534                            State::FrameDone(FrameKind::Zstandard)
535                        }
536                    } else {
537                        State::BlockHeader
538                    };
539                    if self.pending_len != 0 {
540                        let start = self.pending_start;
541                        let length = core::cmp::min(self.pending_len, self.history.len() - start);
542                        self.pending_start = (start + length) % self.history.len();
543                        self.pending_len -= length;
544                        return Ok(InternalStep::Output {
545                            consumed,
546                            start,
547                            length,
548                        });
549                    }
550                }
551                State::Checksum { filled } => {
552                    let amount = core::cmp::min(4 - filled, input.len() - consumed);
553                    self.checksum_buffer[filled..filled + amount]
554                        .copy_from_slice(&input[consumed..consumed + amount]);
555                    consumed += amount;
556                    let filled = filled + amount;
557                    if filled != 4 {
558                        self.state = State::Checksum { filled };
559                        return Ok(InternalStep::NeedInput { consumed });
560                    }
561                    let expected = u32::from_le_bytes(self.checksum_buffer);
562                    let actual = self.checksum.digest() as u32;
563                    if actual != expected {
564                        return Err(DecodeError::ChecksumMismatch { expected, actual });
565                    }
566                    self.validate_frame_end()?;
567                    self.state = State::FrameDone(FrameKind::Zstandard);
568                }
569                State::FrameDone(kind) => {
570                    self.completed_frames = self
571                        .completed_frames
572                        .checked_add(1)
573                        .ok_or(DecodeError::ArithmeticOverflow)?;
574                    self.current_header = None;
575                    self.state = State::Header;
576                    return Ok(InternalStep::FrameFinished { consumed, kind });
577                }
578            }
579        }
580    }
581
582    fn start_frame(&mut self, header: FrameHeader) -> Result<(), DecodeError> {
583        if header.dictionary_id != 0 {
584            return Err(DecodeError::UnsupportedDictionary {
585                id: header.dictionary_id,
586            });
587        }
588        let window =
589            usize::try_from(header.window_size).map_err(|_| DecodeError::WindowTooLarge)?;
590        if window > self.history.len() {
591            return Err(DecodeError::HistoryTooSmall {
592                required: window,
593                provided: self.history.len(),
594            });
595        }
596        self.current_header = Some(header);
597        self.block_limit = core::cmp::min(window, MAX_BLOCK_SIZE);
598        self.frame_output = 0;
599        self.pending_len = 0;
600        self.offsets = [1, 4, 8];
601        self.checksum = XxHash64::new();
602        self.huffman.reset();
603        self.literal_lengths.reset();
604        self.offsets_table.reset();
605        self.match_lengths.reset();
606        Ok(())
607    }
608
609    fn current_header(&self) -> Result<FrameHeader, DecodeError> {
610        self.current_header.ok_or(DecodeError::InvalidFrameHeader)
611    }
612
613    fn process_block(&mut self, header: BlockHeader) -> Result<(), DecodeError> {
614        let start = self.history_position;
615        let before = self.frame_output;
616        match header.kind {
617            BlockKind::Raw => {
618                for index in 0..header.size {
619                    self.write_byte(self.block[index])?;
620                }
621            }
622            BlockKind::Rle => {
623                let byte = self.block[0];
624                for _ in 0..header.size {
625                    self.write_byte(byte)?;
626                }
627            }
628            BlockKind::Compressed => self.decode_compressed(header.size)?,
629        }
630        let produced = usize::try_from(self.frame_output - before)
631            .map_err(|_| DecodeError::ArithmeticOverflow)?;
632        if produced > self.block_limit {
633            return Err(DecodeError::InvalidBlock);
634        }
635        self.record_output(start, produced);
636        Ok(())
637    }
638
639    fn decode_compressed(&mut self, size: usize) -> Result<(), DecodeError> {
640        let (literal_count, consumed) = decode_literals(
641            &self.block[..size],
642            self.literals,
643            &mut self.huffman,
644            self.fse_scratch,
645            self.block_limit,
646            self.options.strict_literal_bitstream,
647        )?;
648        let sequence_input = &self.block[consumed..size];
649        let (sequence_count, mut position) = parse_sequence_count(sequence_input)?;
650        if sequence_count == 0 {
651            if position != sequence_input.len() {
652                return Err(DecodeError::InvalidBlock);
653            }
654            let window_size = self.current_header()?.window_size;
655            let mut writer = HistoryWriter {
656                history: self.history,
657                position: &mut self.history_position,
658                frame_output: &mut self.frame_output,
659                window_size,
660            };
661            for byte in &self.literals[..literal_count] {
662                writer.write(*byte)?;
663            }
664            return Ok(());
665        }
666        let modes = *sequence_input
667            .get(position)
668            .ok_or(DecodeError::InvalidBlock)?;
669        position += 1;
670        if modes & 3 != 0 {
671            return Err(DecodeError::InvalidBlock);
672        }
673        position += build_sequence_table(
674            &mut self.literal_lengths,
675            self.fse_scratch,
676            modes >> 6,
677            &sequence_input[position..],
678            &LL_DEFAULT,
679            6,
680            35,
681            9,
682        )?;
683        position += build_sequence_table(
684            &mut self.offsets_table,
685            self.fse_scratch,
686            (modes >> 4) & 3,
687            &sequence_input[position..],
688            &OF_DEFAULT,
689            5,
690            31,
691            8,
692        )?;
693        position += build_sequence_table(
694            &mut self.match_lengths,
695            self.fse_scratch,
696            (modes >> 2) & 3,
697            &sequence_input[position..],
698            &ML_DEFAULT,
699            6,
700            52,
701            9,
702        )?;
703        let mut bits = BackwardBits::new(
704            sequence_input
705                .get(position..)
706                .ok_or(DecodeError::InvalidBitstream)?,
707        )?;
708        let mut ll_state = bits.read(self.literal_lengths.log())?;
709        let mut of_state = bits.read(self.offsets_table.log())?;
710        let mut ml_state = bits.read(self.match_lengths.log())?;
711        let mut literal_position = 0usize;
712        let window_size = self.current_header()?.window_size;
713        let literal_lengths = &self.literal_lengths;
714        let offsets_table = &self.offsets_table;
715        let match_lengths = &self.match_lengths;
716        let literals = &self.literals[..literal_count];
717        let repeated_offsets = &mut self.offsets;
718        let mut writer = HistoryWriter {
719            history: self.history,
720            position: &mut self.history_position,
721            frame_output: &mut self.frame_output,
722            window_size,
723        };
724
725        for sequence in 0..sequence_count {
726            let ll_code = literal_lengths.symbol(ll_state)? as usize;
727            let of_code = offsets_table.symbol(of_state)?;
728            let ml_code = match_lengths.symbol(ml_state)? as usize;
729            if ll_code >= LL_BASE.len() || ml_code >= ML_BASE.len() || of_code > 31 {
730                return Err(DecodeError::InvalidEntropyTable);
731            }
732            let raw_offset = (1u32 << of_code)
733                .checked_add(bits.read(of_code)?)
734                .ok_or(DecodeError::ArithmeticOverflow)?;
735            let match_length = ML_BASE[ml_code]
736                .checked_add(bits.read(ML_BITS[ml_code])?)
737                .ok_or(DecodeError::ArithmeticOverflow)?;
738            let literal_length = LL_BASE[ll_code]
739                .checked_add(bits.read(LL_BITS[ll_code])?)
740                .ok_or(DecodeError::ArithmeticOverflow)?;
741            let literal_end = literal_position
742                .checked_add(literal_length as usize)
743                .ok_or(DecodeError::ArithmeticOverflow)?;
744            if literal_end > literal_count {
745                return Err(DecodeError::InvalidBlock);
746            }
747            for byte in &literals[literal_position..literal_end] {
748                writer.write(*byte)?;
749            }
750            literal_position = literal_end;
751            let offset = resolve_offset(raw_offset, literal_length, repeated_offsets)?;
752            writer.copy_match(offset as usize, match_length as usize)?;
753
754            if sequence + 1 != sequence_count {
755                literal_lengths.update(&mut ll_state, &mut bits)?;
756                match_lengths.update(&mut ml_state, &mut bits)?;
757                offsets_table.update(&mut of_state, &mut bits)?;
758            }
759        }
760        if bits.remaining() != 0 {
761            return Err(DecodeError::InvalidBitstream);
762        }
763        for byte in &literals[literal_position..literal_count] {
764            writer.write(*byte)?;
765        }
766        Ok(())
767    }
768
769    fn write_byte(&mut self, byte: u8) -> Result<(), DecodeError> {
770        write_history_byte(
771            self.history,
772            &mut self.history_position,
773            &mut self.frame_output,
774            byte,
775        )
776    }
777
778    fn record_output(&mut self, start: usize, length: usize) {
779        if length == 0 {
780            return;
781        }
782        let first = core::cmp::min(length, self.history.len() - start);
783        self.checksum.update(&self.history[start..start + first]);
784        if first != length {
785            self.checksum.update(&self.history[..length - first]);
786        }
787        self.pending_start = start;
788        self.pending_len = length;
789    }
790
791    fn validate_frame_end(&self) -> Result<(), DecodeError> {
792        if let Some(expected) = self.current_header()?.content_size {
793            if self.frame_output != expected {
794                return Err(DecodeError::ContentSizeMismatch {
795                    expected,
796                    actual: self.frame_output,
797                });
798            }
799        }
800        Ok(())
801    }
802}
803
804struct HistoryWriter<'a> {
805    history: &'a mut [u8],
806    position: &'a mut usize,
807    frame_output: &'a mut u64,
808    window_size: u64,
809}
810
811impl HistoryWriter<'_> {
812    fn write(&mut self, byte: u8) -> Result<(), DecodeError> {
813        write_history_byte(self.history, self.position, self.frame_output, byte)
814    }
815
816    fn copy_match(&mut self, offset: usize, length: usize) -> Result<(), DecodeError> {
817        let available = core::cmp::min(*self.frame_output, self.window_size);
818        if offset == 0 || offset as u64 > available {
819            return Err(DecodeError::InvalidOffset);
820        }
821        for _ in 0..length {
822            let source = (*self.position + self.history.len() - offset) % self.history.len();
823            self.write(self.history[source])?;
824        }
825        Ok(())
826    }
827}
828
829fn write_history_byte(
830    history: &mut [u8],
831    position: &mut usize,
832    frame_output: &mut u64,
833    byte: u8,
834) -> Result<(), DecodeError> {
835    if history.is_empty() {
836        return Err(DecodeError::HistoryTooSmall {
837            required: 1,
838            provided: 0,
839        });
840    }
841    history[*position] = byte;
842    *position += 1;
843    if *position == history.len() {
844        *position = 0;
845    }
846    *frame_output = frame_output
847        .checked_add(1)
848        .ok_or(DecodeError::ArithmeticOverflow)?;
849    Ok(())
850}
851
852pub fn inspect_frame(input: &[u8]) -> Result<HeaderStatus, DecodeError> {
853    if input.len() < 4 {
854        return Ok(HeaderStatus::NeedMore { minimum: 4 });
855    }
856    let magic = read_u32(input);
857    if (SKIPPABLE_MAGIC_MIN..=SKIPPABLE_MAGIC_MAX).contains(&magic) {
858        if input.len() < 8 {
859            return Ok(HeaderStatus::NeedMore { minimum: 8 });
860        }
861        return Ok(HeaderStatus::Complete {
862            header: StreamHeader::Skippable {
863                magic,
864                size: read_u32(&input[4..]),
865            },
866            size: 8,
867        });
868    }
869    if magic != ZSTD_MAGIC {
870        return Err(DecodeError::InvalidMagic);
871    }
872    if input.len() < 5 {
873        return Ok(HeaderStatus::NeedMore { minimum: 5 });
874    }
875    let descriptor = input[4];
876    if descriptor & 0x08 != 0 {
877        return Err(DecodeError::InvalidFrameHeader);
878    }
879    let single_segment = descriptor & 0x20 != 0;
880    let dictionary_size = [0usize, 1, 2, 4][(descriptor & 3) as usize];
881    let content_flag = descriptor >> 6;
882    let content_size_bytes = match content_flag {
883        0 if single_segment => 1,
884        0 => 0,
885        1 => 2,
886        2 => 4,
887        _ => 8,
888    };
889    let size = 5 + usize::from(!single_segment) + dictionary_size + content_size_bytes;
890    if input.len() < size {
891        return Ok(HeaderStatus::NeedMore { minimum: size });
892    }
893    let mut position = 5usize;
894    let encoded_window = if single_segment {
895        None
896    } else {
897        let value = input[position];
898        position += 1;
899        let exponent = (value >> 3) as u32;
900        let base = 1u64 << (10 + exponent);
901        Some(base + (base / 8) * (value as u64 & 7))
902    };
903    let dictionary_id = read_variable(&input[position..position + dictionary_size]);
904    position += dictionary_size;
905    let content_size = if content_size_bytes == 0 {
906        None
907    } else {
908        let mut value = read_variable_u64(&input[position..position + content_size_bytes]);
909        if content_size_bytes == 2 {
910            value += 256;
911        }
912        Some(value)
913    };
914    let window_size = if single_segment {
915        content_size.ok_or(DecodeError::InvalidFrameHeader)?
916    } else {
917        encoded_window.ok_or(DecodeError::InvalidFrameHeader)?
918    };
919    Ok(HeaderStatus::Complete {
920        header: StreamHeader::Zstandard(FrameHeader {
921            window_size,
922            content_size,
923            dictionary_id,
924            has_checksum: descriptor & 4 != 0,
925        }),
926        size,
927    })
928}
929
930fn parse_block_header(bytes: [u8; 3], limit: usize) -> Result<BlockHeader, DecodeError> {
931    let value = bytes[0] as u32 | (bytes[1] as u32) << 8 | (bytes[2] as u32) << 16;
932    let kind = match (value >> 1) & 3 {
933        0 => BlockKind::Raw,
934        1 => BlockKind::Rle,
935        2 => BlockKind::Compressed,
936        _ => return Err(DecodeError::InvalidBlock),
937    };
938    let size = (value >> 3) as usize;
939    if size > limit {
940        return Err(DecodeError::InvalidBlock);
941    }
942    Ok(BlockHeader {
943        last: value & 1 != 0,
944        kind,
945        size,
946    })
947}
948
949fn decode_literals(
950    input: &[u8],
951    output: &mut [u8],
952    table: &mut HuffmanTable<'_>,
953    scratch: &mut [i16],
954    block_limit: usize,
955    strict: bool,
956) -> Result<(usize, usize), DecodeError> {
957    let first = *input.first().ok_or(DecodeError::InvalidBlock)?;
958    let kind = first & 3;
959    let format = (first >> 2) & 3;
960    if kind <= 1 {
961        let (regenerated, header_size): (usize, usize) = match format {
962            0 | 2 => ((first >> 3) as usize, 1),
963            1 => {
964                let second = *input.get(1).ok_or(DecodeError::InvalidBlock)?;
965                (((first >> 4) as usize) | ((second as usize) << 4), 2)
966            }
967            _ => {
968                let second = *input.get(1).ok_or(DecodeError::InvalidBlock)?;
969                let third = *input.get(2).ok_or(DecodeError::InvalidBlock)?;
970                (
971                    ((first >> 4) as usize) | ((second as usize) << 4) | ((third as usize) << 12),
972                    3,
973                )
974            }
975        };
976        ensure_literal_space(regenerated, output.len(), block_limit)?;
977        if kind == 0 {
978            let end = header_size
979                .checked_add(regenerated)
980                .ok_or(DecodeError::ArithmeticOverflow)?;
981            let source = input
982                .get(header_size..end)
983                .ok_or(DecodeError::InvalidBlock)?;
984            output[..regenerated].copy_from_slice(source);
985            Ok((regenerated, end))
986        } else {
987            let value = *input.get(header_size).ok_or(DecodeError::InvalidBlock)?;
988            output[..regenerated].fill(value);
989            Ok((regenerated, header_size + 1))
990        }
991    } else {
992        let (regenerated, compressed, streams, header_size): (usize, usize, usize, usize) =
993            match format {
994                0 | 1 => {
995                    if input.len() < 3 {
996                        return Err(DecodeError::InvalidBlock);
997                    }
998                    let combined =
999                        input[0] as u32 | (input[1] as u32) << 8 | (input[2] as u32) << 16;
1000                    (
1001                        ((combined >> 4) & 0x3ff) as usize,
1002                        ((combined >> 14) & 0x3ff) as usize,
1003                        if format == 0 { 1 } else { 4 },
1004                        3,
1005                    )
1006                }
1007                2 => {
1008                    if input.len() < 4 {
1009                        return Err(DecodeError::InvalidBlock);
1010                    }
1011                    let combined = read_u32(input);
1012                    (
1013                        ((combined >> 4) & 0x3fff) as usize,
1014                        ((combined >> 18) & 0x3fff) as usize,
1015                        4,
1016                        4,
1017                    )
1018                }
1019                _ => {
1020                    if input.len() < 5 {
1021                        return Err(DecodeError::InvalidBlock);
1022                    }
1023                    let combined = read_variable_u64(&input[..5]);
1024                    (
1025                        ((combined >> 4) & 0x3ffff) as usize,
1026                        ((combined >> 22) & 0x3ffff) as usize,
1027                        4,
1028                        5,
1029                    )
1030                }
1031            };
1032        ensure_literal_space(regenerated, output.len(), block_limit)?;
1033        let end = header_size
1034            .checked_add(compressed)
1035            .ok_or(DecodeError::ArithmeticOverflow)?;
1036        let mut encoded = input
1037            .get(header_size..end)
1038            .ok_or(DecodeError::InvalidBlock)?;
1039        if kind == 2 {
1040            let table_size = table.read_description(encoded, scratch)?;
1041            encoded = &encoded[table_size..];
1042        } else if !table.is_valid() {
1043            return Err(DecodeError::InvalidEntropyTable);
1044        }
1045        if streams == 1 {
1046            table.decode(encoded, &mut output[..regenerated], strict)?;
1047        } else {
1048            table.decode_four(encoded, &mut output[..regenerated], strict)?;
1049        }
1050        Ok((regenerated, end))
1051    }
1052}
1053
1054fn ensure_literal_space(
1055    required: usize,
1056    provided: usize,
1057    block_limit: usize,
1058) -> Result<(), DecodeError> {
1059    if required > block_limit {
1060        return Err(DecodeError::InvalidBlock);
1061    }
1062    if required > provided {
1063        return Err(DecodeError::LiteralScratchTooSmall { required, provided });
1064    }
1065    Ok(())
1066}
1067
1068fn parse_sequence_count(input: &[u8]) -> Result<(usize, usize), DecodeError> {
1069    let first = *input.first().ok_or(DecodeError::InvalidBlock)? as usize;
1070    match first {
1071        0 => Ok((0, 1)),
1072        1..=127 => Ok((first, 1)),
1073        128..=254 => Ok((
1074            ((first - 128) << 8) + *input.get(1).ok_or(DecodeError::InvalidBlock)? as usize,
1075            2,
1076        )),
1077        _ => Ok((
1078            0x7f00
1079                + u16::from_le_bytes([
1080                    *input.get(1).ok_or(DecodeError::InvalidBlock)?,
1081                    *input.get(2).ok_or(DecodeError::InvalidBlock)?,
1082                ]) as usize,
1083            3,
1084        )),
1085    }
1086}
1087
1088#[allow(clippy::too_many_arguments)]
1089fn build_sequence_table(
1090    table: &mut FseTable<'_>,
1091    scratch: &mut [i16],
1092    mode: u8,
1093    input: &[u8],
1094    predefined: &[i16],
1095    predefined_log: u8,
1096    max_symbol: usize,
1097    max_log: u8,
1098) -> Result<usize, DecodeError> {
1099    match mode {
1100        0 => {
1101            table.build(predefined, predefined_log, scratch)?;
1102            Ok(0)
1103        }
1104        1 => {
1105            table.build_rle(*input.first().ok_or(DecodeError::InvalidEntropyTable)?);
1106            Ok(1)
1107        }
1108        2 => table.read_description(input, max_symbol, max_log, scratch),
1109        3 if table.is_valid() => Ok(0),
1110        _ => Err(DecodeError::InvalidEntropyTable),
1111    }
1112}
1113
1114fn resolve_offset(
1115    encoded: u32,
1116    literal_length: u32,
1117    repeated: &mut [u32; 3],
1118) -> Result<u32, DecodeError> {
1119    let value = if encoded > 3 {
1120        let value = encoded - 3;
1121        repeated[2] = repeated[1];
1122        repeated[1] = repeated[0];
1123        repeated[0] = value;
1124        value
1125    } else if literal_length != 0 {
1126        match encoded {
1127            1 => repeated[0],
1128            2 => {
1129                repeated.swap(0, 1);
1130                repeated[0]
1131            }
1132            3 => {
1133                let value = repeated[2];
1134                repeated[2] = repeated[1];
1135                repeated[1] = repeated[0];
1136                repeated[0] = value;
1137                value
1138            }
1139            _ => return Err(DecodeError::InvalidOffset),
1140        }
1141    } else {
1142        match encoded {
1143            1 => {
1144                repeated.swap(0, 1);
1145                repeated[0]
1146            }
1147            2 => {
1148                let value = repeated[2];
1149                repeated[2] = repeated[1];
1150                repeated[1] = repeated[0];
1151                repeated[0] = value;
1152                value
1153            }
1154            3 => {
1155                let value = repeated[0]
1156                    .checked_sub(1)
1157                    .filter(|value| *value != 0)
1158                    .ok_or(DecodeError::InvalidOffset)?;
1159                repeated[2] = repeated[1];
1160                repeated[1] = repeated[0];
1161                repeated[0] = value;
1162                value
1163            }
1164            _ => return Err(DecodeError::InvalidOffset),
1165        }
1166    };
1167    Ok(value)
1168}
1169
1170fn read_u32(input: &[u8]) -> u32 {
1171    u32::from_le_bytes([input[0], input[1], input[2], input[3]])
1172}
1173
1174fn read_variable(input: &[u8]) -> u32 {
1175    read_variable_u64(input) as u32
1176}
1177
1178fn read_variable_u64(input: &[u8]) -> u64 {
1179    let mut value = 0u64;
1180    for (index, byte) in input.iter().enumerate() {
1181        value |= (*byte as u64) << (index * 8);
1182    }
1183    value
1184}
1185
1186const LL_BASE: [u32; 36] = [
1187    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 40, 48, 64,
1188    128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
1189];
1190const LL_BITS: [u8; 36] = [
1191    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11,
1192    12, 13, 14, 15, 16,
1193];
1194const ML_BASE: [u32; 53] = [
1195    3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
1196    28, 29, 30, 31, 32, 33, 34, 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 131, 259, 515, 1027,
1197    2051, 4099, 8195, 16387, 32771, 65539,
1198];
1199const ML_BITS: [u8; 53] = [
1200    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1201    1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
1202];
1203const LL_DEFAULT: [i16; 36] = [
1204    4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1,
1205    -1, -1, -1, -1,
1206];
1207const ML_DEFAULT: [i16; 53] = [
1208    1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1209    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1,
1210];
1211const OF_DEFAULT: [i16; 29] = [
1212    1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
1213];
1214
1215#[cfg(test)]
1216mod tests {
1217    use crate::*;
1218    use std::vec::Vec;
1219
1220    #[test]
1221    fn parses_sampled_datadog_header() {
1222        let bytes = [0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x78];
1223        assert_eq!(
1224            inspect_frame(&bytes),
1225            Ok(HeaderStatus::Complete {
1226                header: StreamHeader::Zstandard(FrameHeader {
1227                    window_size: 32 * 1024 * 1024,
1228                    content_size: None,
1229                    dictionary_id: 0,
1230                    has_checksum: true,
1231                }),
1232                size: 6,
1233            })
1234        );
1235    }
1236
1237    #[test]
1238    fn decodes_raw_frame_incrementally() {
1239        let frame = [
1240            0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x05, // one-segment, size 5
1241            0x29, 0, 0, // last raw block, size 5
1242            b'h', b'e', b'l', b'l', b'o',
1243        ];
1244        let mut history = [0u8; 5];
1245        let mut block = [0u8; 5];
1246        let mut literals = [0u8; 5];
1247        let mut fse_scratch = [0i16; crate::FSE_SCRATCH_LEN];
1248        let mut fse = std::vec![FseEntry::default(); FSE_ENTRIES];
1249        let mut huffman = std::vec![HuffmanEntry::default(); HUFFMAN_ENTRIES];
1250        let mut decoder = Decoder::new(DecoderBuffers {
1251            fse_scratch: &mut fse_scratch,
1252            fse: &mut fse,
1253            huffman: &mut huffman,
1254            history: &mut history,
1255            block: &mut block,
1256            literals: &mut literals,
1257        })
1258        .unwrap();
1259        let mut input = &frame[..];
1260        let mut output = [0u8; 5];
1261        let mut output_len = 0;
1262        loop {
1263            let step = decoder.decode(input).unwrap();
1264            let consumed = step.consumed();
1265            input = &input[consumed..];
1266            match step {
1267                DecodeStep::Output { bytes, .. } => {
1268                    output[output_len..output_len + bytes.len()].copy_from_slice(bytes);
1269                    output_len += bytes.len();
1270                }
1271                DecodeStep::NeedInput { .. } if input.is_empty() => break,
1272                _ => {}
1273            }
1274        }
1275        assert_eq!(&output, b"hello");
1276        decoder.finish().unwrap();
1277    }
1278
1279    #[test]
1280    fn streams_fragmented_output_through_callback() {
1281        let frame = [
1282            0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x05, 0x29, 0, 0, b'h', b'e', b'l', b'l', b'o',
1283        ];
1284        let mut history = [0u8; 5];
1285        let mut block = [0u8; 5];
1286        let mut literals = [0u8; 5];
1287        let mut fse_scratch = [0i16; crate::FSE_SCRATCH_LEN];
1288        let mut fse = std::vec![FseEntry::default(); FSE_ENTRIES];
1289        let mut huffman = std::vec![HuffmanEntry::default(); HUFFMAN_ENTRIES];
1290        let mut decoder = Decoder::new(DecoderBuffers {
1291            fse_scratch: &mut fse_scratch,
1292            fse: &mut fse,
1293            huffman: &mut huffman,
1294            history: &mut history,
1295            block: &mut block,
1296            literals: &mut literals,
1297        })
1298        .unwrap();
1299        let mut output = Vec::new();
1300        for fragment in frame.chunks(2) {
1301            decoder
1302                .push(fragment, |bytes| {
1303                    output.extend_from_slice(bytes);
1304                    Ok::<_, ()>(())
1305                })
1306                .unwrap();
1307        }
1308        decoder
1309            .finish_with(|bytes| {
1310                output.extend_from_slice(bytes);
1311                Ok::<_, ()>(())
1312            })
1313            .unwrap();
1314        assert_eq!(output, b"hello");
1315    }
1316
1317    #[test]
1318    fn decodes_rle_and_empty_frames() {
1319        let rle = [
1320            0x28, 0xb5, 0x2f, 0xfd, 0x20, 10, // one-segment, content size 10
1321            0x53, 0, 0, // last RLE block, regenerated size 10
1322            b'x',
1323        ];
1324        let mut history = [0u8; 10];
1325        let mut block = [0u8; 1];
1326        let mut literals = [];
1327        let mut fse_scratch = [0i16; crate::FSE_SCRATCH_LEN];
1328        let mut fse = std::vec![FseEntry::default(); FSE_ENTRIES];
1329        let mut huffman = std::vec![HuffmanEntry::default(); HUFFMAN_ENTRIES];
1330        let mut decoder = Decoder::new(DecoderBuffers {
1331            fse_scratch: &mut fse_scratch,
1332            fse: &mut fse,
1333            huffman: &mut huffman,
1334            history: &mut history,
1335            block: &mut block,
1336            literals: &mut literals,
1337        })
1338        .unwrap();
1339        let mut input = rle.as_slice();
1340        let mut output = [0u8; 10];
1341        let mut position = 0;
1342        loop {
1343            let step = decoder.decode(input).unwrap();
1344            let consumed = step.consumed();
1345            input = &input[consumed..];
1346            if let DecodeStep::Output { bytes, .. } = step {
1347                output[position..position + bytes.len()].copy_from_slice(bytes);
1348                position += bytes.len();
1349            } else if matches!(step, DecodeStep::NeedInput { .. }) {
1350                break;
1351            }
1352        }
1353        assert_eq!(output, [b'x'; 10]);
1354        decoder.finish().unwrap();
1355
1356        let empty = [
1357            0x28, 0xb5, 0x2f, 0xfd, 0x20, 0, // one-segment, empty content
1358            1, 0, 0, // last raw block, size 0
1359        ];
1360        let mut history = [];
1361        let mut block = [];
1362        let mut literals = [];
1363        let mut fse_scratch = [0i16; crate::FSE_SCRATCH_LEN];
1364        let mut fse = std::vec![FseEntry::default(); FSE_ENTRIES];
1365        let mut huffman = std::vec![HuffmanEntry::default(); HUFFMAN_ENTRIES];
1366        let mut decoder = Decoder::new(DecoderBuffers {
1367            fse_scratch: &mut fse_scratch,
1368            fse: &mut fse,
1369            huffman: &mut huffman,
1370            history: &mut history,
1371            block: &mut block,
1372            literals: &mut literals,
1373        })
1374        .unwrap();
1375        let mut input = empty.as_slice();
1376        loop {
1377            let step = decoder.decode(input).unwrap();
1378            let consumed = step.consumed();
1379            input = &input[consumed..];
1380            if matches!(step, DecodeStep::NeedInput { .. }) {
1381                break;
1382            }
1383        }
1384        decoder.finish().unwrap();
1385    }
1386}
1387
1388#[cfg(test)]
1389mod workspace_tests {
1390    use crate::*;
1391    #[test]
1392    fn short_table_storage_is_rejected_without_panicking() {
1393        for (fse_len, huffman_len, scratch_len) in [
1394            (0, HUFFMAN_ENTRIES, FSE_SCRATCH_LEN),
1395            (FSE_ENTRIES, 0, FSE_SCRATCH_LEN),
1396            (FSE_ENTRIES - 1, HUFFMAN_ENTRIES, FSE_SCRATCH_LEN),
1397            (FSE_ENTRIES, HUFFMAN_ENTRIES - 1, FSE_SCRATCH_LEN),
1398            (FSE_ENTRIES, HUFFMAN_ENTRIES, 0),
1399            (FSE_ENTRIES, HUFFMAN_ENTRIES, FSE_SCRATCH_LEN - 1),
1400        ] {
1401            let mut fse_scratch = std::vec![i16::MIN; scratch_len];
1402            let mut fse = std::vec![FseEntry::default(); fse_len];
1403            let mut huffman = std::vec![HuffmanEntry::default(); huffman_len];
1404            let result = Decoder::new(DecoderBuffers {
1405                history: &mut [],
1406                block: &mut [],
1407                literals: &mut [],
1408                fse_scratch: &mut fse_scratch,
1409                fse: &mut fse,
1410                huffman: &mut huffman,
1411            });
1412            assert!(matches!(result, Err(DecodeError::InvalidEntropyTable)));
1413        }
1414    }
1415}