zlib_rs/
stable.rs

1use crate::deflate::DeflateConfig;
2use crate::inflate::InflateConfig;
3use crate::ReturnCode;
4pub use crate::{DeflateFlush, InflateFlush};
5
6/// Possible status results of compressing some data or successfully
7/// decompressing a block of data.
8#[derive(Copy, Clone, PartialEq, Eq, Debug)]
9pub enum Status {
10    /// Indicates success.
11    ///
12    /// Means that more input may be needed but isn't available
13    /// and/or there's more output to be written but the output buffer is full.
14    Ok,
15
16    /// Indicates that forward progress is not possible due to input or output
17    /// buffers being empty.
18    ///
19    /// For compression it means the input buffer needs some more data or the
20    /// output buffer needs to be freed up before trying again.
21    ///
22    /// For decompression this means that more input is needed to continue or
23    /// the output buffer isn't large enough to contain the result. The function
24    /// can be called again after fixing both.
25    BufError,
26
27    /// Indicates that all input has been consumed and all output bytes have
28    /// been written. Decompression/compression should not be called again.
29    ///
30    /// For decompression with zlib streams the adler-32 of the decompressed
31    /// data has also been verified.
32    StreamEnd,
33}
34
35/// Errors that can occur when decompressing.
36#[derive(Copy, Clone, PartialEq, Eq, Debug)]
37#[repr(i32)]
38pub enum InflateError {
39    /// Decompressing this input requires a dictionary.
40    NeedDict { dict_id: u32 } = 2,
41    /// The [`Inflate`] is in an inconsistent state, most likely
42    /// due to an invalid configuration parameter.
43    StreamError = -2,
44    /// The input is not a valid deflate stream.
45    DataError = -3,
46    /// A memory allocation failed.
47    MemError = -4,
48}
49
50impl From<InflateError> for ReturnCode {
51    fn from(value: InflateError) -> Self {
52        match value {
53            InflateError::NeedDict { .. } => ReturnCode::NeedDict,
54            InflateError::StreamError => ReturnCode::StreamError,
55            InflateError::DataError => ReturnCode::DataError,
56            InflateError::MemError => ReturnCode::MemError,
57        }
58    }
59}
60
61impl InflateError {
62    pub fn as_str(self) -> &'static str {
63        ReturnCode::from(self).error_message_str()
64    }
65}
66
67/// The state that is used to decompress an input.
68pub struct Inflate(crate::inflate::InflateStream<'static>);
69
70impl Inflate {
71    /// The amount of bytes consumed from the input so far.
72    pub fn total_in(&self) -> u64 {
73        #[allow(clippy::useless_conversion)]
74        u64::from(self.0.total_in)
75    }
76
77    /// The amount of decompressed bytes that have been written to the output thus far.
78    pub fn total_out(&self) -> u64 {
79        #[allow(clippy::useless_conversion)]
80        u64::from(self.0.total_out)
81    }
82
83    /// The error message if the previous operation failed.
84    pub fn error_message(&self) -> Option<&'static str> {
85        if self.0.msg.is_null() {
86            None
87        } else {
88            unsafe { core::ffi::CStr::from_ptr(self.0.msg).to_str() }.ok()
89        }
90    }
91
92    /// Create a new instance. Note that it allocates in various ways and thus should be re-used.
93    ///
94    /// The `window_bits` must be in the range `8..=15`, with `15` being most common.
95    pub fn new(zlib_header: bool, window_bits: u8) -> Self {
96        let config = InflateConfig {
97            window_bits: if zlib_header {
98                i32::from(window_bits)
99            } else {
100                -i32::from(window_bits)
101            },
102        };
103
104        Self(crate::inflate::InflateStream::new(config))
105    }
106
107    /// Reset the state to allow handling a new stream.
108    pub fn reset(&mut self, zlib_header: bool) {
109        let mut config = InflateConfig::default();
110
111        if !zlib_header {
112            config.window_bits = -config.window_bits;
113        }
114
115        crate::inflate::reset_with_config(&mut self.0, config);
116    }
117
118    /// Decompress `input` and write all decompressed bytes into `output`, with `flush` defining some details about this.
119    pub fn decompress(
120        &mut self,
121        input: &[u8],
122        output: &mut [u8],
123        flush: InflateFlush,
124    ) -> Result<Status, InflateError> {
125        self.0.avail_in = input.len() as _;
126        self.0.avail_out = output.len() as _;
127
128        // This cast_mut is unfortunate, that is just how the types are.
129        self.0.next_in = input.as_ptr().cast_mut();
130        self.0.next_out = output.as_mut_ptr();
131
132        // SAFETY: the inflate state was properly initialized.
133        match unsafe { crate::inflate::inflate(&mut self.0, flush) } {
134            ReturnCode::Ok => Ok(Status::Ok),
135            ReturnCode::StreamEnd => Ok(Status::StreamEnd),
136            ReturnCode::NeedDict => Err(InflateError::NeedDict {
137                dict_id: self.0.adler as u32,
138            }),
139            ReturnCode::ErrNo => unreachable!("the rust API does not use files"),
140            ReturnCode::StreamError => Err(InflateError::StreamError),
141            ReturnCode::DataError => Err(InflateError::DataError),
142            ReturnCode::MemError => Err(InflateError::MemError),
143            ReturnCode::BufError => Ok(Status::BufError),
144            ReturnCode::VersionError => unreachable!("the rust API does not use the version"),
145        }
146    }
147
148    pub fn set_dictionary(&mut self, dictionary: &[u8]) -> Result<u32, InflateError> {
149        match crate::inflate::set_dictionary(&mut self.0, dictionary) {
150            ReturnCode::Ok => Ok(self.0.adler as u32),
151            ReturnCode::StreamError => Err(InflateError::StreamError),
152            ReturnCode::DataError => Err(InflateError::DataError),
153            other => unreachable!("set_dictionary does not return {other:?}"),
154        }
155    }
156}
157
158impl Drop for Inflate {
159    fn drop(&mut self) {
160        let _ = crate::inflate::end(&mut self.0);
161    }
162}
163
164/// Errors that can occur when compressing.
165#[derive(Copy, Clone, PartialEq, Eq, Debug)]
166pub enum DeflateError {
167    /// The [`Deflate`] is in an inconsistent state, most likely
168    /// due to an invalid configuration parameter.
169    StreamError = -2,
170    /// The input is not a valid deflate stream.
171    DataError = -3,
172    /// A memory allocation failed.
173    MemError = -4,
174}
175
176impl From<DeflateError> for ReturnCode {
177    fn from(value: DeflateError) -> Self {
178        match value {
179            DeflateError::StreamError => ReturnCode::StreamError,
180            DeflateError::DataError => ReturnCode::DataError,
181            DeflateError::MemError => ReturnCode::MemError,
182        }
183    }
184}
185
186impl DeflateError {
187    pub fn as_str(self) -> &'static str {
188        ReturnCode::from(self).error_message_str()
189    }
190}
191
192impl From<ReturnCode> for Result<Status, DeflateError> {
193    fn from(value: ReturnCode) -> Self {
194        match value {
195            ReturnCode::Ok => Ok(Status::Ok),
196            ReturnCode::StreamEnd => Ok(Status::StreamEnd),
197            ReturnCode::NeedDict => unreachable!("compression does not use dictionary"),
198            ReturnCode::ErrNo => unreachable!("the rust API does not use files"),
199            ReturnCode::StreamError => Err(DeflateError::StreamError),
200            ReturnCode::DataError => Err(DeflateError::DataError),
201            ReturnCode::MemError => Err(DeflateError::MemError),
202            ReturnCode::BufError => Ok(Status::BufError),
203            ReturnCode::VersionError => unreachable!("the rust API does not use the version"),
204        }
205    }
206}
207
208/// The state that is used to compress an input.
209pub struct Deflate(crate::deflate::DeflateStream<'static>);
210
211impl Deflate {
212    /// The number of bytes that were read from the input.
213    pub fn total_in(&self) -> u64 {
214        #[allow(clippy::useless_conversion)]
215        u64::from(self.0.total_in)
216    }
217
218    /// The number of compressed bytes that were written to the output.
219    pub fn total_out(&self) -> u64 {
220        #[allow(clippy::useless_conversion)]
221        u64::from(self.0.total_out)
222    }
223
224    /// The error message if the previous operation failed.
225    pub fn error_message(&self) -> Option<&'static str> {
226        if self.0.msg.is_null() {
227            None
228        } else {
229            unsafe { core::ffi::CStr::from_ptr(self.0.msg).to_str() }.ok()
230        }
231    }
232
233    /// Create a new instance - this allocates so should be done with care.
234    ///
235    /// The `window_bits` must be in the range `8..=15`, with `15` being most common.
236    pub fn new(level: i32, zlib_header: bool, window_bits: u8) -> Self {
237        let config = DeflateConfig {
238            window_bits: if zlib_header {
239                i32::from(window_bits)
240            } else {
241                -i32::from(window_bits)
242            },
243            level,
244            ..DeflateConfig::default()
245        };
246
247        Self(crate::deflate::DeflateStream::new(config))
248    }
249
250    /// Prepare the instance for a new stream.
251    pub fn reset(&mut self) {
252        crate::deflate::reset(&mut self.0);
253    }
254
255    /// Compress `input` and write compressed bytes to `output`, with `flush` controlling additional characteristics.
256    pub fn compress(
257        &mut self,
258        input: &[u8],
259        output: &mut [u8],
260        flush: DeflateFlush,
261    ) -> Result<Status, DeflateError> {
262        self.0.avail_in = input.len() as _;
263        self.0.avail_out = output.len() as _;
264
265        // This cast_mut is unfortunate, that is just how the types are.
266        self.0.next_in = input.as_ptr().cast_mut();
267        self.0.next_out = output.as_mut_ptr();
268
269        crate::deflate::deflate(&mut self.0, flush).into()
270    }
271
272    /// Specifies the compression dictionary to use.
273    ///
274    /// Returns the Adler-32 checksum of the dictionary.
275    pub fn set_dictionary(&mut self, dictionary: &[u8]) -> Result<u32, DeflateError> {
276        match crate::deflate::set_dictionary(&mut self.0, dictionary) {
277            ReturnCode::Ok => Ok(self.0.adler as u32),
278            ReturnCode::StreamError => Err(DeflateError::StreamError),
279            other => unreachable!("set_dictionary does not return {other:?}"),
280        }
281    }
282
283    /// Dynamically updates the compression level.
284    ///
285    /// This can be used to switch between compression levels for different
286    /// kinds of data, or it can be used in conjunction with a call to [`Deflate::reset`]
287    /// to reuse the compressor.
288    ///
289    /// This may return an error if there wasn't enough output space to complete
290    /// the compression of the available input data before changing the
291    /// compression level. Flushing the stream before calling this method
292    /// ensures that the function will succeed on the first call.
293    pub fn set_level(&mut self, level: i32) -> Result<Status, DeflateError> {
294        match crate::deflate::params(&mut self.0, level, Default::default()) {
295            ReturnCode::Ok => Ok(Status::Ok),
296            ReturnCode::StreamError => Err(DeflateError::StreamError),
297            ReturnCode::BufError => Ok(Status::BufError),
298            other => unreachable!("set_level does not return {other:?}"),
299        }
300    }
301}
302
303impl Drop for Deflate {
304    fn drop(&mut self) {
305        let _ = crate::deflate::end(&mut self.0);
306    }
307}