miniz_oxide/inflate/
stream.rs

1//! Extra streaming decompression functionality.
2//!
3//! As of now this is mainly inteded for use to build a higher-level wrapper.
4use std::io::Cursor;
5use std::{cmp, mem};
6
7use crate::inflate::core::{decompress, inflate_flags, DecompressorOxide, TINFL_LZ_DICT_SIZE};
8use crate::inflate::TINFLStatus;
9use crate::{DataFormat, MZError, MZFlush, MZResult, MZStatus, StreamResult};
10
11/// A struct that compbines a decompressor with extra data for streaming decompression.
12///
13pub struct InflateState {
14    /// Inner decompressor struct
15    decomp: DecompressorOxide,
16
17    /// Buffer of input bytes for matches.
18    /// TODO: Could probably do this a bit cleaner with some
19    /// Cursor-like class.
20    /// We may also look into whether we need to keep a buffer here, or just one in the
21    /// decompressor struct.
22    dict: [u8; TINFL_LZ_DICT_SIZE],
23    /// Where in the buffer are we currently at?
24    dict_ofs: usize,
25    /// How many bytes of data to be flushed is there currently in the buffer?
26    dict_avail: usize,
27
28    first_call: bool,
29    has_flushed: bool,
30
31    /// Whether the input data is wrapped in a zlib header and checksum.
32    /// TODO: This should be stored in the decompressor.
33    data_format: DataFormat,
34    last_status: TINFLStatus,
35}
36
37impl Default for InflateState {
38    fn default() -> Self {
39        InflateState {
40            decomp: DecompressorOxide::default(),
41            dict: [0; TINFL_LZ_DICT_SIZE],
42            dict_ofs: 0,
43            dict_avail: 0,
44            first_call: true,
45            has_flushed: false,
46            data_format: DataFormat::Raw,
47            last_status: TINFLStatus::NeedsMoreInput,
48        }
49    }
50}
51impl InflateState {
52    /// Create a new state.
53    ///
54    /// Note that this struct is quite large due to internal buffers, and as such storing it on
55    /// the stack is not recommended.
56    ///
57    /// # Parameters
58    /// `data_format`: Determines whether the compressed data is assumed to wrapped with zlib
59    /// metadata.
60    pub fn new(data_format: DataFormat) -> InflateState {
61        let mut b = InflateState::default();
62        b.data_format = data_format;
63        b
64    }
65
66    /// Create a new state on the heap.
67    ///
68    /// # Parameters
69    /// `data_format`: Determines whether the compressed data is assumed to wrapped with zlib
70    /// metadata.
71    pub fn new_boxed(data_format: DataFormat) -> Box<InflateState> {
72        let mut b: Box<InflateState> = Box::default();
73        b.data_format = data_format;
74        b
75    }
76
77    /// Access the innner decompressor.
78    pub fn decompressor(&mut self) -> &mut DecompressorOxide {
79        &mut self.decomp
80    }
81
82    /// Return the status of the last call to `inflate` with this `InflateState`.
83    pub fn last_status(&self) -> TINFLStatus {
84        self.last_status
85    }
86
87    /// Create a new state using miniz/zlib style window bits parameter.
88    ///
89    /// The decompressor does not support different window sizes. As such,
90    /// any positive (>0) value will set the zlib header flag, while a negative one
91    /// will not.
92    pub fn new_boxed_with_window_bits(window_bits: i32) -> Box<InflateState> {
93        let mut b: Box<InflateState> = Box::default();
94        b.data_format = DataFormat::from_window_bits(window_bits);
95        b
96    }
97
98    /// Reset the decompressor without re-allocating memory, using the given
99    /// data format.
100    pub fn reset(&mut self, data_format: DataFormat) {
101        self.decompressor().init();
102        self.dict = [0; TINFL_LZ_DICT_SIZE];
103        self.dict_ofs = 0;
104        self.dict_avail = 0;
105        self.first_call = true;
106        self.has_flushed = false;
107        self.data_format = data_format;
108        self.last_status = TINFLStatus::NeedsMoreInput;
109    }
110}
111
112/// Try to decompress from `input` to `output` with the given `InflateState`
113///
114/// # Errors
115///
116/// Returns `MZError::Buf` If the size of the `output` slice is empty or no progress was made due to
117/// lack of expected input data or called after the decompression was
118/// finished without MZFlush::Finish.
119///
120/// Returns `MZError::Param` if the compressor parameters are set wrong.
121pub fn inflate(
122    state: &mut InflateState,
123    input: &[u8],
124    output: &mut [u8],
125    flush: MZFlush,
126) -> StreamResult {
127    let mut bytes_consumed = 0;
128    let mut bytes_written = 0;
129    let mut next_in = input;
130    let mut next_out = output;
131
132    if flush == MZFlush::Full {
133        return StreamResult::error(MZError::Stream);
134    }
135
136    let mut decomp_flags = inflate_flags::TINFL_FLAG_COMPUTE_ADLER32;
137    if state.data_format == DataFormat::Zlib {
138        decomp_flags |= inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER;
139    }
140
141    let first_call = state.first_call;
142    state.first_call = false;
143    if (state.last_status as i32) < 0 {
144        return StreamResult::error(MZError::Data);
145    }
146
147    if state.has_flushed && (flush != MZFlush::Finish) {
148        return StreamResult::error(MZError::Stream);
149    }
150    state.has_flushed |= flush == MZFlush::Finish;
151
152    if (flush == MZFlush::Finish) && first_call {
153        decomp_flags |= inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
154
155        let status = decompress(
156            &mut state.decomp,
157            next_in,
158            &mut Cursor::new(next_out),
159            decomp_flags,
160        );
161        let in_bytes = status.1;
162        let out_bytes = status.2;
163        let status = status.0;
164
165        state.last_status = status;
166
167        bytes_consumed += in_bytes;
168        bytes_written += out_bytes;
169
170        let ret_status = {
171            if (status as i32) < 0 {
172                Err(MZError::Data)
173            } else if status != TINFLStatus::Done {
174                state.last_status = TINFLStatus::Failed;
175                Err(MZError::Buf)
176            } else {
177                Ok(MZStatus::StreamEnd)
178            }
179        };
180        return StreamResult {
181            bytes_consumed,
182            bytes_written,
183            status: ret_status,
184        };
185    }
186
187    if flush != MZFlush::Finish {
188        decomp_flags |= inflate_flags::TINFL_FLAG_HAS_MORE_INPUT;
189    }
190
191    if state.dict_avail != 0 {
192        bytes_written += push_dict_out(state, &mut next_out);
193        return StreamResult {
194            bytes_consumed,
195            bytes_written,
196            status: Ok(
197                if (state.last_status == TINFLStatus::Done) && (state.dict_avail == 0) {
198                    MZStatus::StreamEnd
199                } else {
200                    MZStatus::Ok
201                },
202            ),
203        };
204    }
205
206    let status = inflate_loop(
207        state,
208        &mut next_in,
209        &mut next_out,
210        &mut bytes_consumed,
211        &mut bytes_written,
212        decomp_flags,
213        flush,
214    );
215    StreamResult {
216        bytes_consumed,
217        bytes_written,
218        status,
219    }
220}
221
222fn inflate_loop(
223    state: &mut InflateState,
224    next_in: &mut &[u8],
225    next_out: &mut &mut [u8],
226    total_in: &mut usize,
227    total_out: &mut usize,
228    decomp_flags: u32,
229    flush: MZFlush,
230) -> MZResult {
231    let orig_in_len = next_in.len();
232    loop {
233        let status = {
234            let mut cursor = Cursor::new(&mut state.dict[..]);
235            cursor.set_position(state.dict_ofs as u64);
236            decompress(&mut state.decomp, *next_in, &mut cursor, decomp_flags)
237        };
238
239        let in_bytes = status.1;
240        let out_bytes = status.2;
241        let status = status.0;
242
243        state.last_status = status;
244
245        *next_in = &next_in[in_bytes..];
246        *total_in += in_bytes;
247
248        state.dict_avail = out_bytes;
249        *total_out += push_dict_out(state, next_out);
250
251        // The stream was corrupted, and decompression failed.
252        if (status as i32) < 0 {
253            return Err(MZError::Data);
254        }
255
256        // The decompressor has flushed all it's data and is waiting for more input, but
257        // there was no more input provided.
258        if (status == TINFLStatus::NeedsMoreInput) && orig_in_len == 0 {
259            return Err(MZError::Buf);
260        }
261
262        if flush == MZFlush::Finish {
263            if status == TINFLStatus::Done {
264                // There is not enough space in the output buffer to flush the remaining
265                // decompressed data in the internal buffer.
266                return if state.dict_avail != 0 {
267                    Err(MZError::Buf)
268                } else {
269                    Ok(MZStatus::StreamEnd)
270                };
271            // No more space in the output buffer, but we're not done.
272            } else if next_out.is_empty() {
273                return Err(MZError::Buf);
274            }
275        } else {
276            // We're not expected to finish, so it's fine if we can't flush everything yet.
277            let empty_buf = next_in.is_empty() || next_out.is_empty();
278            if (status == TINFLStatus::Done) || empty_buf || (state.dict_avail != 0) {
279                return if (status == TINFLStatus::Done) && (state.dict_avail == 0) {
280                    // No more data left, we're done.
281                    Ok(MZStatus::StreamEnd)
282                } else {
283                    // Ok for now, still waiting for more input data or output space.
284                    Ok(MZStatus::Ok)
285                };
286            }
287        }
288    }
289}
290
291fn push_dict_out(state: &mut InflateState, next_out: &mut &mut [u8]) -> usize {
292    let n = cmp::min(state.dict_avail as usize, next_out.len());
293    (next_out[..n]).copy_from_slice(&state.dict[state.dict_ofs..state.dict_ofs + n]);
294    *next_out = &mut mem::replace(next_out, &mut [])[n..];
295    state.dict_avail -= n;
296    state.dict_ofs = (state.dict_ofs + (n)) & (TINFL_LZ_DICT_SIZE - 1);
297    n
298}
299
300#[cfg(test)]
301mod test {
302    use super::{inflate, InflateState};
303    use crate::{DataFormat, MZFlush, MZStatus};
304    #[test]
305    fn test_state() {
306        let encoded = [
307            120u8, 156, 243, 72, 205, 201, 201, 215, 81, 168, 202, 201, 76, 82, 4, 0, 27, 101, 4,
308            19,
309        ];
310        let mut out = vec![0; 50];
311        let mut state = InflateState::new_boxed(DataFormat::Zlib);
312        let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish);
313        let status = res.status.expect("Failed to decompress!");
314        assert_eq!(status, MZStatus::StreamEnd);
315        assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
316        assert_eq!(res.bytes_consumed, encoded.len());
317
318        state.reset(DataFormat::Zlib);
319        let status = res.status.expect("Failed to decompress!");
320        assert_eq!(status, MZStatus::StreamEnd);
321        assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
322        assert_eq!(res.bytes_consumed, encoded.len());
323    }
324}