1 | //! This module contains functionality for compression. |
2 | |
3 | use crate::alloc::vec; |
4 | use crate::alloc::vec::Vec; |
5 | |
6 | mod buffer; |
7 | pub mod core; |
8 | pub mod stream; |
9 | use self::core::*; |
10 | |
11 | /// How much processing the compressor should do to compress the data. |
12 | /// `NoCompression` and `Bestspeed` have special meanings, the other levels determine the number |
13 | /// of checks for matches in the hash chains and whether to use lazy or greedy parsing. |
14 | #[repr (i32)] |
15 | #[derive (Debug, Copy, Clone, PartialEq, Eq, Hash)] |
16 | pub enum CompressionLevel { |
17 | /// Don't do any compression, only output uncompressed blocks. |
18 | NoCompression = 0, |
19 | /// Fast compression. Uses a special compression routine that is optimized for speed. |
20 | BestSpeed = 1, |
21 | /// Slow/high compression. Do a lot of checks to try to find good matches. |
22 | BestCompression = 9, |
23 | /// Even more checks, can be very slow. |
24 | UberCompression = 10, |
25 | /// Default compromise between speed and compression. |
26 | DefaultLevel = 6, |
27 | /// Use the default compression level. |
28 | DefaultCompression = -1, |
29 | } |
30 | |
31 | // Missing safe rust analogue (this and mem-to-mem are quite similar) |
32 | /* |
33 | fn tdefl_compress( |
34 | d: Option<&mut CompressorOxide>, |
35 | in_buf: *const c_void, |
36 | in_size: Option<&mut usize>, |
37 | out_buf: *mut c_void, |
38 | out_size: Option<&mut usize>, |
39 | flush: TDEFLFlush, |
40 | ) -> TDEFLStatus { |
41 | let res = match d { |
42 | None => { |
43 | in_size.map(|size| *size = 0); |
44 | out_size.map(|size| *size = 0); |
45 | (TDEFLStatus::BadParam, 0, 0) |
46 | }, |
47 | Some(compressor) => { |
48 | let callback_res = CallbackOxide::new( |
49 | compressor.callback_func.clone(), |
50 | in_buf, |
51 | in_size, |
52 | out_buf, |
53 | out_size, |
54 | ); |
55 | |
56 | if let Ok(mut callback) = callback_res { |
57 | let res = compress(compressor, &mut callback, flush); |
58 | callback.update_size(Some(res.1), Some(res.2)); |
59 | res |
60 | } else { |
61 | (TDEFLStatus::BadParam, 0, 0) |
62 | } |
63 | } |
64 | }; |
65 | res.0 |
66 | }*/ |
67 | |
68 | // Missing safe rust analogue |
69 | /* |
70 | fn tdefl_init( |
71 | d: Option<&mut CompressorOxide>, |
72 | put_buf_func: PutBufFuncPtr, |
73 | put_buf_user: *mut c_void, |
74 | flags: c_int, |
75 | ) -> TDEFLStatus { |
76 | if let Some(d) = d { |
77 | *d = CompressorOxide::new( |
78 | put_buf_func.map(|func| |
79 | CallbackFunc { put_buf_func: func, put_buf_user: put_buf_user } |
80 | ), |
81 | flags as u32, |
82 | ); |
83 | TDEFLStatus::Okay |
84 | } else { |
85 | TDEFLStatus::BadParam |
86 | } |
87 | }*/ |
88 | |
89 | // Missing safe rust analogue (though maybe best served by flate2 front-end instead) |
90 | /* |
91 | fn tdefl_compress_mem_to_output( |
92 | buf: *const c_void, |
93 | buf_len: usize, |
94 | put_buf_func: PutBufFuncPtr, |
95 | put_buf_user: *mut c_void, |
96 | flags: c_int, |
97 | ) -> bool*/ |
98 | |
99 | // Missing safe Rust analogue |
100 | /* |
101 | fn tdefl_compress_mem_to_mem( |
102 | out_buf: *mut c_void, |
103 | out_buf_len: usize, |
104 | src_buf: *const c_void, |
105 | src_buf_len: usize, |
106 | flags: c_int, |
107 | ) -> usize*/ |
108 | |
109 | /// Compress the input data to a vector, using the specified compression level (0-10). |
110 | pub fn compress_to_vec(input: &[u8], level: u8) -> Vec<u8> { |
111 | compress_to_vec_inner(input, level, window_bits:0, strategy:0) |
112 | } |
113 | |
114 | /// Compress the input data to a vector, using the specified compression level (0-10), and with a |
115 | /// zlib wrapper. |
116 | pub fn compress_to_vec_zlib(input: &[u8], level: u8) -> Vec<u8> { |
117 | compress_to_vec_inner(input, level, window_bits:1, strategy:0) |
118 | } |
119 | |
120 | /// Simple function to compress data to a vec. |
121 | fn compress_to_vec_inner(mut input: &[u8], level: u8, window_bits: i32, strategy: i32) -> Vec<u8> { |
122 | // The comp flags function sets the zlib flag if the window_bits parameter is > 0. |
123 | let flags = create_comp_flags_from_zip_params(level.into(), window_bits, strategy); |
124 | let mut compressor = CompressorOxide::new(flags); |
125 | let mut output = vec![0; ::core::cmp::max(input.len() / 2, 2)]; |
126 | |
127 | let mut out_pos = 0; |
128 | loop { |
129 | let (status, bytes_in, bytes_out) = compress( |
130 | &mut compressor, |
131 | input, |
132 | &mut output[out_pos..], |
133 | TDEFLFlush::Finish, |
134 | ); |
135 | out_pos += bytes_out; |
136 | |
137 | match status { |
138 | TDEFLStatus::Done => { |
139 | output.truncate(out_pos); |
140 | break; |
141 | } |
142 | TDEFLStatus::Okay if bytes_in <= input.len() => { |
143 | input = &input[bytes_in..]; |
144 | |
145 | // We need more space, so resize the vector. |
146 | if output.len().saturating_sub(out_pos) < 30 { |
147 | output.resize(output.len() * 2, 0) |
148 | } |
149 | } |
150 | // Not supposed to happen unless there is a bug. |
151 | _ => panic!("Bug! Unexpectedly failed to compress!" ), |
152 | } |
153 | } |
154 | |
155 | output |
156 | } |
157 | |
158 | #[cfg (test)] |
159 | mod test { |
160 | use super::{compress_to_vec, compress_to_vec_inner, CompressionStrategy}; |
161 | use crate::inflate::decompress_to_vec; |
162 | use alloc::vec; |
163 | |
164 | /// Test deflate example. |
165 | /// |
166 | /// Check if the encoder produces the same code as the example given by Mark Adler here: |
167 | /// https://stackoverflow.com/questions/17398931/deflate-encoding-with-static-huffman-codes/17415203 |
168 | #[test ] |
169 | fn compress_small() { |
170 | let test_data = b"Deflate late" ; |
171 | let check = [ |
172 | 0x73, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49, 0x55, 0x00, 0x11, 0x00, |
173 | ]; |
174 | |
175 | let res = compress_to_vec(test_data, 1); |
176 | assert_eq!(&check[..], res.as_slice()); |
177 | |
178 | let res = compress_to_vec(test_data, 9); |
179 | assert_eq!(&check[..], res.as_slice()); |
180 | } |
181 | |
182 | #[test ] |
183 | fn compress_huff_only() { |
184 | let test_data = b"Deflate late" ; |
185 | |
186 | let res = compress_to_vec_inner(test_data, 1, 0, CompressionStrategy::HuffmanOnly as i32); |
187 | let d = decompress_to_vec(res.as_slice()).expect("Failed to decompress!" ); |
188 | assert_eq!(test_data, d.as_slice()); |
189 | } |
190 | |
191 | /// Test that a raw block compresses fine. |
192 | #[test ] |
193 | fn compress_raw() { |
194 | let text = b"Hello, zlib!" ; |
195 | let encoded = { |
196 | let len = text.len(); |
197 | let notlen = !len; |
198 | let mut encoded = vec![ |
199 | 1, |
200 | len as u8, |
201 | (len >> 8) as u8, |
202 | notlen as u8, |
203 | (notlen >> 8) as u8, |
204 | ]; |
205 | encoded.extend_from_slice(&text[..]); |
206 | encoded |
207 | }; |
208 | |
209 | let res = compress_to_vec(text, 0); |
210 | assert_eq!(encoded, res.as_slice()); |
211 | } |
212 | |
213 | #[test ] |
214 | fn short() { |
215 | let test_data = [10, 10, 10, 10, 10, 55]; |
216 | let c = compress_to_vec(&test_data, 9); |
217 | |
218 | let d = decompress_to_vec(c.as_slice()).expect("Failed to decompress!" ); |
219 | assert_eq!(&test_data, d.as_slice()); |
220 | // Check that a static block is used here, rather than a raw block |
221 | // , so the data is actually compressed. |
222 | // (The optimal compressed length would be 5, but neither miniz nor zlib manages that either |
223 | // as neither checks matches against the byte at index 0.) |
224 | assert!(c.len() <= 6); |
225 | } |
226 | } |
227 | |