]>
Commit | Line | Data |
---|---|---|
f035d41b XL |
1 | //! This module contains functionality for decompression. |
2 | ||
1b1a35ee | 3 | use ::core::cmp::min; |
3dfed10e | 4 | use ::core::usize; |
1b1a35ee XL |
5 | use crate::alloc::boxed::Box; |
6 | use crate::alloc::vec; | |
7 | use crate::alloc::vec::Vec; | |
f035d41b XL |
8 | |
9 | pub mod core; | |
10 | mod output_buffer; | |
11 | pub mod stream; | |
12 | use self::core::*; | |
13 | ||
14 | const TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS: i32 = -4; | |
15 | const TINFL_STATUS_BAD_PARAM: i32 = -3; | |
16 | const TINFL_STATUS_ADLER32_MISMATCH: i32 = -2; | |
17 | const TINFL_STATUS_FAILED: i32 = -1; | |
18 | const TINFL_STATUS_DONE: i32 = 0; | |
19 | const TINFL_STATUS_NEEDS_MORE_INPUT: i32 = 1; | |
20 | const TINFL_STATUS_HAS_MORE_OUTPUT: i32 = 2; | |
21 | ||
22 | /// Return status codes. | |
23 | #[repr(i8)] | |
24 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] | |
25 | pub enum TINFLStatus { | |
26 | /// More input data was expected, but the caller indicated that there was more data, so the | |
27 | /// input stream is likely truncated. | |
28 | FailedCannotMakeProgress = TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS as i8, | |
29 | /// One or more of the input parameters were invalid. | |
30 | BadParam = TINFL_STATUS_BAD_PARAM as i8, | |
31 | /// The decompression went fine, but the adler32 checksum did not match the one | |
32 | /// provided in the header. | |
33 | Adler32Mismatch = TINFL_STATUS_ADLER32_MISMATCH as i8, | |
34 | /// Failed to decompress due to invalid data. | |
35 | Failed = TINFL_STATUS_FAILED as i8, | |
36 | /// Finished decomression without issues. | |
37 | Done = TINFL_STATUS_DONE as i8, | |
38 | /// The decompressor needs more input data to continue decompressing. | |
39 | NeedsMoreInput = TINFL_STATUS_NEEDS_MORE_INPUT as i8, | |
40 | /// There is still pending data that didn't fit in the output buffer. | |
41 | HasMoreOutput = TINFL_STATUS_HAS_MORE_OUTPUT as i8, | |
42 | } | |
43 | ||
44 | impl TINFLStatus { | |
45 | pub fn from_i32(value: i32) -> Option<TINFLStatus> { | |
46 | use self::TINFLStatus::*; | |
47 | match value { | |
48 | TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS => Some(FailedCannotMakeProgress), | |
49 | TINFL_STATUS_BAD_PARAM => Some(BadParam), | |
50 | TINFL_STATUS_ADLER32_MISMATCH => Some(Adler32Mismatch), | |
51 | TINFL_STATUS_FAILED => Some(Failed), | |
52 | TINFL_STATUS_DONE => Some(Done), | |
53 | TINFL_STATUS_NEEDS_MORE_INPUT => Some(NeedsMoreInput), | |
54 | TINFL_STATUS_HAS_MORE_OUTPUT => Some(HasMoreOutput), | |
55 | _ => None, | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
60 | /// Decompress the deflate-encoded data in `input` to a vector. | |
61 | /// | |
62 | /// Returns a status and an integer representing where the decompressor failed on failure. | |
63 | #[inline] | |
64 | pub fn decompress_to_vec(input: &[u8]) -> Result<Vec<u8>, TINFLStatus> { | |
1b1a35ee | 65 | decompress_to_vec_inner(input, 0, usize::max_value()) |
f035d41b XL |
66 | } |
67 | ||
68 | /// Decompress the deflate-encoded data (with a zlib wrapper) in `input` to a vector. | |
69 | /// | |
70 | /// Returns a status and an integer representing where the decompressor failed on failure. | |
71 | #[inline] | |
72 | pub fn decompress_to_vec_zlib(input: &[u8]) -> Result<Vec<u8>, TINFLStatus> { | |
1b1a35ee XL |
73 | decompress_to_vec_inner( |
74 | input, | |
75 | inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER, | |
76 | usize::max_value(), | |
77 | ) | |
f035d41b XL |
78 | } |
79 | ||
1b1a35ee XL |
80 | /// Decompress the deflate-encoded data in `input` to a vector. |
81 | /// The vector is grown to at most `max_size` bytes; if the data does not fit in that size, | |
82 | /// `TINFLStatus::HasMoreOutput` error is returned. | |
83 | /// | |
84 | /// Returns a status and an integer representing where the decompressor failed on failure. | |
85 | #[inline] | |
86 | pub fn decompress_to_vec_with_limit(input: &[u8], max_size: usize) -> Result<Vec<u8>, TINFLStatus> { | |
87 | decompress_to_vec_inner(input, 0, max_size) | |
88 | } | |
89 | ||
90 | /// Decompress the deflate-encoded data (with a zlib wrapper) in `input` to a vector. | |
91 | /// The vector is grown to at most `max_size` bytes; if the data does not fit in that size, | |
92 | /// `TINFLStatus::HasMoreOutput` error is returned. | |
93 | /// | |
94 | /// Returns a status and an integer representing where the decompressor failed on failure. | |
95 | #[inline] | |
96 | pub fn decompress_to_vec_zlib_with_limit( | |
97 | input: &[u8], | |
98 | max_size: usize, | |
99 | ) -> Result<Vec<u8>, TINFLStatus> { | |
100 | decompress_to_vec_inner(input, inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER, max_size) | |
101 | } | |
102 | ||
103 | fn decompress_to_vec_inner( | |
104 | input: &[u8], | |
105 | flags: u32, | |
106 | max_output_size: usize, | |
107 | ) -> Result<Vec<u8>, TINFLStatus> { | |
f035d41b | 108 | let flags = flags | inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; |
1b1a35ee | 109 | let mut ret: Vec<u8> = vec![0; min(input.len().saturating_mul(2), max_output_size)]; |
f035d41b XL |
110 | |
111 | let mut decomp = Box::<DecompressorOxide>::default(); | |
112 | ||
113 | let mut in_pos = 0; | |
114 | let mut out_pos = 0; | |
115 | loop { | |
3dfed10e XL |
116 | // Wrap the whole output slice so we know we have enough of the |
117 | // decompressed data for matches. | |
118 | let (status, in_consumed, out_consumed) = | |
119 | decompress(&mut decomp, &input[in_pos..], &mut ret, out_pos, flags); | |
f035d41b XL |
120 | in_pos += in_consumed; |
121 | out_pos += out_consumed; | |
122 | ||
123 | match status { | |
124 | TINFLStatus::Done => { | |
125 | ret.truncate(out_pos); | |
126 | return Ok(ret); | |
127 | } | |
128 | ||
129 | TINFLStatus::HasMoreOutput => { | |
1b1a35ee XL |
130 | // We need more space, so check if we can resize the buffer and do it. |
131 | let new_len = ret | |
132 | .len() | |
133 | .checked_add(out_pos) | |
134 | .ok_or(TINFLStatus::HasMoreOutput)?; | |
135 | if new_len > max_output_size { | |
136 | return Err(TINFLStatus::HasMoreOutput); | |
137 | }; | |
138 | ret.resize(new_len, 0); | |
f035d41b XL |
139 | } |
140 | ||
141 | _ => return Err(status), | |
142 | } | |
143 | } | |
144 | } | |
145 | ||
146 | #[cfg(test)] | |
147 | mod test { | |
1b1a35ee XL |
148 | use super::TINFLStatus; |
149 | use super::{decompress_to_vec_zlib, decompress_to_vec_zlib_with_limit}; | |
150 | const encoded: [u8; 20] = [ | |
151 | 120, 156, 243, 72, 205, 201, 201, 215, 81, 168, 202, 201, 76, 82, 4, 0, 27, 101, 4, 19, | |
152 | ]; | |
f035d41b XL |
153 | |
154 | #[test] | |
155 | fn decompress_vec() { | |
f035d41b XL |
156 | let res = decompress_to_vec_zlib(&encoded[..]).unwrap(); |
157 | assert_eq!(res.as_slice(), &b"Hello, zlib!"[..]); | |
158 | } | |
1b1a35ee XL |
159 | |
160 | #[test] | |
161 | fn decompress_vec_with_high_limit() { | |
162 | let res = decompress_to_vec_zlib_with_limit(&encoded[..], 100_000).unwrap(); | |
163 | assert_eq!(res.as_slice(), &b"Hello, zlib!"[..]); | |
164 | } | |
165 | ||
166 | #[test] | |
167 | fn fail_to_decompress_with_limit() { | |
168 | let res = decompress_to_vec_zlib_with_limit(&encoded[..], 8); | |
169 | match res { | |
170 | Err(TINFLStatus::HasMoreOutput) => (), // expected result | |
171 | _ => panic!("Decompression output size limit was not enforced"), | |
172 | } | |
173 | } | |
f035d41b | 174 | } |