Skip to main content

gzip_zero/
lib.rs

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
10/// Required size of the caller-owned DEFLATE history ring.
11pub const HISTORY_SIZE: usize = 32 * 1024;
12
13const FIXED_HEADER_SIZE: usize = 10;
14const TRAILER_SIZE: usize = 8;
15
16/// Buffers borrowed by a [`Decoder`].
17pub struct DecoderBuffers<'a> {
18    /// The DEFLATE history ring. It must contain exactly [`HISTORY_SIZE`] bytes.
19    pub history: &'a mut [u8],
20}
21
22/// Fixed fields from a gzip member header.
23#[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/// Kind of progress made by one call to [`Decoder::decode`].
31#[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/// A malformed stream or invalid decoder configuration.
79#[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
132/// Allocation-free incremental gzip decoder.
133pub 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                    // miniz never wraps within a single call, so `history[start..end]`
372                    // is contiguous and in bounds. `output_position` below preserves
373                    // `start < len`, which also guarantees at least one free byte.
374                    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        // Load-bearing for cross-member isolation; do not remove as an optimization.
451        //
452        // `history` doubles as miniz's wrapping output window, so a malformed member
453        // whose DEFLATE stream back-references before its own output resolves that
454        // distance against whatever the ring still holds. miniz does not reject
455        // out-of-range distances in wrapping mode — it masks them — so without this
456        // zeroing the previous member's plaintext is emitted as this member's output.
457        // Covered by `tests/invariants.rs`.
458        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        // Unreachable while `output_position < history.len()`: a full output
471        // buffer always has non-empty output, handled before this function.
472        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}