Skip to main content

zrip_decode/
context.rs

1#[cfg(feature = "alloc")]
2use alloc::borrow::Cow;
3#[cfg(feature = "alloc")]
4use alloc::boxed::Box;
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7
8use crate::BlockDecodeWorkspace;
9use zrip_core::dict::Dictionary;
10use zrip_core::error::DecompressError;
11
12/// Reusable decompression context that amortizes buffer allocations.
13///
14/// Holds internal buffers (output, Huffman/FSE workspace) across calls.
15/// Useful when decompressing many small frames in a loop.
16///
17/// ```
18/// let data = b"repeated decompression".repeat(100);
19/// let compressed = zrip::compress(&data, 1).unwrap();
20///
21/// let mut ctx = zrip::DecompressContext::new();
22/// for _ in 0..10 {
23///     let output = ctx.decompress(&compressed).unwrap();
24///     assert_eq!(&*output, &data[..]);
25/// }
26/// ```
27pub struct DecompressContext {
28    dict: Option<Dictionary>,
29    output: Vec<u8>,
30    ws: Box<BlockDecodeWorkspace>,
31}
32
33impl Default for DecompressContext {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl DecompressContext {
40    /// Creates a new context without a dictionary.
41    pub fn new() -> Self {
42        Self {
43            dict: None,
44            output: Vec::new(),
45            ws: Box::new(BlockDecodeWorkspace::new()),
46        }
47    }
48
49    /// Creates a new context with a pre-loaded dictionary.
50    pub fn with_dict(dict: Dictionary) -> Self {
51        Self {
52            dict: Some(dict),
53            output: Vec::new(),
54            ws: Box::new(BlockDecodeWorkspace::new()),
55        }
56    }
57
58    /// Decompresses `input` using [`DEFAULT_DECOMPRESS_LIMIT`](zrip_core::DEFAULT_DECOMPRESS_LIMIT).
59    pub fn decompress(&mut self, input: &[u8]) -> Result<Cow<'_, [u8]>, DecompressError> {
60        self.decompress_with_limit(input, zrip_core::DEFAULT_DECOMPRESS_LIMIT)
61    }
62
63    /// Decompresses `input` with an explicit output size limit.
64    ///
65    /// Returns [`DecompressError::OutputTooSmall`] if the decompressed output
66    /// would exceed `max_output` bytes.
67    pub fn decompress_with_limit(
68        &mut self,
69        input: &[u8],
70        max_output: usize,
71    ) -> Result<Cow<'_, [u8]>, DecompressError> {
72        self.output.clear();
73        let dict_ref = self.dict.as_ref();
74        let mut offset = 0;
75        while offset < input.len() {
76            let remaining = &input[offset..];
77            if let Some(skip_len) = super::skip_skippable_frame(remaining) {
78                offset += skip_len;
79                continue;
80            }
81            let consumed = super::decompress_frame(
82                remaining,
83                &mut self.output,
84                max_output,
85                dict_ref,
86                &mut self.ws,
87            )?;
88            offset += consumed;
89        }
90        if self.output.len() >= zrip_core::LARGE_OUTPUT_THRESHOLD {
91            Ok(Cow::Owned(core::mem::take(&mut self.output)))
92        } else {
93            Ok(Cow::Borrowed(&self.output))
94        }
95    }
96}