gix_features/zlib/
mod.rs

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