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/// ```no_run
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        let mut ws = Box::new(BlockDecodeWorkspace::new());
52        ws.cache_dict(&dict);
53        Self {
54            dict: Some(dict),
55            output: Vec::new(),
56            ws,
57        }
58    }
59
60    /// Decompresses `input` using [`DEFAULT_DECOMPRESS_LIMIT`](zrip_core::DEFAULT_DECOMPRESS_LIMIT).
61    pub fn decompress(&mut self, input: &[u8]) -> Result<Cow<'_, [u8]>, DecompressError> {
62        self.decompress_with_limit(input, zrip_core::DEFAULT_DECOMPRESS_LIMIT)
63    }
64
65    /// Decompresses `input` with an explicit output size limit.
66    ///
67    /// Returns [`DecompressError::OutputTooSmall`] if the decompressed output
68    /// would exceed `max_output` bytes.
69    pub fn decompress_with_limit(
70        &mut self,
71        input: &[u8],
72        max_output: usize,
73    ) -> Result<Cow<'_, [u8]>, DecompressError> {
74        self.output.clear();
75        let dict_ref = self.dict.as_ref();
76        let mut offset = 0;
77        while offset < input.len() {
78            let remaining = &input[offset..];
79            if let Some(skip_len) = super::skip_skippable_frame(remaining) {
80                offset += skip_len;
81                continue;
82            }
83            let consumed = super::decompress_frame(
84                remaining,
85                &mut self.output,
86                max_output,
87                dict_ref,
88                &mut self.ws,
89            )?;
90            offset += consumed;
91        }
92        if self.output.len() >= zrip_core::LARGE_OUTPUT_THRESHOLD {
93            Ok(Cow::Owned(core::mem::take(&mut self.output)))
94        } else {
95            Ok(Cow::Borrowed(&self.output))
96        }
97    }
98}