gix_features/zlib/
mod.rs

1use zlib_rs::InflateError;
2
3/// A type to hold all state needed for decompressing a ZLIB encoded stream.
4pub struct Decompress(zlib_rs::Inflate);
5
6impl Default for Decompress {
7    fn default() -> Self {
8        Self::new()
9    }
10}
11
12impl Decompress {
13    /// The amount of bytes consumed from the input so far.
14    pub fn total_in(&self) -> u64 {
15        self.0.total_in()
16    }
17
18    /// The amount of decompressed bytes that have been written to the output thus far.
19    pub fn total_out(&self) -> u64 {
20        self.0.total_out()
21    }
22
23    /// Create a new instance. Note that it allocates in various ways and thus should be re-used.
24    pub fn new() -> Self {
25        let inner = zlib_rs::Inflate::new(true, zlib_rs::MAX_WBITS as u8);
26        Self(inner)
27    }
28
29    /// Reset the state to allow handling a new stream.
30    pub fn reset(&mut self) {
31        self.0.reset(true);
32    }
33
34    /// Decompress `input` and write all decompressed bytes into `output`, with `flush` defining some details about this.
35    pub fn decompress(
36        &mut self,
37        input: &[u8],
38        output: &mut [u8],
39        flush: FlushDecompress,
40    ) -> Result<Status, DecompressError> {
41        let inflate_flush = match flush {
42            FlushDecompress::None => zlib_rs::InflateFlush::NoFlush,
43            FlushDecompress::Sync => zlib_rs::InflateFlush::SyncFlush,
44            FlushDecompress::Finish => zlib_rs::InflateFlush::Finish,
45        };
46
47        let status = self.0.decompress(input, output, inflate_flush)?;
48        match status {
49            zlib_rs::Status::Ok => Ok(Status::Ok),
50            zlib_rs::Status::BufError => Ok(Status::BufError),
51            zlib_rs::Status::StreamEnd => Ok(Status::StreamEnd),
52        }
53    }
54}
55
56/// The error produced by [`Decompress::decompress()`].
57#[derive(Debug, thiserror::Error)]
58#[allow(missing_docs)]
59pub enum DecompressError {
60    #[error("stream error")]
61    StreamError,
62    #[error("Not enough memory")]
63    InsufficientMemory,
64    #[error("Invalid input data")]
65    DataError,
66    #[error("Decompressing this input requires a dictionary")]
67    NeedDict,
68}
69
70impl From<zlib_rs::InflateError> for DecompressError {
71    fn from(value: InflateError) -> Self {
72        match value {
73            InflateError::NeedDict { .. } => DecompressError::NeedDict,
74            InflateError::StreamError => DecompressError::StreamError,
75            InflateError::DataError => DecompressError::DataError,
76            InflateError::MemError => DecompressError::InsufficientMemory,
77        }
78    }
79}
80
81/// The status returned by [`Decompress::decompress()`].
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Status {
84    /// The decompress operation went well. Not to be confused with `StreamEnd`, so one can continue
85    /// the decompression.
86    Ok,
87    /// An error occurred when decompression.
88    BufError,
89    /// The stream was fully decompressed.
90    StreamEnd,
91}
92
93/// Values which indicate the form of flushing to be used when
94/// decompressing in-memory data.
95#[derive(Copy, Clone, PartialEq, Eq, Debug)]
96#[non_exhaustive]
97#[allow(clippy::unnecessary_cast)]
98pub enum FlushDecompress {
99    /// A typical parameter for passing to compression/decompression functions,
100    /// this indicates that the underlying stream to decide how much data to
101    /// accumulate before producing output in order to maximize compression.
102    None = 0,
103
104    /// All pending output is flushed to the output buffer and the output is
105    /// aligned on a byte boundary so that the decompressor can get all input
106    /// data available so far.
107    ///
108    /// Flushing may degrade compression for some compression algorithms and so
109    /// it should only be used when necessary. This will complete the current
110    /// deflate block and follow it with an empty stored block.
111    Sync = 2,
112
113    /// Pending input is processed and pending output is flushed.
114    ///
115    /// The return value may indicate that the stream is not yet done and more
116    /// data has yet to be processed.
117    Finish = 4,
118}
119
120/// non-streaming interfaces for decompression
121pub mod inflate {
122    /// The error returned by various [Inflate methods][super::Inflate]
123    #[derive(Debug, thiserror::Error)]
124    #[allow(missing_docs)]
125    pub enum Error {
126        #[error("Could not write all bytes when decompressing content")]
127        WriteInflated(#[from] std::io::Error),
128        #[error("Could not decode zip stream, status was '{0}'")]
129        Inflate(#[from] super::DecompressError),
130        #[error("The zlib status indicated an error, status was '{0:?}'")]
131        Status(super::Status),
132    }
133}
134
135/// Decompress a few bytes of a zlib stream without allocation
136#[derive(Default)]
137pub struct Inflate {
138    /// The actual decompressor doing all the work.
139    pub state: Decompress,
140}
141
142impl Inflate {
143    /// Run the decompressor exactly once. Cannot be run multiple times
144    pub fn once(&mut self, input: &[u8], out: &mut [u8]) -> Result<(Status, usize, usize), inflate::Error> {
145        let before_in = self.state.total_in();
146        let before_out = self.state.total_out();
147        let status = self.state.decompress(input, out, FlushDecompress::None)?;
148        Ok((
149            status,
150            (self.state.total_in() - before_in) as usize,
151            (self.state.total_out() - before_out) as usize,
152        ))
153    }
154
155    /// Ready this instance for decoding another data stream.
156    pub fn reset(&mut self) {
157        self.state.reset();
158    }
159}
160
161///
162pub mod stream;