Skip to main content

libzstd_bitexact_rs/
decompress.rs

1//! Top-level decompression: frame iteration and the block loop
2//! (`ZSTD_decompressMultiFrame` / `ZSTD_decompressFrame`).
3
4use crate::block::{self, BLOCK_SIZE_MAX, FrameContext};
5use crate::dictionary::Dictionary;
6use crate::error::Error;
7use crate::frame;
8use crate::xxhash::xxh64;
9
10pub(crate) const ZSTD_MAGIC: u32 = 0xFD2F_B528;
11pub(crate) const SKIPPABLE_MAGIC_MASK: u32 = 0xFFFF_FFF0;
12pub(crate) const SKIPPABLE_MAGIC: u32 = 0x184D_2A50;
13
14/// Cap on speculative preallocation from the declared content size, so a
15/// frame header lying about its size cannot trigger a huge allocation.
16const MAX_PREALLOC: u64 = 32 << 20;
17
18/// `ZSTD_WINDOWLOG_MAX` on 64-bit targets: the largest window log the format
19/// permits, and the default ceiling [`DecodeOptions`] enforces. It is also the
20/// hard cap — raising [`DecodeOptions::window_log_max`] above it has no effect,
21/// since larger windows are invalid regardless.
22pub const WINDOW_LOG_MAX: u32 = 31;
23
24/// Configurable decompression: an output-size limit, a maximum accepted window
25/// log, and an optional dictionary.
26///
27/// ```
28/// use libzstd_bitexact_rs::DecodeOptions;
29/// let frame = [0x28, 0xB5, 0x2F, 0xFD, 0x20, 0x05, 0x2B, 0x00, 0x00, b'a'];
30/// let out = DecodeOptions::new().limit(1 << 20).decompress(&frame).unwrap();
31/// assert_eq!(out, b"aaaaa");
32/// ```
33#[derive(Clone)]
34pub struct DecodeOptions<'d> {
35    limit: usize,
36    window_log_max: u32,
37    dictionary: Option<&'d Dictionary>,
38}
39
40impl Default for DecodeOptions<'_> {
41    fn default() -> Self {
42        DecodeOptions {
43            limit: usize::MAX,
44            window_log_max: WINDOW_LOG_MAX,
45            dictionary: None,
46        }
47    }
48}
49
50impl<'d> DecodeOptions<'d> {
51    /// Default options: no output limit, window log up to [`WINDOW_LOG_MAX`],
52    /// no dictionary.
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    /// Fail with [`Error::OutputTooLarge`] once the output would exceed
58    /// `limit` bytes. Use this on untrusted input to defuse decompression
59    /// bombs.
60    pub fn limit(mut self, limit: usize) -> Self {
61        self.limit = limit;
62        self
63    }
64
65    /// Reject frames whose declared window log exceeds `window_log_max`
66    /// (`ZSTD_d_windowLogMax`). Values above [`WINDOW_LOG_MAX`] are treated as
67    /// [`WINDOW_LOG_MAX`].
68    pub fn window_log_max(mut self, window_log_max: u32) -> Self {
69        self.window_log_max = window_log_max;
70        self
71    }
72
73    /// Decode using `dictionary` as window history and entropy-table seed
74    /// (`ZSTD_decompress_usingDict`). The dictionary applies to every frame in
75    /// the input.
76    pub fn dictionary(mut self, dictionary: &'d Dictionary) -> Self {
77        self.dictionary = Some(dictionary);
78        self
79    }
80
81    pub(crate) fn limit_value(&self) -> usize {
82        self.limit
83    }
84
85    pub(crate) fn window_log_max_value(&self) -> u32 {
86        self.window_log_max
87    }
88
89    pub(crate) fn dictionary_value(&self) -> Option<&'d Dictionary> {
90        self.dictionary
91    }
92
93    /// Decompress a sequence of frames with these options.
94    pub fn decompress(&self, src: &[u8]) -> Result<Vec<u8>, Error> {
95        let mut out = Vec::new();
96        let mut input = src;
97        while !input.is_empty() {
98            if input.len() < 4 {
99                return Err(Error::SrcSizeWrong);
100            }
101            let magic = u32::from_le_bytes(input[..4].try_into().unwrap());
102            if magic == ZSTD_MAGIC {
103                input = self.decode_frame(&input[4..], &mut out)?;
104            } else if magic & SKIPPABLE_MAGIC_MASK == SKIPPABLE_MAGIC {
105                if input.len() < 8 {
106                    return Err(Error::SrcSizeWrong);
107                }
108                let size = u32::from_le_bytes(input[4..8].try_into().unwrap()) as usize;
109                input = input.get(8 + size..).ok_or(Error::SrcSizeWrong)?;
110            } else {
111                return Err(Error::UnknownMagic(magic));
112            }
113        }
114        Ok(out)
115    }
116
117    /// Decode one frame (after its magic) into `out`; returns the rest of the
118    /// input following the frame.
119    fn decode_frame<'a>(&self, src: &'a [u8], out: &mut Vec<u8>) -> Result<&'a [u8], Error> {
120        let header = frame::parse(src, self.window_log_max)?;
121
122        // Resolve the dictionary against the frame's declared ID. A frame ID
123        // of zero accepts whatever dictionary the caller supplied (raw-content
124        // dictionaries always carry ID zero); a non-zero ID must match.
125        let dict = match (self.dictionary, header.dict_id) {
126            (_, 0) => self.dictionary,
127            (Some(d), id) if d.id() == id => Some(d),
128            (Some(d), id) => {
129                return Err(Error::DictionaryWrong {
130                    expected: id,
131                    actual: d.id(),
132                });
133            }
134            (None, id) => return Err(Error::DictionaryRequired(id)),
135        };
136        let dict_content = dict.map_or(&[][..], Dictionary::content);
137
138        let frame_base = out.len();
139        if let Some(fcs) = header.content_size {
140            if fcs as u128 + frame_base as u128 > self.limit as u128 {
141                return Err(Error::OutputTooLarge);
142            }
143            out.reserve(fcs.min(MAX_PREALLOC) as usize);
144        }
145        let block_size_max = header.window_size.min(BLOCK_SIZE_MAX as u64) as usize;
146
147        let mut ctx = FrameContext::with_dictionary(dict);
148        let mut input = &src[header.header_len..];
149        loop {
150            let bh = input.get(..3).ok_or(Error::SrcSizeWrong)?;
151            let raw = u32::from(bh[0]) | u32::from(bh[1]) << 8 | u32::from(bh[2]) << 16;
152            input = &input[3..];
153            let last = raw & 1 != 0;
154            let block_type = (raw >> 1) & 3;
155            let size = (raw >> 3) as usize;
156
157            match block_type {
158                // Raw_Block: `size` bytes copied verbatim.
159                0 => {
160                    if size > block_size_max {
161                        return Err(Error::Corrupted("block size exceeds block size limit"));
162                    }
163                    let data = input.get(..size).ok_or(Error::SrcSizeWrong)?;
164                    if out.len() + size > self.limit {
165                        return Err(Error::OutputTooLarge);
166                    }
167                    out.extend_from_slice(data);
168                    input = &input[size..];
169                }
170                // RLE_Block: one byte, repeated `size` times.
171                1 => {
172                    if size > block_size_max {
173                        return Err(Error::Corrupted("block size exceeds block size limit"));
174                    }
175                    let byte = *input.first().ok_or(Error::SrcSizeWrong)?;
176                    if out.len() + size > self.limit {
177                        return Err(Error::OutputTooLarge);
178                    }
179                    out.resize(out.len() + size, byte);
180                    input = &input[1..];
181                }
182                // Compressed_Block.
183                2 => {
184                    if size > block_size_max {
185                        return Err(Error::Corrupted("block size exceeds block size limit"));
186                    }
187                    let data = input.get(..size).ok_or(Error::SrcSizeWrong)?;
188                    block::decode_compressed_block(
189                        &mut ctx,
190                        data,
191                        out,
192                        frame_base,
193                        dict_content,
194                        block_size_max,
195                        self.limit,
196                    )?;
197                    input = &input[size..];
198                }
199                _ => return Err(Error::BlockTypeInvalid),
200            }
201
202            if last {
203                break;
204            }
205        }
206
207        if let Some(fcs) = header.content_size {
208            if (out.len() - frame_base) as u64 != fcs {
209                return Err(Error::FrameContentSizeMismatch);
210            }
211        }
212
213        if header.has_checksum {
214            let stored = input.get(..4).ok_or(Error::SrcSizeWrong)?;
215            let expected = u32::from_le_bytes(stored.try_into().unwrap());
216            let actual = xxh64(&out[frame_base..], 0) as u32;
217            if expected != actual {
218                return Err(Error::ChecksumMismatch { expected, actual });
219            }
220            input = &input[4..];
221        }
222
223        Ok(input)
224    }
225}
226
227/// Decompress a sequence of Zstandard frames (and/or skippable frames).
228///
229/// This is the equivalent of `ZSTD_decompress`: all frames in `src` are
230/// decoded back to back and their contents concatenated. Trailing bytes that
231/// do not form a complete frame are an error. Empty input yields empty
232/// output.
233pub fn decompress(src: &[u8]) -> Result<Vec<u8>, Error> {
234    DecodeOptions::new().decompress(src)
235}
236
237/// Like [`decompress`], but fails with [`Error::OutputTooLarge`] once the
238/// output would exceed `limit` bytes. Use this on untrusted input to defuse
239/// decompression bombs.
240pub fn decompress_with_limit(src: &[u8], limit: usize) -> Result<Vec<u8>, Error> {
241    DecodeOptions::new().limit(limit).decompress(src)
242}