Skip to content

Commit 02a8857

Browse files
committed
fix(deflate) set min window bits in inflate header when using rle
1 parent c179b18 commit 02a8857

File tree

2 files changed

+77
-7
lines changed

2 files changed

+77
-7
lines changed

miniz_oxide/src/deflate/core.rs

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ pub enum CompressionStrategy {
206206
Fixed = 4,
207207
}
208208

209+
impl From<CompressionStrategy> for i32 {
210+
#[inline(always)]
211+
fn from(value: CompressionStrategy) -> Self {
212+
value as i32
213+
}
214+
}
215+
209216
/// A list of deflate flush types.
210217
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
211218
pub enum TDEFLFlush {
@@ -299,10 +306,15 @@ pub(crate) const MAX_MATCH_LEN: usize = 258;
299306
const DEFAULT_FLAGS: u32 = NUM_PROBES[4] | TDEFL_WRITE_ZLIB_HEADER;
300307

301308
mod zlib {
309+
use super::TDEFL_RLE_MATCHES;
310+
302311
const DEFAULT_CM: u8 = 8;
303312
const DEFAULT_CINFO: u8 = 7 << 4;
304313
const _DEFAULT_FDICT: u8 = 0;
305314
const DEFAULT_CMF: u8 = DEFAULT_CM | DEFAULT_CINFO;
315+
// CMF used for RLE (technically it uses a window size of 0 but the lowest that can
316+
// be specified in the header corresponds to a window size of 1 << (0 + 8) aka 256.
317+
const MIN_CMF: u8 = DEFAULT_CM | 0;
306318
/// The 16-bit value consisting of CMF and FLG must be divisible by this to be valid.
307319
const FCHECK_DIVISOR: u8 = 31;
308320

@@ -324,7 +336,9 @@ mod zlib {
324336
use super::NUM_PROBES;
325337

326338
let num_probes = flags & (super::MAX_PROBES_MASK as u32);
327-
if flags & super::TDEFL_GREEDY_PARSING_FLAG != 0 {
339+
if (flags & super::TDEFL_GREEDY_PARSING_FLAG != 0)
340+
|| (flags & super::TDEFL_RLE_MATCHES != 0)
341+
{
328342
if num_probes <= 1 {
329343
0
330344
} else {
@@ -337,18 +351,26 @@ mod zlib {
337351
}
338352
}
339353

354+
const fn cmf_from_flags(flags: u32) -> u8 {
355+
if flags & TDEFL_RLE_MATCHES == 0 {
356+
DEFAULT_CMF
357+
} else {
358+
MIN_CMF
359+
}
360+
}
361+
340362
/// Get the zlib header for the level using the default window size and no
341363
/// dictionary.
342-
fn header_from_level(level: u8) -> [u8; 2] {
343-
let cmf = DEFAULT_CMF;
364+
fn header_from_level(level: u8, flags: u32) -> [u8; 2] {
365+
let cmf = cmf_from_flags(flags);
344366
[cmf, add_fcheck(cmf, level << 6)]
345367
}
346368

347369
/// Create a zlib header from the given compression flags.
348370
/// Only level is considered.
349371
pub fn header_from_flags(flags: u32) -> [u8; 2] {
350372
let level = zlib_level_from_flags(flags);
351-
header_from_level(level)
373+
header_from_level(level, flags)
352374
}
353375

354376
#[cfg(test)]
@@ -381,7 +403,7 @@ mod zlib {
381403

382404
#[test]
383405
fn test_header() {
384-
let header = super::header_from_level(3);
406+
let header = super::header_from_level(3, 0);
385407
assert_eq!(
386408
((usize::from(header[0]) * 256) + usize::from(header[1])) % 31,
387409
0
@@ -776,7 +798,7 @@ const HUFF_CODES_TABLE: usize = 2;
776798
/// Status of RLE encoding of huffman code lengths.
777799
struct Rle {
778800
pub z_count: u32,
779-
pub repeat_count: u32,
801+
pub repeat_count: u16,
780802
pub prev_code_size: u8,
781803
}
782804

@@ -792,7 +814,7 @@ impl Rle {
792814
if self.repeat_count != 0 {
793815
if self.repeat_count < 3 {
794816
counts[self.prev_code_size as usize] =
795-
counts[self.prev_code_size as usize].wrapping_add(self.repeat_count as u16);
817+
counts[self.prev_code_size as usize].wrapping_add(self.repeat_count);
796818
let code = self.prev_code_size;
797819
write(&[code, code, code][..self.repeat_count as usize])?;
798820
} else {
@@ -2466,4 +2488,45 @@ mod test {
24662488
let decoded = decompress_to_vec(&encoded[..]).unwrap();
24672489
assert_eq!(&decoded[..], &slice[..]);
24682490
}
2491+
2492+
#[test]
2493+
fn zlib_window_bits() {
2494+
use crate::inflate::stream::{inflate, InflateState};
2495+
use crate::DataFormat;
2496+
use alloc::boxed::Box;
2497+
let slice = [
2498+
1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 6, 1, 2, 3, 1, 2, 3, 2, 3, 1, 2, 3, 35, 22, 22, 2,
2499+
6, 2, 6,
2500+
];
2501+
let mut encoded = vec![];
2502+
let flags = create_comp_flags_from_zip_params(2, 1, CompressionStrategy::RLE.into());
2503+
let mut d = CompressorOxide::new(flags);
2504+
let (status, in_consumed) =
2505+
compress_to_output(&mut d, &slice, TDEFLFlush::Finish, |out: &[u8]| {
2506+
encoded.extend_from_slice(out);
2507+
true
2508+
});
2509+
2510+
assert_eq!(status, TDEFLStatus::Done);
2511+
assert_eq!(in_consumed, slice.len());
2512+
2513+
let mut output = vec![0; slice.len()];
2514+
2515+
let mut decompressor = Box::new(InflateState::new(DataFormat::Zlib));
2516+
2517+
let mut out_slice = output.as_mut_slice();
2518+
// Feed 1 byte at a time and no back buffer to test that RLE encoding has been used.
2519+
for i in 0..encoded.len() {
2520+
let result = inflate(
2521+
&mut decompressor,
2522+
&encoded[i..i + 1],
2523+
out_slice,
2524+
crate::MZFlush::None,
2525+
);
2526+
out_slice = &mut out_slice[result.bytes_written..];
2527+
}
2528+
let cmf = decompressor.decompressor().zlib_header().0;
2529+
assert_eq!(cmf, 8);
2530+
assert_eq!(output, slice)
2531+
}
24692532
}

miniz_oxide/src/inflate/core.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ impl DecompressorOxide {
238238
None
239239
}
240240
}
241+
242+
// Get zlib header for tests
243+
// Only for tests for now, may provide a proper function for this for later.
244+
#[cfg(all(test, feature = "with-alloc"))]
245+
pub(crate) const fn zlib_header(&self) -> (u32, u32) {
246+
(self.z_header0, self.z_header1)
247+
}
241248
}
242249

243250
impl Default for DecompressorOxide {

0 commit comments

Comments
 (0)