Skip to main content

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