1#![doc = include_str!("../README.md")]
2#![no_std]
3#![forbid(unsafe_code)]
4
5use core::fmt;
6
7use miniz_oxide::inflate::core::{decompress, inflate_flags, DecompressorOxide};
8use miniz_oxide::inflate::TINFLStatus;
9
10pub const HISTORY_SIZE: usize = 32 * 1024;
12
13const FIXED_HEADER_SIZE: usize = 10;
14const TRAILER_SIZE: usize = 8;
15
16pub struct DecoderBuffers<'a> {
18 pub history: &'a mut [u8],
20}
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct MemberHeader {
25 pub modification_time: u32,
26 pub extra_flags: u8,
27 pub operating_system: u8,
28}
29
30#[derive(Debug, Eq, PartialEq)]
32pub enum DecodeStep<'a> {
33 NeedInput {
34 consumed: usize,
35 },
36 MemberStarted {
37 consumed: usize,
38 header: MemberHeader,
39 },
40 Output {
41 consumed: usize,
42 bytes: &'a [u8],
43 },
44 MemberFinished {
45 consumed: usize,
46 },
47}
48
49impl DecodeStep<'_> {
50 pub const fn consumed(&self) -> usize {
51 match self {
52 Self::NeedInput { consumed }
53 | Self::MemberStarted { consumed, .. }
54 | Self::Output { consumed, .. }
55 | Self::MemberFinished { consumed } => *consumed,
56 }
57 }
58}
59
60enum InternalStep {
61 NeedInput {
62 consumed: usize,
63 },
64 MemberStarted {
65 consumed: usize,
66 header: MemberHeader,
67 },
68 Output {
69 consumed: usize,
70 start: usize,
71 end: usize,
72 },
73 MemberFinished {
74 consumed: usize,
75 },
76}
77
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub enum DecodeError {
81 InvalidHistorySize { actual: usize },
82 InvalidHeader,
83 UnsupportedCompressionMethod { method: u8 },
84 InvalidHeaderChecksum,
85 InvalidDeflateStream,
86 InvalidDataChecksum { expected: u32, actual: u32 },
87 InvalidDataSize { expected: u32, actual: u32 },
88 UnexpectedEof,
89 DecoderPoisoned,
90}
91
92impl fmt::Display for DecodeError {
93 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 Self::InvalidHistorySize { actual } => write!(
96 formatter,
97 "gzip history must contain {HISTORY_SIZE} bytes, got {actual}"
98 ),
99 Self::InvalidHeader => formatter.write_str("invalid gzip header"),
100 Self::UnsupportedCompressionMethod { method } => {
101 write!(formatter, "unsupported gzip compression method {method}")
102 }
103 Self::InvalidHeaderChecksum => formatter.write_str("gzip header checksum mismatch"),
104 Self::InvalidDeflateStream => formatter.write_str("invalid DEFLATE stream"),
105 Self::InvalidDataChecksum { expected, actual } => write!(
106 formatter,
107 "gzip data checksum mismatch: expected {expected:08x}, got {actual:08x}"
108 ),
109 Self::InvalidDataSize { expected, actual } => write!(
110 formatter,
111 "gzip data size mismatch: expected {expected}, got {actual}"
112 ),
113 Self::UnexpectedEof => formatter.write_str("unexpected end of gzip stream"),
114 Self::DecoderPoisoned => formatter.write_str("gzip decoder is poisoned"),
115 }
116 }
117}
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120enum State {
121 Header,
122 ExtraLength,
123 Extra,
124 Name,
125 Comment,
126 HeaderChecksum,
127 StartDeflate,
128 Deflate,
129 Trailer,
130}
131
132pub struct Decoder<'a> {
134 history: &'a mut [u8],
135 decompressor: DecompressorOxide,
136 state: State,
137 poisoned: bool,
138 completed_members: u64,
139 fixed: [u8; FIXED_HEADER_SIZE],
140 fixed_len: usize,
141 small: [u8; TRAILER_SIZE],
142 small_len: usize,
143 flags: u8,
144 extra_remaining: usize,
145 header_crc: u32,
146 data_crc: u32,
147 data_size: u32,
148 output_position: usize,
149 header: MemberHeader,
150}
151
152impl<'a> Decoder<'a> {
153 pub fn new(buffers: DecoderBuffers<'a>) -> Result<Self, DecodeError> {
154 if buffers.history.len() != HISTORY_SIZE {
155 return Err(DecodeError::InvalidHistorySize {
156 actual: buffers.history.len(),
157 });
158 }
159 buffers.history.fill(0);
160 Ok(Self {
161 history: buffers.history,
162 decompressor: DecompressorOxide::new(),
163 state: State::Header,
164 poisoned: false,
165 completed_members: 0,
166 fixed: [0; FIXED_HEADER_SIZE],
167 fixed_len: 0,
168 small: [0; TRAILER_SIZE],
169 small_len: 0,
170 flags: 0,
171 extra_remaining: 0,
172 header_crc: !0,
173 data_crc: !0,
174 data_size: 0,
175 output_position: 0,
176 header: MemberHeader {
177 modification_time: 0,
178 extra_flags: 0,
179 operating_system: 0,
180 },
181 })
182 }
183
184 pub fn decode<'decoder>(
185 &'decoder mut self,
186 input: &[u8],
187 ) -> Result<DecodeStep<'decoder>, DecodeError> {
188 if self.poisoned {
189 return Err(DecodeError::DecoderPoisoned);
190 }
191 let step = match self.decode_inner(input) {
192 Ok(step) => step,
193 Err(error) => {
194 self.poisoned = true;
195 return Err(error);
196 }
197 };
198 Ok(match step {
199 InternalStep::NeedInput { consumed } => DecodeStep::NeedInput { consumed },
200 InternalStep::MemberStarted { consumed, header } => {
201 DecodeStep::MemberStarted { consumed, header }
202 }
203 InternalStep::Output {
204 consumed,
205 start,
206 end,
207 } => DecodeStep::Output {
208 consumed,
209 bytes: &self.history[start..end],
210 },
211 InternalStep::MemberFinished { consumed } => DecodeStep::MemberFinished { consumed },
212 })
213 }
214
215 pub fn finish(&self) -> Result<(), DecodeError> {
216 if self.poisoned {
217 return Err(DecodeError::DecoderPoisoned);
218 }
219 if self.completed_members != 0
220 && self.state == State::Header
221 && self.fixed_len == 0
222 && self.small_len == 0
223 {
224 Ok(())
225 } else {
226 Err(DecodeError::UnexpectedEof)
227 }
228 }
229
230 pub fn reset(&mut self) {
231 self.decompressor.init();
232 self.history.fill(0);
233 self.state = State::Header;
234 self.poisoned = false;
235 self.completed_members = 0;
236 self.fixed_len = 0;
237 self.small_len = 0;
238 self.flags = 0;
239 self.extra_remaining = 0;
240 self.header_crc = !0;
241 self.data_crc = !0;
242 self.data_size = 0;
243 self.output_position = 0;
244 }
245
246 fn decode_inner(&mut self, input: &[u8]) -> Result<InternalStep, DecodeError> {
247 let mut position = 0;
248 loop {
249 match self.state {
250 State::Header => {
251 let copied =
252 copy_into(&mut self.fixed, &mut self.fixed_len, &input[position..]);
253 self.header_crc =
254 crc32_update(self.header_crc, &input[position..position + copied]);
255 position += copied;
256 if self.fixed_len != FIXED_HEADER_SIZE {
257 return Ok(InternalStep::NeedInput { consumed: position });
258 }
259 if self.fixed[0..2] != [0x1f, 0x8b] || self.fixed[3] & 0xe0 != 0 {
260 return Err(DecodeError::InvalidHeader);
261 }
262 if self.fixed[2] != 8 {
263 return Err(DecodeError::UnsupportedCompressionMethod {
264 method: self.fixed[2],
265 });
266 }
267 self.flags = self.fixed[3];
268 self.header = MemberHeader {
269 modification_time: u32::from_le_bytes([
270 self.fixed[4],
271 self.fixed[5],
272 self.fixed[6],
273 self.fixed[7],
274 ]),
275 extra_flags: self.fixed[8],
276 operating_system: self.fixed[9],
277 };
278 self.fixed_len = 0;
279 self.small_len = 0;
280 self.state = self.next_optional_state();
281 }
282 State::ExtraLength => {
283 let copied = copy_into(
284 &mut self.small[..2],
285 &mut self.small_len,
286 &input[position..],
287 );
288 self.header_crc =
289 crc32_update(self.header_crc, &input[position..position + copied]);
290 position += copied;
291 if self.small_len != 2 {
292 return Ok(InternalStep::NeedInput { consumed: position });
293 }
294 self.extra_remaining =
295 u16::from_le_bytes([self.small[0], self.small[1]]) as usize;
296 self.small_len = 0;
297 self.state = if self.extra_remaining == 0 {
298 self.after_extra()
299 } else {
300 State::Extra
301 };
302 }
303 State::Extra => {
304 let length = self.extra_remaining.min(input.len() - position);
305 self.header_crc =
306 crc32_update(self.header_crc, &input[position..position + length]);
307 position += length;
308 self.extra_remaining -= length;
309 if self.extra_remaining != 0 {
310 return Ok(InternalStep::NeedInput { consumed: position });
311 }
312 self.state = self.after_extra();
313 }
314 State::Name | State::Comment => {
315 let remaining = &input[position..];
316 let Some(end) = remaining.iter().position(|byte| *byte == 0) else {
317 self.header_crc = crc32_update(self.header_crc, remaining);
318 return Ok(InternalStep::NeedInput {
319 consumed: input.len(),
320 });
321 };
322 let length = end + 1;
323 self.header_crc = crc32_update(self.header_crc, &remaining[..length]);
324 position += length;
325 self.state = if self.state == State::Name {
326 self.after_name()
327 } else {
328 self.after_comment()
329 };
330 }
331 State::HeaderChecksum => {
332 let copied = copy_into(
333 &mut self.small[..2],
334 &mut self.small_len,
335 &input[position..],
336 );
337 position += copied;
338 if self.small_len != 2 {
339 return Ok(InternalStep::NeedInput { consumed: position });
340 }
341 let expected = u16::from_le_bytes([self.small[0], self.small[1]]);
342 let actual = (!self.header_crc) as u16;
343 if expected != actual {
344 return Err(DecodeError::InvalidHeaderChecksum);
345 }
346 self.small_len = 0;
347 self.start_deflate();
348 return Ok(InternalStep::MemberStarted {
349 consumed: position,
350 header: self.header,
351 });
352 }
353 State::StartDeflate => {
354 self.start_deflate();
355 return Ok(InternalStep::MemberStarted {
356 consumed: position,
357 header: self.header,
358 });
359 }
360 State::Deflate => {
361 let start = self.output_position;
362 let (status, consumed, written) = decompress(
363 &mut self.decompressor,
364 &input[position..],
365 self.history,
366 start,
367 inflate_flags::TINFL_FLAG_HAS_MORE_INPUT,
368 );
369 position += consumed;
370 let end = start + written;
371 debug_assert!(end <= self.history.len());
375 self.data_crc = crc32_update(self.data_crc, &self.history[start..end]);
376 self.data_size = self.data_size.wrapping_add(written as u32);
377 self.output_position = if end == self.history.len() { 0 } else { end };
378 match status {
379 TINFLStatus::Done => self.state = State::Trailer,
380 TINFLStatus::HasMoreOutput | TINFLStatus::NeedsMoreInput => {}
381 _ => return Err(DecodeError::InvalidDeflateStream),
382 }
383 if written != 0 {
384 return Ok(InternalStep::Output {
385 consumed: position,
386 start,
387 end,
388 });
389 }
390 if needs_input_without_output(status)? {
391 return Ok(InternalStep::NeedInput { consumed: position });
392 }
393 }
394 State::Trailer => {
395 let copied =
396 copy_into(&mut self.small, &mut self.small_len, &input[position..]);
397 position += copied;
398 if trailer_incomplete(self.small_len) {
399 return Ok(InternalStep::NeedInput { consumed: position });
400 }
401 validate_trailer(&self.small, !self.data_crc, self.data_size)?;
402 self.completed_members += 1;
403 self.small_len = 0;
404 self.state = State::Header;
405 self.header_crc = !0;
406 return Ok(InternalStep::MemberFinished { consumed: position });
407 }
408 }
409
410 if position == input.len() {
411 return Ok(InternalStep::NeedInput { consumed: position });
412 }
413 }
414 }
415
416 fn next_optional_state(&self) -> State {
417 if self.flags & 0x04 != 0 {
418 State::ExtraLength
419 } else {
420 self.after_extra()
421 }
422 }
423
424 fn after_extra(&self) -> State {
425 if self.flags & 0x08 != 0 {
426 State::Name
427 } else {
428 self.after_name()
429 }
430 }
431
432 fn after_name(&self) -> State {
433 if self.flags & 0x10 != 0 {
434 State::Comment
435 } else {
436 self.after_comment()
437 }
438 }
439
440 fn after_comment(&self) -> State {
441 if self.flags & 0x02 != 0 {
442 State::HeaderChecksum
443 } else {
444 State::StartDeflate
445 }
446 }
447
448 fn start_deflate(&mut self) {
449 self.decompressor.init();
450 self.history.fill(0);
459 self.data_crc = !0;
460 self.data_size = 0;
461 self.output_position = 0;
462 self.state = State::Deflate;
463 }
464}
465
466fn needs_input_without_output(status: TINFLStatus) -> Result<bool, DecodeError> {
467 match status {
468 TINFLStatus::Done => Ok(false),
469 TINFLStatus::NeedsMoreInput => Ok(true),
470 TINFLStatus::HasMoreOutput => Err(DecodeError::InvalidDeflateStream),
473 _ => unreachable!(),
474 }
475}
476
477fn trailer_incomplete(length: usize) -> bool {
478 length != TRAILER_SIZE
479}
480
481fn validate_trailer(
482 trailer: &[u8; TRAILER_SIZE],
483 actual_crc: u32,
484 actual_size: u32,
485) -> Result<(), DecodeError> {
486 let expected_crc = u32::from_le_bytes([trailer[0], trailer[1], trailer[2], trailer[3]]);
487 if expected_crc != actual_crc {
488 return Err(DecodeError::InvalidDataChecksum {
489 expected: expected_crc,
490 actual: actual_crc,
491 });
492 }
493 let expected_size = u32::from_le_bytes([trailer[4], trailer[5], trailer[6], trailer[7]]);
494 if expected_size != actual_size {
495 return Err(DecodeError::InvalidDataSize {
496 expected: expected_size,
497 actual: actual_size,
498 });
499 }
500 Ok(())
501}
502
503fn copy_into(destination: &mut [u8], filled: &mut usize, input: &[u8]) -> usize {
504 let length = input.len().min(destination.len() - *filled);
505 destination[*filled..*filled + length].copy_from_slice(&input[..length]);
506 *filled += length;
507 length
508}
509
510fn crc32_update(mut crc: u32, bytes: &[u8]) -> u32 {
511 for byte in bytes {
512 crc = CRC32_TABLE[((crc ^ u32::from(*byte)) & 0xff) as usize] ^ (crc >> 8);
513 }
514 crc
515}
516
517const fn crc32_table() -> [u32; 256] {
518 let mut table = [0u32; 256];
519 let mut index = 0;
520 while index < table.len() {
521 let mut value = index as u32;
522 let mut bit = 0;
523 while bit < 8 {
524 value = if value & 1 == 0 {
525 value >> 1
526 } else {
527 0xedb8_8320 ^ (value >> 1)
528 };
529 bit += 1;
530 }
531 table[index] = value;
532 index += 1;
533 }
534 table
535}
536
537const CRC32_TABLE: [u32; 256] = crc32_table();
538
539#[cfg(test)]
540mod tests {
541 extern crate std;
542
543 use std::string::ToString;
544
545 use super::*;
546
547 fn new_decoder(history: &mut [u8; HISTORY_SIZE]) -> Decoder<'_> {
548 Decoder::new(DecoderBuffers { history }).unwrap()
549 }
550
551 #[test]
552 fn formats_decode_errors() {
553 assert_eq!(
554 DecodeError::InvalidDataChecksum {
555 expected: 0x1234,
556 actual: 0xabcd,
557 }
558 .to_string(),
559 "gzip data checksum mismatch: expected 00001234, got 0000abcd"
560 );
561 }
562
563 #[test]
564 fn finish_requires_a_completed_member_at_a_clean_header_boundary() {
565 let mut history = [0; HISTORY_SIZE];
566 let mut decoder = new_decoder(&mut history);
567
568 assert_eq!(decoder.finish(), Err(DecodeError::UnexpectedEof));
569
570 decoder.completed_members = 1;
571 assert_eq!(decoder.finish(), Ok(()));
572
573 decoder.state = State::Deflate;
574 assert_eq!(decoder.finish(), Err(DecodeError::UnexpectedEof));
575 decoder.state = State::Header;
576
577 decoder.fixed_len = 1;
578 assert_eq!(decoder.finish(), Err(DecodeError::UnexpectedEof));
579 decoder.fixed_len = 0;
580
581 decoder.small_len = 1;
582 assert_eq!(decoder.finish(), Err(DecodeError::UnexpectedEof));
583 }
584
585 #[test]
586 fn reset_restores_every_stream_state_field() {
587 let mut history = [0; HISTORY_SIZE];
588 let mut decoder = new_decoder(&mut history);
589 decoder.history.fill(0xff);
590 decoder.state = State::Trailer;
591 decoder.poisoned = true;
592 decoder.completed_members = 1;
593 decoder.fixed_len = 1;
594 decoder.small_len = 1;
595 decoder.flags = 0xff;
596 decoder.extra_remaining = 1;
597 decoder.header_crc = 0;
598 decoder.data_crc = 0;
599 decoder.data_size = 1;
600 decoder.output_position = 1;
601
602 decoder.reset();
603
604 assert!(decoder.history.iter().all(|byte| *byte == 0));
605 assert_eq!(decoder.state, State::Header);
606 assert!(!decoder.poisoned);
607 assert_eq!(decoder.completed_members, 0);
608 assert_eq!(decoder.fixed_len, 0);
609 assert_eq!(decoder.small_len, 0);
610 assert_eq!(decoder.flags, 0);
611 assert_eq!(decoder.extra_remaining, 0);
612 assert_eq!(decoder.header_crc, !0);
613 assert_eq!(decoder.data_crc, !0);
614 assert_eq!(decoder.data_size, 0);
615 assert_eq!(decoder.output_position, 0);
616 }
617
618 #[test]
619 fn rejects_bad_magic_and_reserved_flags_independently() {
620 for header in [
621 [0, 0, 8, 0, 0, 0, 0, 0, 0, 0],
622 [0x1f, 0x8b, 8, 0x20, 0, 0, 0, 0, 0, 0],
623 ] {
624 let mut history = [0; HISTORY_SIZE];
625 assert_eq!(
626 new_decoder(&mut history).decode(&header),
627 Err(DecodeError::InvalidHeader)
628 );
629 }
630 }
631
632 #[test]
633 fn recognizes_the_extra_field_flag_exactly() {
634 let mut history = [0; HISTORY_SIZE];
635 let mut decoder = new_decoder(&mut history);
636
637 decoder.flags = 0;
638 assert_eq!(decoder.next_optional_state(), State::StartDeflate);
639 decoder.flags = 0x04;
640 assert_eq!(decoder.next_optional_state(), State::ExtraLength);
641 }
642
643 #[test]
644 fn starts_deflate_from_clean_state() {
645 let mut history = [0; HISTORY_SIZE];
646 let mut decoder = new_decoder(&mut history);
647 decoder.history.fill(0xff);
648 decoder.data_crc = 0;
649 decoder.data_size = 1;
650 decoder.output_position = 1;
651
652 decoder.start_deflate();
653
654 assert!(decoder.history.iter().all(|byte| *byte == 0));
655 assert_eq!(decoder.data_crc, !0);
656 assert_eq!(decoder.data_size, 0);
657 assert_eq!(decoder.output_position, 0);
658 assert_eq!(decoder.state, State::Deflate);
659 }
660
661 #[test]
662 fn classifies_statuses_without_output() {
663 assert_eq!(needs_input_without_output(TINFLStatus::Done), Ok(false));
664 assert_eq!(
665 needs_input_without_output(TINFLStatus::NeedsMoreInput),
666 Ok(true)
667 );
668 assert_eq!(
669 needs_input_without_output(TINFLStatus::HasMoreOutput),
670 Err(DecodeError::InvalidDeflateStream)
671 );
672 }
673
674 #[test]
675 fn validates_trailer_length_checksum_and_size() {
676 assert!(trailer_incomplete(TRAILER_SIZE - 1));
677 assert!(!trailer_incomplete(TRAILER_SIZE));
678
679 let mut trailer = [0; TRAILER_SIZE];
680 trailer[..4].copy_from_slice(&0x1234_u32.to_le_bytes());
681 trailer[4..].copy_from_slice(&56_u32.to_le_bytes());
682 assert_eq!(validate_trailer(&trailer, 0x1234, 56), Ok(()));
683 assert_eq!(
684 validate_trailer(&trailer, 0xabcd, 56),
685 Err(DecodeError::InvalidDataChecksum {
686 expected: 0x1234,
687 actual: 0xabcd,
688 })
689 );
690 assert_eq!(
691 validate_trailer(&trailer, 0x1234, 78),
692 Err(DecodeError::InvalidDataSize {
693 expected: 56,
694 actual: 78,
695 })
696 );
697 }
698
699 #[test]
700 fn copies_into_the_unfilled_destination_suffix() {
701 let mut destination = [1, 0, 0];
702 let mut filled = 1;
703
704 assert_eq!(copy_into(&mut destination, &mut filled, &[2, 3, 4]), 2);
705 assert_eq!(destination, [1, 2, 3]);
706 assert_eq!(filled, 3);
707 }
708}