Skip to main content

structured_zstd/decoding/
errors.rs

1//! Errors that might occur while decoding zstd formatted data
2
3use crate::bit_io::GetBitsError;
4use crate::blocks::block::BlockType;
5use crate::blocks::literals_section::LiteralsSectionType;
6use crate::io::Error;
7use alloc::vec::Vec;
8use core::fmt;
9#[cfg(feature = "std")]
10use std::error::Error as StdError;
11
12#[derive(Debug)]
13#[non_exhaustive]
14pub enum FrameDescriptorError {
15    InvalidFrameContentSizeFlag {
16        got: u8,
17    },
18    /// The descriptor's `Reserved_bit` (bit 3) is set, which a decoder of this
19    /// format version must refuse.
20    ReservedBitSet,
21}
22
23impl fmt::Display for FrameDescriptorError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Self::InvalidFrameContentSizeFlag { got } => write!(
27                f,
28                "Invalid Frame_Content_Size_Flag; Is: {got}, Should be one of: 0, 1, 2, 3"
29            ),
30            Self::ReservedBitSet => write!(
31                f,
32                "Reserved_bit of the Frame_Header_Descriptor is set; it must be zero"
33            ),
34        }
35    }
36}
37
38#[cfg(feature = "std")]
39impl StdError for FrameDescriptorError {}
40
41#[derive(Debug)]
42#[non_exhaustive]
43pub enum FrameHeaderError {
44    WindowTooBig { got: u64 },
45    WindowTooSmall { got: u64 },
46    FrameDescriptorError(FrameDescriptorError),
47    DictIdTooSmall { got: usize, expected: usize },
48    MismatchedFrameSize { got: usize, expected: u8 },
49    FrameSizeIsZero,
50    InvalidFrameSize { got: u8 },
51}
52
53impl fmt::Display for FrameHeaderError {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::WindowTooBig { got } => write!(
57                f,
58                "window_size bigger than allowed maximum. Is: {}, Should be lower than: {}",
59                got,
60                crate::common::MAX_WINDOW_SIZE
61            ),
62            Self::WindowTooSmall { got } => write!(
63                f,
64                "window_size smaller than allowed minimum. Is: {}, Should be greater than: {}",
65                got,
66                crate::common::MIN_WINDOW_SIZE
67            ),
68            Self::FrameDescriptorError(e) => write!(f, "{e:?}"),
69            Self::DictIdTooSmall { got, expected } => write!(
70                f,
71                "Not enough bytes in dict_id. Is: {got}, Should be: {expected}"
72            ),
73            Self::MismatchedFrameSize { got, expected } => write!(
74                f,
75                "frame_content_size does not have the right length. Is: {got}, Should be: {expected}"
76            ),
77            Self::FrameSizeIsZero => write!(f, "frame_content_size was zero"),
78            Self::InvalidFrameSize { got } => write!(
79                f,
80                "Invalid frame_content_size. Is: {got}, Should be one of 1, 2, 4, 8 bytes"
81            ),
82        }
83    }
84}
85
86#[cfg(feature = "std")]
87impl StdError for FrameHeaderError {
88    fn source(&self) -> Option<&(dyn StdError + 'static)> {
89        match self {
90            FrameHeaderError::FrameDescriptorError(source) => Some(source),
91            _ => None,
92        }
93    }
94}
95
96impl From<FrameDescriptorError> for FrameHeaderError {
97    fn from(error: FrameDescriptorError) -> Self {
98        Self::FrameDescriptorError(error)
99    }
100}
101
102#[derive(Debug)]
103#[non_exhaustive]
104pub enum ReadFrameHeaderError {
105    MagicNumberReadError(Error),
106    BadMagicNumber(u32),
107    FrameDescriptorReadError(Error),
108    InvalidFrameDescriptor(FrameDescriptorError),
109    WindowDescriptorReadError(Error),
110    DictionaryIdReadError(Error),
111    FrameContentSizeReadError(Error),
112    SkipFrame { magic_number: u32, length: u32 },
113}
114
115impl fmt::Display for ReadFrameHeaderError {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            Self::MagicNumberReadError(e) => write!(f, "Error while reading magic number: {e}"),
119            Self::BadMagicNumber(e) => write!(f, "Read wrong magic number: 0x{e:X}"),
120            Self::FrameDescriptorReadError(e) => {
121                write!(f, "Error while reading frame descriptor: {e}")
122            }
123            Self::InvalidFrameDescriptor(e) => write!(f, "{e:?}"),
124            Self::WindowDescriptorReadError(e) => {
125                write!(f, "Error while reading window descriptor: {e}")
126            }
127            Self::DictionaryIdReadError(e) => write!(f, "Error while reading dictionary id: {e}"),
128            Self::FrameContentSizeReadError(e) => {
129                write!(f, "Error while reading frame content size: {e}")
130            }
131            Self::SkipFrame {
132                magic_number,
133                length,
134            } => write!(
135                f,
136                "SkippableFrame encountered with MagicNumber 0x{magic_number:X} and length {length} bytes"
137            ),
138        }
139    }
140}
141
142#[cfg(feature = "std")]
143impl StdError for ReadFrameHeaderError {
144    fn source(&self) -> Option<&(dyn StdError + 'static)> {
145        match self {
146            ReadFrameHeaderError::MagicNumberReadError(source) => Some(source),
147            ReadFrameHeaderError::FrameDescriptorReadError(source) => Some(source),
148            ReadFrameHeaderError::InvalidFrameDescriptor(source) => Some(source),
149            ReadFrameHeaderError::WindowDescriptorReadError(source) => Some(source),
150            ReadFrameHeaderError::DictionaryIdReadError(source) => Some(source),
151            ReadFrameHeaderError::FrameContentSizeReadError(source) => Some(source),
152            _ => None,
153        }
154    }
155}
156
157impl From<FrameDescriptorError> for ReadFrameHeaderError {
158    fn from(error: FrameDescriptorError) -> Self {
159        Self::InvalidFrameDescriptor(error)
160    }
161}
162
163#[derive(Debug)]
164#[non_exhaustive]
165pub enum BlockHeaderReadError {
166    ReadError(Error),
167    FoundReservedBlock,
168    BlockTypeError(BlockTypeError),
169    BlockSizeError(BlockSizeError),
170}
171
172#[cfg(feature = "std")]
173impl std::error::Error for BlockHeaderReadError {
174    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
175        match self {
176            BlockHeaderReadError::ReadError(source) => Some(source),
177            BlockHeaderReadError::BlockTypeError(source) => Some(source),
178            BlockHeaderReadError::BlockSizeError(source) => Some(source),
179            BlockHeaderReadError::FoundReservedBlock => None,
180        }
181    }
182}
183
184impl ::core::fmt::Display for BlockHeaderReadError {
185    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result {
186        match self {
187            BlockHeaderReadError::ReadError(_) => write!(f, "Error while reading the block header"),
188            BlockHeaderReadError::FoundReservedBlock => write!(
189                f,
190                "Reserved block occured. This is considered corruption by the documentation"
191            ),
192            BlockHeaderReadError::BlockTypeError(e) => write!(f, "Error getting block type: {e}"),
193            BlockHeaderReadError::BlockSizeError(e) => {
194                write!(f, "Error getting block content size: {e}")
195            }
196        }
197    }
198}
199
200impl From<Error> for BlockHeaderReadError {
201    fn from(val: Error) -> Self {
202        Self::ReadError(val)
203    }
204}
205
206impl From<BlockTypeError> for BlockHeaderReadError {
207    fn from(val: BlockTypeError) -> Self {
208        Self::BlockTypeError(val)
209    }
210}
211
212impl From<BlockSizeError> for BlockHeaderReadError {
213    fn from(val: BlockSizeError) -> Self {
214        Self::BlockSizeError(val)
215    }
216}
217
218#[derive(Debug)]
219#[non_exhaustive]
220pub enum BlockTypeError {
221    InvalidBlocktypeNumber { num: u8 },
222}
223
224#[cfg(feature = "std")]
225impl std::error::Error for BlockTypeError {}
226
227impl core::fmt::Display for BlockTypeError {
228    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
229        match self {
230            BlockTypeError::InvalidBlocktypeNumber { num } => {
231                write!(
232                    f,
233                    "Invalid Blocktype number. Is: {num}. Should be one of: 0, 1, 2, 3 (3 is reserved).",
234                )
235            }
236        }
237    }
238}
239
240#[derive(Debug)]
241#[non_exhaustive]
242pub enum BlockSizeError {
243    BlockSizeTooLarge { size: u32 },
244}
245
246#[cfg(feature = "std")]
247impl std::error::Error for BlockSizeError {}
248
249impl core::fmt::Display for BlockSizeError {
250    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251        match self {
252            BlockSizeError::BlockSizeTooLarge { size } => {
253                write!(
254                    f,
255                    "Blocksize was bigger than the absolute maximum {} (128kb). Is: {}",
256                    crate::common::MAX_BLOCK_SIZE,
257                    size,
258                )
259            }
260        }
261    }
262}
263
264#[derive(Debug)]
265#[non_exhaustive]
266pub enum DecompressBlockError {
267    BlockContentReadError(Error),
268    MalformedSectionHeader {
269        expected_len: usize,
270        remaining_bytes: usize,
271    },
272    DecompressLiteralsError(DecompressLiteralsError),
273    LiteralsSectionParseError(LiteralsSectionParseError),
274    SequencesHeaderParseError(SequencesHeaderParseError),
275    DecodeSequenceError(DecodeSequenceError),
276    ExecuteSequencesError(ExecuteSequencesError),
277    /// The block's literals, or its whole output, run past the block maximum
278    /// (RFC 8878 3.1.1.2.4, `Block_Maximum_Size`): `size` bytes where
279    /// `maximum` is the most a block of this frame may produce, the smaller of
280    /// its window and 128 KiB.
281    ExpandsPastBlockMaximum {
282        size: usize,
283        maximum: usize,
284    },
285    /// A compressed block's content is `size` bytes, past the block maximum
286    /// (RFC 8878 3.1.1.2.4, `Block_Maximum_Size`) of `maximum`, the smaller of
287    /// the frame's window and 128 KiB, whatever it would decode to.
288    ContentPastBlockMaximum {
289        size: usize,
290        maximum: usize,
291    },
292    /// A block with no sequences whose literals do not fit a fixed-capacity
293    /// backend: `requested` bytes at `tail` against `capacity`. The block is
294    /// within the block maximum, so this says the caller's slice is short, and
295    /// the frame decoder turns it into `TargetTooSmall` (or a content-size
296    /// mismatch for a frame that declared one). Growable backends grow instead
297    /// and never produce it.
298    LiteralsOutputOverflow {
299        tail: usize,
300        requested: usize,
301        capacity: usize,
302    },
303}
304
305#[cfg(feature = "std")]
306impl std::error::Error for DecompressBlockError {
307    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
308        match self {
309            DecompressBlockError::BlockContentReadError(source) => Some(source),
310            DecompressBlockError::DecompressLiteralsError(source) => Some(source),
311            DecompressBlockError::LiteralsSectionParseError(source) => Some(source),
312            DecompressBlockError::SequencesHeaderParseError(source) => Some(source),
313            DecompressBlockError::DecodeSequenceError(source) => Some(source),
314            DecompressBlockError::ExecuteSequencesError(source) => Some(source),
315            _ => None,
316        }
317    }
318}
319
320impl core::fmt::Display for DecompressBlockError {
321    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
322        match self {
323            DecompressBlockError::BlockContentReadError(e) => {
324                write!(f, "Error while reading the block content: {e}")
325            }
326            DecompressBlockError::MalformedSectionHeader {
327                expected_len,
328                remaining_bytes,
329            } => {
330                write!(
331                    f,
332                    "Malformed section header. Says literals would be this long: {expected_len} but there are only {remaining_bytes} bytes left",
333                )
334            }
335            DecompressBlockError::DecompressLiteralsError(e) => write!(f, "{e:?}"),
336            DecompressBlockError::LiteralsSectionParseError(e) => write!(f, "{e:?}"),
337            DecompressBlockError::SequencesHeaderParseError(e) => write!(f, "{e:?}"),
338            DecompressBlockError::DecodeSequenceError(e) => write!(f, "{e:?}"),
339            DecompressBlockError::ExecuteSequencesError(e) => write!(f, "{e:?}"),
340            DecompressBlockError::ExpandsPastBlockMaximum { size, maximum } => write!(
341                f,
342                "Block expands to {size} bytes, past this frame's maximum of {maximum}"
343            ),
344            DecompressBlockError::ContentPastBlockMaximum { size, maximum } => write!(
345                f,
346                "Compressed block of {size} bytes, past this frame's maximum of {maximum}"
347            ),
348            DecompressBlockError::LiteralsOutputOverflow {
349                tail,
350                requested,
351                capacity,
352            } => write!(
353                f,
354                "Literals would write past the output buffer: tail={tail}, requested={requested}, capacity={capacity}"
355            ),
356        }
357    }
358}
359
360impl From<Error> for DecompressBlockError {
361    fn from(val: Error) -> Self {
362        Self::BlockContentReadError(val)
363    }
364}
365
366impl From<DecompressLiteralsError> for DecompressBlockError {
367    fn from(val: DecompressLiteralsError) -> Self {
368        Self::DecompressLiteralsError(val)
369    }
370}
371
372impl From<LiteralsSectionParseError> for DecompressBlockError {
373    fn from(val: LiteralsSectionParseError) -> Self {
374        Self::LiteralsSectionParseError(val)
375    }
376}
377
378impl From<SequencesHeaderParseError> for DecompressBlockError {
379    fn from(val: SequencesHeaderParseError) -> Self {
380        Self::SequencesHeaderParseError(val)
381    }
382}
383
384impl From<DecodeSequenceError> for DecompressBlockError {
385    fn from(val: DecodeSequenceError) -> Self {
386        Self::DecodeSequenceError(val)
387    }
388}
389
390impl From<ExecuteSequencesError> for DecompressBlockError {
391    fn from(val: ExecuteSequencesError) -> Self {
392        Self::ExecuteSequencesError(val)
393    }
394}
395
396#[derive(Debug)]
397#[non_exhaustive]
398pub enum DecodeBlockContentError {
399    DecoderStateIsFailed,
400    ExpectedHeaderOfPreviousBlock,
401    ReadError {
402        step: BlockType,
403        source: Error,
404    },
405    DecompressBlockError(DecompressBlockError),
406    /// The block's decompressed payload would not fit in the
407    /// caller-provided output buffer (only reachable via the
408    /// direct-decode path with a fixed-capacity backend). Internal
409    /// diagnostic variant — the frame-level decoder always
410    /// converts this into
411    /// `FrameDecoderError::FrameContentSizeMismatch` before
412    /// returning to the user, so external callers never observe
413    /// it. The `step: BlockType` field is kept for in-crate
414    /// debugging (which decode arm triggered the overshoot) but is
415    /// not part of any caller-visible distinction.
416    BackendOverflow {
417        step: BlockType,
418    },
419}
420
421#[cfg(feature = "std")]
422impl std::error::Error for DecodeBlockContentError {
423    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
424        match self {
425            DecodeBlockContentError::ReadError { step: _, source } => Some(source),
426            DecodeBlockContentError::DecompressBlockError(source) => Some(source),
427            _ => None,
428        }
429    }
430}
431
432impl core::fmt::Display for DecodeBlockContentError {
433    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
434        match self {
435            DecodeBlockContentError::DecoderStateIsFailed => {
436                write!(
437                    f,
438                    "Can't decode next block if failed along the way. Results will be nonsense",
439                )
440            }
441            DecodeBlockContentError::ExpectedHeaderOfPreviousBlock => {
442                write!(
443                    f,
444                    "Can't decode next block body, while expecting to decode the header of the previous block. Results will be nonsense",
445                )
446            }
447            DecodeBlockContentError::ReadError { step, source } => {
448                write!(f, "Error while reading bytes for {step}: {source}",)
449            }
450            DecodeBlockContentError::DecompressBlockError(e) => write!(f, "{e:?}"),
451            DecodeBlockContentError::BackendOverflow { step } => write!(
452                f,
453                "{step} block's decompressed payload exceeds the caller-provided output buffer",
454            ),
455        }
456    }
457}
458
459impl From<DecompressBlockError> for DecodeBlockContentError {
460    fn from(val: DecompressBlockError) -> Self {
461        Self::DecompressBlockError(val)
462    }
463}
464
465#[derive(Debug)]
466#[non_exhaustive]
467pub enum DecodeBufferError {
468    NotEnoughBytesInDictionary {
469        got: usize,
470        need: usize,
471    },
472    OffsetTooBig {
473        offset: usize,
474        buf_len: usize,
475    },
476    ZeroOffset,
477    /// Legacy unit variant kept for binary compatibility with earlier
478    /// snapshots of this enum. Not surfaced from any current
479    /// production path — `BufferBackend::try_extend` /
480    /// `try_extend_from_within` and the new `try_reserve` (used by
481    /// `DecodeBuffer::repeat`) carry their failures through the
482    /// richer [`Self::OutputBufferOverflow`] variant below, which
483    /// reports the offending `tail` / `requested` / `capacity`
484    /// triple. New code should pattern-match on
485    /// `OutputBufferOverflow`; this unit variant is retained to keep
486    /// the `#[non_exhaustive]` enum's existing discriminant set
487    /// stable.
488    BackendOverflow,
489    /// Repeat-side match copy would write past the writable tail of
490    /// a fixed-capacity backend (`UserSliceBackend`). Surfaced by
491    /// `DecodeBuffer::repeat` / `_lookahead`
492    /// when the new `BufferBackend::try_reserve` rejects the
493    /// pre-write capacity check — keeping the safe public decode
494    /// APIs error-returning instead of panicking via the per-call
495    /// `assert!` inside `extend_from_within_unchecked`. Growable
496    /// backends (`FlatBuf`, `RingBuffer`) never produce this; their
497    /// `try_reserve` falls through to infallible `reserve`.
498    OutputBufferOverflow {
499        tail: usize,
500        requested: usize,
501        capacity: usize,
502    },
503}
504
505#[cfg(feature = "std")]
506impl std::error::Error for DecodeBufferError {}
507
508impl core::fmt::Display for DecodeBufferError {
509    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
510        match self {
511            DecodeBufferError::NotEnoughBytesInDictionary { got, need } => {
512                write!(
513                    f,
514                    "Need {need} bytes from the dictionary but it is only {got} bytes long",
515                )
516            }
517            DecodeBufferError::OffsetTooBig { offset, buf_len } => {
518                write!(f, "offset: {offset} bigger than buffer: {buf_len}",)
519            }
520            DecodeBufferError::ZeroOffset => {
521                write!(f, "Illegal offset: 0 found")
522            }
523            DecodeBufferError::BackendOverflow => {
524                write!(
525                    f,
526                    "Match repeat would overflow the output buffer's fixed capacity"
527                )
528            }
529            DecodeBufferError::OutputBufferOverflow {
530                tail,
531                requested,
532                capacity,
533            } => {
534                write!(
535                    f,
536                    "Match repeat would write past fixed-capacity buffer: tail={tail}, requested={requested}, capacity={capacity}"
537                )
538            }
539        }
540    }
541}
542
543#[derive(Debug)]
544#[non_exhaustive]
545pub enum DictionaryDecodeError {
546    BadMagicNum { got: [u8; 4] },
547    DictionaryTooSmall { got: usize, need: usize },
548    ZeroDictionaryId,
549    ZeroRepeatOffsetInDictionary { index: u8 },
550    FSETableError(FSETableError),
551    HuffmanTableError(HuffmanTableError),
552}
553
554#[cfg(feature = "std")]
555impl std::error::Error for DictionaryDecodeError {
556    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
557        match self {
558            DictionaryDecodeError::FSETableError(source) => Some(source),
559            DictionaryDecodeError::HuffmanTableError(source) => Some(source),
560            _ => None,
561        }
562    }
563}
564
565impl core::fmt::Display for DictionaryDecodeError {
566    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
567        match self {
568            DictionaryDecodeError::BadMagicNum { got } => {
569                write!(
570                    f,
571                    "Bad magic_num at start of the dictionary; Got: {:#04X?}, Expected: {:#04x?}",
572                    got,
573                    crate::decoding::dictionary::MAGIC_NUM,
574                )
575            }
576            DictionaryDecodeError::DictionaryTooSmall { got, need } => {
577                write!(
578                    f,
579                    "Dictionary is too small: got {got} bytes, need at least {need} bytes",
580                )
581            }
582            DictionaryDecodeError::ZeroDictionaryId => {
583                write!(f, "Dictionary id must be non-zero")
584            }
585            DictionaryDecodeError::ZeroRepeatOffsetInDictionary { index } => {
586                write!(f, "Dictionary repeat offset rep{index} must be non-zero")
587            }
588            DictionaryDecodeError::FSETableError(e) => write!(f, "{e:?}"),
589            DictionaryDecodeError::HuffmanTableError(e) => write!(f, "{e:?}"),
590        }
591    }
592}
593
594impl From<FSETableError> for DictionaryDecodeError {
595    fn from(val: FSETableError) -> Self {
596        Self::FSETableError(val)
597    }
598}
599
600impl From<HuffmanTableError> for DictionaryDecodeError {
601    fn from(val: HuffmanTableError) -> Self {
602        Self::HuffmanTableError(val)
603    }
604}
605
606/// The class a decoding failure falls in, as libzstd's stable error codes name
607/// it (`zstd_errors.h`). The C API returns these codes and the command line
608/// prints [`ErrorCode::message`], so both report a failure alike.
609///
610/// # Examples
611///
612/// ```
613/// use structured_zstd::decoding::errors::{ErrorCode, FrameDecoderError};
614///
615/// let err = FrameDecoderError::DictNotProvided { dict_id: 7 };
616/// assert_eq!(err.code(), ErrorCode::DictionaryWrong);
617/// assert_eq!(err.code().message(), "Dictionary mismatch");
618/// ```
619#[derive(Debug, Clone, Copy, PartialEq, Eq)]
620#[non_exhaustive]
621pub enum ErrorCode {
622    /// Not a frame: the magic number is unknown.
623    PrefixUnknown,
624    /// The frame header asks for something this decoder does not support.
625    FrameParameterUnsupported,
626    /// The frame's window is past the ceiling in force.
627    FrameParameterWindowTooLarge,
628    /// A setting lies outside its allowed range.
629    ParameterOutOfBound,
630    /// The dictionary could not be read.
631    DictionaryCorrupted,
632    /// The frame needs another dictionary than the one supplied, or one that
633    /// was not supplied.
634    DictionaryWrong,
635    /// The output buffer is too small.
636    DstSizeTooSmall,
637    /// The decoder was used before a frame was started.
638    InitMissing,
639    /// The content checksum does not match the decoded data.
640    ChecksumWrong,
641    /// The frame is malformed or damaged.
642    CorruptionDetected,
643}
644
645impl ErrorCode {
646    /// libzstd's message for the code (`ZSTD_getErrorString`).
647    pub fn message(self) -> &'static str {
648        match self {
649            ErrorCode::PrefixUnknown => "Unknown frame descriptor",
650            ErrorCode::FrameParameterUnsupported => "Unsupported frame parameter",
651            ErrorCode::FrameParameterWindowTooLarge => {
652                "Frame requires too much memory for decoding"
653            }
654            ErrorCode::ParameterOutOfBound => "Parameter is out of bound",
655            ErrorCode::DictionaryCorrupted => "Dictionary is corrupted",
656            ErrorCode::DictionaryWrong => "Dictionary mismatch",
657            ErrorCode::DstSizeTooSmall => "Destination buffer is too small",
658            ErrorCode::InitMissing => "Context should be init first",
659            ErrorCode::ChecksumWrong => "Restored data doesn't match checksum",
660            ErrorCode::CorruptionDetected => "Data corruption detected",
661        }
662    }
663}
664
665impl FrameDescriptorError {
666    /// The code libzstd returns for the refused descriptor.
667    pub fn code(&self) -> ErrorCode {
668        match self {
669            // A set Reserved_bit is an unsupported frame parameter
670            // (upstream zstd `zstd_decompress.c:511`).
671            FrameDescriptorError::ReservedBitSet => ErrorCode::FrameParameterUnsupported,
672            _ => ErrorCode::CorruptionDetected,
673        }
674    }
675}
676
677#[derive(Debug)]
678#[non_exhaustive]
679pub enum FrameDecoderError {
680    ReadFrameHeaderError(ReadFrameHeaderError),
681    FrameHeaderError(FrameHeaderError),
682    /// The frame's window exceeds the ceiling in force: the decoder's
683    /// [`FrameDecoder::set_max_window_size`](crate::decoding::FrameDecoder::set_max_window_size)
684    /// setting, or [`MAX_DECODER_WINDOW_SIZE`](crate::decoding::MAX_DECODER_WINDOW_SIZE).
685    #[non_exhaustive]
686    WindowSizeTooBig {
687        /// The window the frame declares.
688        requested: u64,
689        /// The ceiling it exceeded.
690        limit: u64,
691    },
692    /// A requested window ceiling lies outside 1 KiB (the smallest window a
693    /// frame declares, RFC 8878 3.1.1.1.2) ..=
694    /// [`MAX_DECODER_WINDOW_SIZE`](crate::decoding::MAX_DECODER_WINDOW_SIZE).
695    WindowCeilingOutOfRange {
696        /// The ceiling asked for.
697        requested: u64,
698    },
699    DictionaryDecodeError(DictionaryDecodeError),
700    FailedToReadBlockHeader(BlockHeaderReadError),
701    FailedToReadBlockBody(DecodeBlockContentError),
702    FailedToReadChecksum(Error),
703    NotYetInitialized,
704    FailedToInitialize(FrameHeaderError),
705    FailedToDrainDecodebuffer(Error),
706    FailedToSkipFrame,
707    TargetTooSmall,
708    /// Decoded block sizes don't sum to the frame's declared
709    /// `frame_content_size` (either a block claims to expand past
710    /// FCS, or the stream ends before reaching FCS). Indicates a
711    /// malformed or corrupt frame — distinct from
712    /// [`Self::TargetTooSmall`] (which is the caller's
713    /// responsibility) so callers can tell decoder-side issues
714    /// apart from their own buffer sizing mistakes.
715    FrameContentSizeMismatch {
716        declared: u64,
717        produced: u64,
718    },
719    /// The frame carried a trailing XXH64 content checksum and the decoder
720    /// was set to [`ContentChecksum::Verify`](crate::decoding::ContentChecksum::Verify),
721    /// but the digest computed over the decompressed output did not match the
722    /// stored value. Indicates corruption in the compressed stream or its
723    /// trailing checksum. `expected` is the value read from the frame tail;
724    /// `calculated` is the digest the decoder computed (both low 32 bits).
725    ChecksumMismatch {
726        expected: u32,
727        calculated: u32,
728    },
729    DictNotProvided {
730        dict_id: u32,
731    },
732    DictIdMismatch {
733        expected: u32,
734        provided: u32,
735    },
736    DictAlreadyRegistered {
737        dict_id: u32,
738    },
739    /// Frame header's `dict_id` did not match the value pinned via
740    /// `FrameDecoder::expect_dict_id`. Returned BEFORE any block
741    /// decode and BEFORE any output is produced — no XXH64 init,
742    /// no partial output. Scratch buffer allocation / reservation
743    /// for the decode pipeline happens during frame-header parsing,
744    /// which is already complete when this validation fires, so
745    /// the cost of scratch sizing is paid even on a mismatched
746    /// header. `expected` is the pinned value (`Some(0)` is
747    /// treated as "no dictionary expected", matching a frame whose
748    /// header omits the optional `Dictionary_ID` field); `found`
749    /// reports what the frame actually carried (`None` when the
750    /// header omits the field, `Some(id)` when it does not).
751    #[cfg(feature = "lsm")]
752    UnexpectedDictId {
753        expected: Option<u32>,
754        found: Option<u32>,
755    },
756    /// Frame header's raw `Window_Descriptor` byte did not match
757    /// the value pinned via `FrameDecoder::expect_window_descriptor`.
758    /// Returned BEFORE any block decode work. Single-segment frames
759    /// (which omit the `Window_Descriptor` byte from the wire) are
760    /// reported via `found: None` so callers can distinguish
761    /// "wrong descriptor" from "no descriptor on the wire".
762    #[cfg(feature = "lsm")]
763    UnexpectedWindowDescriptor {
764        expected: u8,
765        found: Option<u8>,
766    },
767    /// Block-precise variant of [`Self::FailedToReadBlockHeader`]: a block
768    /// header read failed and the decoder captured WHERE. `block_index` is
769    /// the 0-based index of the failing block in the frame; `frame_offset`
770    /// is the frame-absolute byte offset of that block's `Block_Header`
771    /// (matches `FrameEmitInfo.blocks[block_index].offset_in_frame` from the
772    /// encode side). Lets per-block recovery (ECC repair) target the one bad
773    /// block instead of re-fetching the whole frame.
774    #[cfg(feature = "lsm")]
775    FailedToReadBlockHeaderAt {
776        source: BlockHeaderReadError,
777        block_index: u32,
778        frame_offset: u32,
779    },
780    /// Block-precise variant of [`Self::FailedToReadBlockBody`]: a block
781    /// body decode failed. Carries the same `block_index` / `frame_offset`
782    /// coordinates plus the failing block's structural metadata
783    /// ([`FrameBlock`]) reconstructed from its header, so a consumer can
784    /// locate and repair exactly this block.
785    ///
786    /// [`FrameBlock`]: crate::encoding::frame_emit_info::FrameBlock
787    #[cfg(feature = "lsm")]
788    FailedToReadBlockBodyAt {
789        source: DecodeBlockContentError,
790        block_index: u32,
791        frame_offset: u32,
792        block: crate::encoding::frame_emit_info::FrameBlock,
793    },
794    /// `FrameDecoder::decode_blocks_partial` was called with
795    /// `start_block > end_block` (the half-open block range is
796    /// empty-or-inverted and cannot describe a valid subset). API
797    /// misuse, surfaced as `Err` rather than a `PartialDecode`
798    /// outcome — distinct from a corrupt-frame stop, which is
799    /// reported via `PartialDecode::stopped_at`.
800    #[cfg(feature = "lsm")]
801    InvalidBlockRange {
802        start_block: u32,
803        end_block: u32,
804    },
805    /// A resuming [`FrameDecoder::decode_blocks_partial`] was given a
806    /// `window_prime` (via [`ResumeInput`]) shorter than the match window the
807    /// resume block can reach back into. The resumed decode would read past the
808    /// primed history and silently mis-resolve matches, so it is rejected up
809    /// front. `got` is the supplied prime length; `need` is the required
810    /// minimum (`min(window_size, output_offset)`).
811    ///
812    /// [`FrameDecoder::decode_blocks_partial`]: crate::decoding::FrameDecoder::decode_blocks_partial
813    /// [`ResumeInput`]: crate::decoding::ResumeInput
814    #[cfg(feature = "lsm")]
815    ResumeWindowTooShort {
816        got: usize,
817        need: usize,
818    },
819    /// A resuming [`FrameDecoder::decode_blocks_partial`] was given a
820    /// [`ResumeInput`] whose [`ResumeState`] was captured from a frame with a
821    /// different decode-relevant shape (window size, dictionary id,
822    /// single-segment flag, content-checksum flag, or magicless mode) than the
823    /// frame currently [`reset`](crate::decoding::FrameDecoder::reset) into the
824    /// decoder. Applying entropy/repcode state across mismatched frames would
825    /// yield byte-wrong output, so it is rejected up front.
826    ///
827    /// [`FrameDecoder::decode_blocks_partial`]: crate::decoding::FrameDecoder::decode_blocks_partial
828    /// [`ResumeInput`]: crate::decoding::ResumeInput
829    /// [`ResumeState`]: crate::decoding::ResumeState
830    #[cfg(feature = "lsm")]
831    ResumeFrameMismatch,
832    /// A resume was attempted while a dictionary that carries no ID was
833    /// applied, on either the emitting or the resuming side.
834    ///
835    /// The frame key tells dictionaries apart by the ID the decoder recorded,
836    /// and a raw-content dictionary has none — every one of them is ID 0. Two
837    /// different raw dictionaries therefore key alike, so a snapshot captured
838    /// under one would be restored under the other: foreign entropy and repcode
839    /// state, and output that is wrong with nothing to say so. A dictionary that
840    /// cannot be told from another cannot carry a resume across, so it is
841    /// refused. Give the dictionary an ID (a serialized dictionary carries one,
842    /// or supply a non-zero one to
843    /// [`Dictionary::from_raw_content`](crate::decoding::Dictionary::from_raw_content))
844    /// to make it resumable.
845    #[cfg(feature = "lsm")]
846    ResumeUnidentifiedDictionary,
847}
848
849impl FrameDecoderError {
850    /// The code libzstd returns for the same failure. A variant with no exact
851    /// counterpart is `CorruptionDetected`, the code for a malformed frame.
852    pub fn code(&self) -> ErrorCode {
853        match self {
854            FrameDecoderError::ReadFrameHeaderError(
855                ReadFrameHeaderError::InvalidFrameDescriptor(descriptor),
856            ) => descriptor.code(),
857            FrameDecoderError::ReadFrameHeaderError(_) => ErrorCode::PrefixUnknown,
858            // A window past the format's maximum is refused while the header
859            // is read, before the ceiling check, and is the same failure.
860            FrameDecoderError::FrameHeaderError(FrameHeaderError::WindowTooBig { .. })
861            | FrameDecoderError::FailedToInitialize(FrameHeaderError::WindowTooBig { .. }) => {
862                ErrorCode::FrameParameterWindowTooLarge
863            }
864            FrameDecoderError::FrameHeaderError(_) | FrameDecoderError::FailedToInitialize(_) => {
865                ErrorCode::FrameParameterUnsupported
866            }
867            FrameDecoderError::WindowSizeTooBig { .. } => ErrorCode::FrameParameterWindowTooLarge,
868            FrameDecoderError::WindowCeilingOutOfRange { .. } => ErrorCode::ParameterOutOfBound,
869            FrameDecoderError::DictionaryDecodeError(_) => ErrorCode::DictionaryCorrupted,
870            FrameDecoderError::DictNotProvided { .. }
871            | FrameDecoderError::DictIdMismatch { .. }
872            | FrameDecoderError::DictAlreadyRegistered { .. } => ErrorCode::DictionaryWrong,
873            FrameDecoderError::TargetTooSmall => ErrorCode::DstSizeTooSmall,
874            FrameDecoderError::NotYetInitialized => ErrorCode::InitMissing,
875            FrameDecoderError::ChecksumMismatch { .. } => ErrorCode::ChecksumWrong,
876            _ => ErrorCode::CorruptionDetected,
877        }
878    }
879}
880
881#[cfg(feature = "std")]
882impl StdError for FrameDecoderError {
883    fn source(&self) -> Option<&(dyn StdError + 'static)> {
884        match self {
885            FrameDecoderError::ReadFrameHeaderError(source) => Some(source),
886            FrameDecoderError::FrameHeaderError(source) => Some(source),
887            FrameDecoderError::DictionaryDecodeError(source) => Some(source),
888            FrameDecoderError::FailedToReadBlockHeader(source) => Some(source),
889            FrameDecoderError::FailedToReadBlockBody(source) => Some(source),
890            #[cfg(feature = "lsm")]
891            FrameDecoderError::FailedToReadBlockHeaderAt { source, .. } => Some(source),
892            #[cfg(feature = "lsm")]
893            FrameDecoderError::FailedToReadBlockBodyAt { source, .. } => Some(source),
894            FrameDecoderError::FailedToReadChecksum(source) => Some(source),
895            FrameDecoderError::FailedToInitialize(source) => Some(source),
896            FrameDecoderError::FailedToDrainDecodebuffer(source) => Some(source),
897            _ => None,
898        }
899    }
900}
901
902impl core::fmt::Display for FrameDecoderError {
903    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result {
904        match self {
905            FrameDecoderError::ReadFrameHeaderError(e) => {
906                write!(f, "{e:?}")
907            }
908            FrameDecoderError::FrameHeaderError(e) => {
909                write!(f, "{e:?}")
910            }
911            FrameDecoderError::WindowSizeTooBig { requested, limit } => {
912                write!(
913                    f,
914                    "Specified window_size is too big; Requested: {requested}, Allowed: {limit}",
915                )
916            }
917            FrameDecoderError::WindowCeilingOutOfRange { requested } => {
918                write!(
919                    f,
920                    "window ceiling {requested} is outside {}..={}",
921                    crate::common::MIN_WINDOW_SIZE,
922                    crate::common::MAX_DECODER_WINDOW_SIZE,
923                )
924            }
925            FrameDecoderError::DictionaryDecodeError(e) => {
926                write!(f, "{e:?}")
927            }
928            FrameDecoderError::FailedToReadBlockHeader(e) => {
929                write!(f, "Failed to parse/decode block body: {e}")
930            }
931            FrameDecoderError::FailedToReadBlockBody(e) => {
932                write!(f, "Failed to parse block header: {e}")
933            }
934            #[cfg(feature = "lsm")]
935            FrameDecoderError::FailedToReadBlockHeaderAt {
936                source,
937                block_index,
938                frame_offset,
939            } => {
940                write!(
941                    f,
942                    "Failed to read block header at block {block_index} (frame offset {frame_offset}): {source}"
943                )
944            }
945            #[cfg(feature = "lsm")]
946            FrameDecoderError::FailedToReadBlockBodyAt {
947                source,
948                block_index,
949                frame_offset,
950                ..
951            } => {
952                write!(
953                    f,
954                    "Failed to decode block body at block {block_index} (frame offset {frame_offset}): {source}"
955                )
956            }
957            FrameDecoderError::FailedToReadChecksum(e) => {
958                write!(f, "Failed to read checksum: {e}")
959            }
960            FrameDecoderError::NotYetInitialized => {
961                write!(f, "Decoder must initialized or reset before using it",)
962            }
963            FrameDecoderError::FailedToInitialize(e) => {
964                write!(f, "Decoder encountered error while initializing: {e}")
965            }
966            FrameDecoderError::FailedToDrainDecodebuffer(e) => {
967                write!(
968                    f,
969                    "Decoder encountered error while draining the decodebuffer: {e}",
970                )
971            }
972            FrameDecoderError::FailedToSkipFrame => {
973                write!(
974                    f,
975                    "Failed to skip bytes for the length given in the frame header"
976                )
977            }
978            FrameDecoderError::TargetTooSmall => {
979                write!(
980                    f,
981                    "Target must have at least as many bytes as the content size reported by the frame"
982                )
983            }
984            FrameDecoderError::FrameContentSizeMismatch { declared, produced } => {
985                write!(
986                    f,
987                    "Frame content size mismatch (corrupt frame): declared {declared} bytes, blocks summed to {produced} bytes"
988                )
989            }
990            FrameDecoderError::ChecksumMismatch {
991                expected,
992                calculated,
993            } => {
994                write!(
995                    f,
996                    "Content checksum mismatch (corrupt frame): frame stored 0x{expected:08X}, decoder calculated 0x{calculated:08X}"
997                )
998            }
999            FrameDecoderError::DictNotProvided { dict_id } => {
1000                write!(
1001                    f,
1002                    "Frame header specified dictionary id 0x{dict_id:X} that wasn't provided via add_dict()/add_dict_from_bytes() (or add_dict_handle() on atomic targets) or reset_with_dict_handle()/decode_all_with_dict_handle()/decode_all_with_dict_bytes()"
1003                )
1004            }
1005            FrameDecoderError::DictIdMismatch { expected, provided } => {
1006                write!(
1007                    f,
1008                    "Frame header dictionary id 0x{expected:X} does not match provided dictionary id 0x{provided:X}"
1009                )
1010            }
1011            FrameDecoderError::DictAlreadyRegistered { dict_id } => {
1012                write!(
1013                    f,
1014                    "Dictionary id 0x{dict_id:X} already registered in decoder"
1015                )
1016            }
1017            #[cfg(feature = "lsm")]
1018            FrameDecoderError::UnexpectedDictId { expected, found } => {
1019                write!(f, "Frame header dict_id mismatch: expected ")?;
1020                match expected {
1021                    Some(id) => write!(f, "0x{id:X}")?,
1022                    None => write!(f, "<none>")?,
1023                }
1024                write!(f, ", found ")?;
1025                match found {
1026                    Some(id) => write!(f, "0x{id:X}"),
1027                    None => write!(f, "<none>"),
1028                }
1029            }
1030            #[cfg(feature = "lsm")]
1031            FrameDecoderError::UnexpectedWindowDescriptor { expected, found } => {
1032                write!(
1033                    f,
1034                    "Frame header window_descriptor mismatch: expected 0x{expected:02X}, found "
1035                )?;
1036                match found {
1037                    Some(byte) => write!(f, "0x{byte:02X}"),
1038                    None => write!(f, "<none> (single-segment frame omits window_descriptor)"),
1039                }
1040            }
1041            #[cfg(feature = "lsm")]
1042            FrameDecoderError::InvalidBlockRange {
1043                start_block,
1044                end_block,
1045            } => {
1046                write!(
1047                    f,
1048                    "Invalid block range for partial decode: start_block {start_block} > end_block {end_block}"
1049                )
1050            }
1051            #[cfg(feature = "lsm")]
1052            FrameDecoderError::ResumeWindowTooShort { got, need } => {
1053                write!(
1054                    f,
1055                    "resume window_prime too short: got {got} bytes, need at least {need}"
1056                )
1057            }
1058            #[cfg(feature = "lsm")]
1059            FrameDecoderError::ResumeFrameMismatch => {
1060                write!(
1061                    f,
1062                    "resume state was captured from a frame with a different decode shape than the current frame"
1063                )
1064            }
1065            #[cfg(feature = "lsm")]
1066            FrameDecoderError::ResumeUnidentifiedDictionary => {
1067                write!(
1068                    f,
1069                    "a dictionary with no ID cannot be told from another one, so it cannot carry a resume state across frames"
1070                )
1071            }
1072        }
1073    }
1074}
1075
1076impl From<DictionaryDecodeError> for FrameDecoderError {
1077    fn from(val: DictionaryDecodeError) -> Self {
1078        Self::DictionaryDecodeError(val)
1079    }
1080}
1081
1082impl From<BlockHeaderReadError> for FrameDecoderError {
1083    fn from(val: BlockHeaderReadError) -> Self {
1084        Self::FailedToReadBlockHeader(val)
1085    }
1086}
1087
1088impl From<FrameHeaderError> for FrameDecoderError {
1089    fn from(val: FrameHeaderError) -> Self {
1090        Self::FrameHeaderError(val)
1091    }
1092}
1093
1094impl From<ReadFrameHeaderError> for FrameDecoderError {
1095    fn from(val: ReadFrameHeaderError) -> Self {
1096        Self::ReadFrameHeaderError(val)
1097    }
1098}
1099
1100#[derive(Debug)]
1101#[non_exhaustive]
1102pub enum DecompressLiteralsError {
1103    MissingCompressedSize,
1104    MissingNumStreams,
1105    GetBitsError(GetBitsError),
1106    HuffmanTableError(HuffmanTableError),
1107    HuffmanDecoderError(HuffmanDecoderError),
1108    UninitializedHuffmanTable,
1109    MissingBytesForJumpHeader { got: usize },
1110    MissingBytesForLiterals { got: usize, needed: usize },
1111    ExtraPadding { skipped_bits: i32 },
1112    BitstreamReadMismatch { read_til: isize, expected: isize },
1113    DecodedLiteralCountMismatch { decoded: usize, expected: usize },
1114}
1115
1116#[cfg(feature = "std")]
1117impl std::error::Error for DecompressLiteralsError {
1118    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1119        match self {
1120            DecompressLiteralsError::GetBitsError(source) => Some(source),
1121            DecompressLiteralsError::HuffmanTableError(source) => Some(source),
1122            DecompressLiteralsError::HuffmanDecoderError(source) => Some(source),
1123            _ => None,
1124        }
1125    }
1126}
1127impl core::fmt::Display for DecompressLiteralsError {
1128    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1129        match self {
1130            DecompressLiteralsError::MissingCompressedSize => {
1131                write!(
1132                    f,
1133                    "compressed size was none even though it must be set to something for compressed literals",
1134                )
1135            }
1136            DecompressLiteralsError::MissingNumStreams => {
1137                write!(
1138                    f,
1139                    "num_streams was none even though it must be set to something (1 or 4) for compressed literals",
1140                )
1141            }
1142            DecompressLiteralsError::GetBitsError(e) => write!(f, "{e:?}"),
1143            DecompressLiteralsError::HuffmanTableError(e) => write!(f, "{e:?}"),
1144            DecompressLiteralsError::HuffmanDecoderError(e) => write!(f, "{e:?}"),
1145            DecompressLiteralsError::UninitializedHuffmanTable => {
1146                write!(
1147                    f,
1148                    "Tried to reuse huffman table but it was never initialized",
1149                )
1150            }
1151            DecompressLiteralsError::MissingBytesForJumpHeader { got } => {
1152                write!(f, "Need 6 bytes to decode jump header, got {got} bytes",)
1153            }
1154            DecompressLiteralsError::MissingBytesForLiterals { got, needed } => {
1155                write!(
1156                    f,
1157                    "Need at least {needed} bytes to decode literals. Have: {got} bytes",
1158                )
1159            }
1160            DecompressLiteralsError::ExtraPadding { skipped_bits } => {
1161                write!(
1162                    f,
1163                    "Padding at the end of the sequence_section was more than a byte long: {skipped_bits} bits. Probably caused by data corruption",
1164                )
1165            }
1166            DecompressLiteralsError::BitstreamReadMismatch { read_til, expected } => {
1167                write!(
1168                    f,
1169                    "Bitstream was read till: {read_til}, should have been: {expected}",
1170                )
1171            }
1172            DecompressLiteralsError::DecodedLiteralCountMismatch { decoded, expected } => {
1173                write!(
1174                    f,
1175                    "Did not decode enough literals: {decoded}, Should have been: {expected}",
1176                )
1177            }
1178        }
1179    }
1180}
1181
1182impl From<HuffmanDecoderError> for DecompressLiteralsError {
1183    fn from(val: HuffmanDecoderError) -> Self {
1184        Self::HuffmanDecoderError(val)
1185    }
1186}
1187
1188impl From<GetBitsError> for DecompressLiteralsError {
1189    fn from(val: GetBitsError) -> Self {
1190        Self::GetBitsError(val)
1191    }
1192}
1193
1194impl From<HuffmanTableError> for DecompressLiteralsError {
1195    fn from(val: HuffmanTableError) -> Self {
1196        Self::HuffmanTableError(val)
1197    }
1198}
1199
1200#[derive(Debug)]
1201#[non_exhaustive]
1202pub enum ExecuteSequencesError {
1203    DecodebufferError(DecodeBufferError),
1204    NotEnoughBytesForSequence {
1205        wanted: usize,
1206        have: usize,
1207    },
1208    ZeroOffset,
1209    /// An inline sequence (`exec_sequence_inline`) would have written past
1210    /// the writable tail of the output buffer. Raised by every capacity-bounded
1211    /// backend that runs the inline executor (the fixed-capacity user slice and
1212    /// the pre-reserved flat buffer alike). Indicates the frame is corrupt: its
1213    /// sequences expand past the declared `frame_content_size` plus the
1214    /// caller-supplied `WILDCOPY_OVERLENGTH` slack. The fast-path check is
1215    /// per-sequence; the post-block FCS overflow check would also catch the
1216    /// same shape, but the per-sequence guard is what keeps the unsafe write
1217    /// surface inside the buffer on the way to the post-block check.
1218    OutputBufferOverflow {
1219        tail: usize,
1220        requested: usize,
1221        capacity: usize,
1222    },
1223}
1224
1225impl core::fmt::Display for ExecuteSequencesError {
1226    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1227        match self {
1228            ExecuteSequencesError::DecodebufferError(e) => {
1229                write!(f, "{e:?}")
1230            }
1231            ExecuteSequencesError::NotEnoughBytesForSequence { wanted, have } => {
1232                write!(
1233                    f,
1234                    "Sequence wants to copy up to byte {wanted}. Bytes in literalsbuffer: {have}"
1235                )
1236            }
1237            ExecuteSequencesError::ZeroOffset => {
1238                write!(f, "Illegal offset: 0 found")
1239            }
1240            ExecuteSequencesError::OutputBufferOverflow {
1241                tail,
1242                requested,
1243                capacity,
1244            } => {
1245                write!(
1246                    f,
1247                    "Inline sequence would write past the output buffer: tail={tail}, requested={requested}, capacity={capacity}"
1248                )
1249            }
1250        }
1251    }
1252}
1253
1254#[cfg(feature = "std")]
1255impl std::error::Error for ExecuteSequencesError {
1256    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1257        match self {
1258            ExecuteSequencesError::DecodebufferError(source) => Some(source),
1259            _ => None,
1260        }
1261    }
1262}
1263
1264impl From<DecodeBufferError> for ExecuteSequencesError {
1265    fn from(val: DecodeBufferError) -> Self {
1266        Self::DecodebufferError(val)
1267    }
1268}
1269
1270impl From<crate::decoding::buffer_backend::BackendOverflow> for ExecuteSequencesError {
1271    fn from(val: crate::decoding::buffer_backend::BackendOverflow) -> Self {
1272        Self::OutputBufferOverflow {
1273            tail: val.tail,
1274            requested: val.requested,
1275            capacity: val.capacity,
1276        }
1277    }
1278}
1279
1280impl ExecuteSequencesError {
1281    /// `Some(requested)` when this error is a fixed-capacity output-buffer
1282    /// overshoot from the Compressed-block sequence executor — either the
1283    /// inline-sequence path ([`Self::OutputBufferOverflow`]) or the
1284    /// match-repeat fallback ([`DecodeBufferError::OutputBufferOverflow`]
1285    /// wrapped in [`Self::DecodebufferError`]). `requested` is the byte
1286    /// count the failing write tried to append past the slice end.
1287    ///
1288    /// `None` for every non-overflow variant. Used by
1289    /// `FrameDecoder::run_direct_decode` to fold an in-block Compressed
1290    /// overshoot into the same `FrameContentSizeMismatch` contract the
1291    /// Raw/RLE [`DecodeBlockContentError::BackendOverflow`] arm already
1292    /// produces — both mean "the frame's content expands past the
1293    /// declared `frame_content_size`".
1294    pub(crate) fn output_overflow_requested(&self) -> Option<usize> {
1295        match self {
1296            ExecuteSequencesError::OutputBufferOverflow { requested, .. } => Some(*requested),
1297            ExecuteSequencesError::DecodebufferError(DecodeBufferError::OutputBufferOverflow {
1298                requested,
1299                ..
1300            }) => Some(*requested),
1301            _ => None,
1302        }
1303    }
1304}
1305
1306#[derive(Debug)]
1307#[non_exhaustive]
1308pub enum DecodeSequenceError {
1309    GetBitsError(GetBitsError),
1310    FSEDecoderError(FSEDecoderError),
1311    FSETableError(FSETableError),
1312    ExtraPadding {
1313        skipped_bits: i32,
1314    },
1315    UnsupportedOffset {
1316        offset_code: u8,
1317    },
1318    ZeroOffset,
1319    NotEnoughBytesForNumSequences,
1320    ExtraBits {
1321        bits_remaining: isize,
1322    },
1323    MissingCompressionMode,
1324    MissingByteForRleLlTable,
1325    MissingByteForRleOfTable,
1326    MissingByteForRleMlTable,
1327    /// An RLE-mode sequence table's single symbol code is out of range
1328    /// for its axis (LL/ML/OF). `axis` names the axis; `code` is the
1329    /// offending value read from the stream.
1330    InvalidRleCode {
1331        axis: &'static str,
1332        code: u8,
1333    },
1334}
1335
1336#[cfg(feature = "std")]
1337impl std::error::Error for DecodeSequenceError {
1338    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1339        match self {
1340            DecodeSequenceError::GetBitsError(source) => Some(source),
1341            DecodeSequenceError::FSEDecoderError(source) => Some(source),
1342            DecodeSequenceError::FSETableError(source) => Some(source),
1343            _ => None,
1344        }
1345    }
1346}
1347
1348impl core::fmt::Display for DecodeSequenceError {
1349    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1350        match self {
1351            DecodeSequenceError::GetBitsError(e) => write!(f, "{e:?}"),
1352            DecodeSequenceError::FSEDecoderError(e) => write!(f, "{e:?}"),
1353            DecodeSequenceError::FSETableError(e) => write!(f, "{e:?}"),
1354            DecodeSequenceError::ExtraPadding { skipped_bits } => {
1355                write!(
1356                    f,
1357                    "Padding at the end of the sequence_section was more than a byte long: {skipped_bits} bits. Probably caused by data corruption",
1358                )
1359            }
1360            DecodeSequenceError::UnsupportedOffset { offset_code } => {
1361                write!(
1362                    f,
1363                    "Do not support offsets bigger than 1<<32; got: {offset_code}",
1364                )
1365            }
1366            DecodeSequenceError::ZeroOffset => write!(
1367                f,
1368                "Read an offset == 0. That is an illegal value for offsets"
1369            ),
1370            DecodeSequenceError::NotEnoughBytesForNumSequences => write!(
1371                f,
1372                "Bytestream did not contain enough bytes to decode num_sequences"
1373            ),
1374            DecodeSequenceError::ExtraBits { bits_remaining } => write!(f, "{bits_remaining}"),
1375            DecodeSequenceError::MissingCompressionMode => write!(
1376                f,
1377                "compression modes are none but they must be set to something"
1378            ),
1379            DecodeSequenceError::MissingByteForRleLlTable => {
1380                write!(f, "Need a byte to read for RLE ll table")
1381            }
1382            DecodeSequenceError::MissingByteForRleOfTable => {
1383                write!(f, "Need a byte to read for RLE of table")
1384            }
1385            DecodeSequenceError::MissingByteForRleMlTable => {
1386                write!(f, "Need a byte to read for RLE ml table")
1387            }
1388            DecodeSequenceError::InvalidRleCode { axis, code } => {
1389                write!(f, "RLE {axis} table code {code} is out of range")
1390            }
1391        }
1392    }
1393}
1394
1395impl From<GetBitsError> for DecodeSequenceError {
1396    fn from(val: GetBitsError) -> Self {
1397        Self::GetBitsError(val)
1398    }
1399}
1400
1401impl From<FSETableError> for DecodeSequenceError {
1402    fn from(val: FSETableError) -> Self {
1403        Self::FSETableError(val)
1404    }
1405}
1406
1407impl From<FSEDecoderError> for DecodeSequenceError {
1408    fn from(val: FSEDecoderError) -> Self {
1409        Self::FSEDecoderError(val)
1410    }
1411}
1412
1413#[derive(Debug)]
1414#[non_exhaustive]
1415pub enum LiteralsSectionParseError {
1416    IllegalLiteralSectionType { got: u8 },
1417    GetBitsError(GetBitsError),
1418    NotEnoughBytes { have: usize, need: u8 },
1419}
1420
1421#[cfg(feature = "std")]
1422impl std::error::Error for LiteralsSectionParseError {
1423    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1424        match self {
1425            LiteralsSectionParseError::GetBitsError(source) => Some(source),
1426            _ => None,
1427        }
1428    }
1429}
1430impl core::fmt::Display for LiteralsSectionParseError {
1431    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1432        match self {
1433            LiteralsSectionParseError::IllegalLiteralSectionType { got } => {
1434                write!(
1435                    f,
1436                    "Illegal literalssectiontype. Is: {got}, must be in: 0, 1, 2, 3"
1437                )
1438            }
1439            LiteralsSectionParseError::GetBitsError(e) => write!(f, "{e:?}"),
1440            LiteralsSectionParseError::NotEnoughBytes { have, need } => {
1441                write!(
1442                    f,
1443                    "Not enough byte to parse the literals section header. Have: {have}, Need: {need}",
1444                )
1445            }
1446        }
1447    }
1448}
1449
1450impl From<GetBitsError> for LiteralsSectionParseError {
1451    fn from(val: GetBitsError) -> Self {
1452        Self::GetBitsError(val)
1453    }
1454}
1455
1456impl core::fmt::Display for LiteralsSectionType {
1457    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
1458        match self {
1459            LiteralsSectionType::Compressed => write!(f, "Compressed"),
1460            LiteralsSectionType::Raw => write!(f, "Raw"),
1461            LiteralsSectionType::RLE => write!(f, "RLE"),
1462            LiteralsSectionType::Treeless => write!(f, "Treeless"),
1463        }
1464    }
1465}
1466
1467#[derive(Debug)]
1468#[non_exhaustive]
1469pub enum SequencesHeaderParseError {
1470    NotEnoughBytes { need_at_least: u8, got: usize },
1471}
1472
1473#[cfg(feature = "std")]
1474impl std::error::Error for SequencesHeaderParseError {}
1475
1476impl core::fmt::Display for SequencesHeaderParseError {
1477    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1478        match self {
1479            SequencesHeaderParseError::NotEnoughBytes { need_at_least, got } => {
1480                write!(
1481                    f,
1482                    "source must have at least {need_at_least} bytes to parse header; got {got} bytes",
1483                )
1484            }
1485        }
1486    }
1487}
1488
1489#[derive(Debug)]
1490#[non_exhaustive]
1491pub enum FSETableError {
1492    AccLogIsZero,
1493    AccLogTooBig {
1494        got: u8,
1495        max: u8,
1496    },
1497    GetBitsError(GetBitsError),
1498    ProbabilityCounterMismatch {
1499        got: u32,
1500        expected_sum: u32,
1501        symbol_probabilities: Vec<i32>,
1502    },
1503    TooManySymbols {
1504        got: usize,
1505    },
1506    /// Probability value outside the RFC 8878 §4.1.1 allowed set
1507    /// `{-1, 0, 1..=table_size}`. Carries the violating value, the
1508    /// table size (`1 << accuracy_log`) and `accuracy_log` so the
1509    /// caller can pinpoint the failure without re-deriving the bound.
1510    InvalidProbability {
1511        value: i32,
1512        table_size: u32,
1513        accuracy_log: u8,
1514    },
1515    /// `calc_baseline_and_numbits` produced a state-entry whose bit
1516    /// width exceeds the table's accuracy log, violating the
1517    /// `new_state + (1 << num_bits) - 1 < table_size` invariant that
1518    /// the unchecked `read_entry` decode hot path relies on. The
1519    /// triggering probability is in-range per RFC 8878 §4.1.1; the
1520    /// failure is an internal table-shape inconsistency surfaced
1521    /// against the public `build_from_probabilities` API.
1522    TableInvariantViolation {
1523        prob: i32,
1524        symbol: u8,
1525        num_bits: u8,
1526        accuracy_log: u8,
1527    },
1528}
1529
1530#[cfg(feature = "std")]
1531impl std::error::Error for FSETableError {
1532    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1533        match self {
1534            FSETableError::GetBitsError(source) => Some(source),
1535            _ => None,
1536        }
1537    }
1538}
1539
1540impl core::fmt::Display for FSETableError {
1541    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1542        match self {
1543            FSETableError::AccLogIsZero => write!(f, "Acclog must be at least 1"),
1544            FSETableError::AccLogTooBig { got, max } => {
1545                write!(
1546                    f,
1547                    "Found FSE acc_log: {got} bigger than allowed maximum in this case: {max}"
1548                )
1549            }
1550            FSETableError::GetBitsError(e) => write!(f, "{e:?}"),
1551            FSETableError::ProbabilityCounterMismatch {
1552                got,
1553                expected_sum,
1554                symbol_probabilities,
1555            } => {
1556                write!(
1557                    f,
1558                    "FSE probability sum mismatch: got {got}, expected {expected_sum}. Indicates corrupted data or an invalid distribution\n {symbol_probabilities:?}",
1559                )
1560            }
1561            FSETableError::TooManySymbols { got } => {
1562                write!(
1563                    f,
1564                    "There are too many symbols in this distribution: {got}. Max: 256",
1565                )
1566            }
1567            FSETableError::InvalidProbability {
1568                value,
1569                table_size,
1570                accuracy_log,
1571            } => {
1572                write!(
1573                    f,
1574                    "FSE probability value {value} is outside the RFC 8878 allowed set (must be -1, 0, or in 1..={table_size}; accuracy_log={accuracy_log})",
1575                )
1576            }
1577            FSETableError::TableInvariantViolation {
1578                prob,
1579                symbol,
1580                num_bits,
1581                accuracy_log,
1582            } => {
1583                write!(
1584                    f,
1585                    "FSE table invariant violation: symbol {symbol} (prob {prob}) produced num_bits {num_bits} > accuracy_log {accuracy_log}",
1586                )
1587            }
1588        }
1589    }
1590}
1591
1592impl From<GetBitsError> for FSETableError {
1593    fn from(val: GetBitsError) -> Self {
1594        Self::GetBitsError(val)
1595    }
1596}
1597
1598#[derive(Debug)]
1599#[non_exhaustive]
1600pub enum FSEDecoderError {
1601    GetBitsError(GetBitsError),
1602    TableIsUninitialized,
1603    /// Externally constructed `FSETable` violates the
1604    /// `decode.len() == 1 << accuracy_log` shape invariant. Only
1605    /// reachable under `feature = "fuzz-exports"`, where fuzz
1606    /// harnesses can set the `FSETable.decode` / `accuracy_log`
1607    /// fields directly and skip `build_decoding_table`.
1608    InvalidTableShape {
1609        decode_len: usize,
1610        accuracy_log: u8,
1611    },
1612}
1613
1614#[cfg(feature = "std")]
1615impl std::error::Error for FSEDecoderError {
1616    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1617        match self {
1618            FSEDecoderError::GetBitsError(source) => Some(source),
1619            _ => None,
1620        }
1621    }
1622}
1623
1624impl core::fmt::Display for FSEDecoderError {
1625    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1626        match self {
1627            FSEDecoderError::GetBitsError(e) => write!(f, "{e:?}"),
1628            FSEDecoderError::TableIsUninitialized => {
1629                write!(f, "Tried to use an uninitialized table!")
1630            }
1631            FSEDecoderError::InvalidTableShape {
1632                decode_len,
1633                accuracy_log,
1634            } => match 1usize.checked_shl((*accuracy_log).into()) {
1635                Some(expected) => write!(
1636                    f,
1637                    "FSETable shape invariant violated: decode.len() = {decode_len}, expected 1 << accuracy_log = {expected} (accuracy_log = {accuracy_log})",
1638                ),
1639                None => write!(
1640                    f,
1641                    "FSETable shape invariant violated: decode.len() = {decode_len}, accuracy_log = {accuracy_log} overflows 1 << accuracy_log for usize",
1642                ),
1643            },
1644        }
1645    }
1646}
1647
1648impl From<GetBitsError> for FSEDecoderError {
1649    fn from(val: GetBitsError) -> Self {
1650        Self::GetBitsError(val)
1651    }
1652}
1653
1654#[derive(Debug)]
1655#[non_exhaustive]
1656pub enum HuffmanTableError {
1657    GetBitsError(GetBitsError),
1658    FSEDecoderError(FSEDecoderError),
1659    FSETableError(FSETableError),
1660    SourceIsEmpty,
1661    NotEnoughBytesForWeights {
1662        got_bytes: usize,
1663        expected_bytes: u8,
1664    },
1665    ExtraPadding {
1666        skipped_bits: i32,
1667    },
1668    TooManyWeights {
1669        got: usize,
1670    },
1671    MissingWeights,
1672    LeftoverIsNotAPowerOf2 {
1673        got: u32,
1674    },
1675    NotEnoughBytesToDecompressWeights {
1676        have: usize,
1677        need: usize,
1678    },
1679    FSETableUsedTooManyBytes {
1680        used: usize,
1681        available_bytes: u8,
1682    },
1683    NotEnoughBytesInSource {
1684        got: usize,
1685        need: usize,
1686    },
1687    WeightBiggerThanMaxNumBits {
1688        got: u8,
1689    },
1690    MaxBitsTooHigh {
1691        got: u8,
1692    },
1693}
1694
1695#[cfg(feature = "std")]
1696impl StdError for HuffmanTableError {
1697    fn source(&self) -> Option<&(dyn StdError + 'static)> {
1698        match self {
1699            HuffmanTableError::GetBitsError(source) => Some(source),
1700            HuffmanTableError::FSEDecoderError(source) => Some(source),
1701            HuffmanTableError::FSETableError(source) => Some(source),
1702            _ => None,
1703        }
1704    }
1705}
1706
1707impl core::fmt::Display for HuffmanTableError {
1708    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1709        match self {
1710            HuffmanTableError::GetBitsError(e) => write!(f, "{e:?}"),
1711            HuffmanTableError::FSEDecoderError(e) => write!(f, "{e:?}"),
1712            HuffmanTableError::FSETableError(e) => write!(f, "{e:?}"),
1713            HuffmanTableError::SourceIsEmpty => write!(f, "Source needs to have at least one byte"),
1714            HuffmanTableError::NotEnoughBytesForWeights {
1715                got_bytes,
1716                expected_bytes,
1717            } => {
1718                write!(
1719                    f,
1720                    "Header says there should be {expected_bytes} bytes for the weights but there are only {got_bytes} bytes in the stream"
1721                )
1722            }
1723            HuffmanTableError::ExtraPadding { skipped_bits } => {
1724                write!(
1725                    f,
1726                    "Padding at the end of the sequence_section was more than a byte long: {skipped_bits} bits. Probably caused by data corruption",
1727                )
1728            }
1729            HuffmanTableError::TooManyWeights { got } => {
1730                write!(
1731                    f,
1732                    "More than 255 weights decoded (got {got} weights). Stream is probably corrupted",
1733                )
1734            }
1735            HuffmanTableError::MissingWeights => {
1736                write!(f, "Can\'t build huffman table without any weights")
1737            }
1738            HuffmanTableError::LeftoverIsNotAPowerOf2 { got } => {
1739                write!(f, "Leftover must be power of two but is: {got}")
1740            }
1741            HuffmanTableError::NotEnoughBytesToDecompressWeights { have, need } => {
1742                write!(
1743                    f,
1744                    "Not enough bytes in stream to decompress weights. Is: {have}, Should be: {need}",
1745                )
1746            }
1747            HuffmanTableError::FSETableUsedTooManyBytes {
1748                used,
1749                available_bytes,
1750            } => {
1751                write!(
1752                    f,
1753                    "FSE table used more bytes: {used} than were meant to be used for the whole stream of huffman weights ({available_bytes})",
1754                )
1755            }
1756            HuffmanTableError::NotEnoughBytesInSource { got, need } => {
1757                write!(f, "Source needs to have at least {need} bytes, got: {got}",)
1758            }
1759            HuffmanTableError::WeightBiggerThanMaxNumBits { got } => {
1760                write!(
1761                    f,
1762                    "Cant have weight: {} bigger than max_num_bits: {}",
1763                    got,
1764                    crate::huff0::MAX_MAX_NUM_BITS,
1765                )
1766            }
1767            HuffmanTableError::MaxBitsTooHigh { got } => {
1768                write!(
1769                    f,
1770                    "max_bits derived from weights is: {} should be lower than: {}",
1771                    got,
1772                    crate::huff0::MAX_MAX_NUM_BITS,
1773                )
1774            }
1775        }
1776    }
1777}
1778
1779impl From<GetBitsError> for HuffmanTableError {
1780    fn from(val: GetBitsError) -> Self {
1781        Self::GetBitsError(val)
1782    }
1783}
1784
1785impl From<FSEDecoderError> for HuffmanTableError {
1786    fn from(val: FSEDecoderError) -> Self {
1787        Self::FSEDecoderError(val)
1788    }
1789}
1790
1791impl From<FSETableError> for HuffmanTableError {
1792    fn from(val: FSETableError) -> Self {
1793        Self::FSETableError(val)
1794    }
1795}
1796
1797#[derive(Debug)]
1798#[non_exhaustive]
1799pub enum HuffmanDecoderError {
1800    GetBitsError(GetBitsError),
1801}
1802
1803impl core::fmt::Display for HuffmanDecoderError {
1804    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1805        match self {
1806            HuffmanDecoderError::GetBitsError(e) => write!(f, "{e:?}"),
1807        }
1808    }
1809}
1810
1811#[cfg(feature = "std")]
1812impl StdError for HuffmanDecoderError {
1813    fn source(&self) -> Option<&(dyn StdError + 'static)> {
1814        match self {
1815            HuffmanDecoderError::GetBitsError(source) => Some(source),
1816        }
1817    }
1818}
1819
1820impl From<GetBitsError> for HuffmanDecoderError {
1821    fn from(val: GetBitsError) -> Self {
1822        Self::GetBitsError(val)
1823    }
1824}
1825
1826#[cfg(test)]
1827mod tests;