Skip to main content

rapidgzip_core/
analyze.rs

1//! Bounded structural analysis of DEFLATE streams.
2//!
3//! Analysis walks every block in causal order and records framing, Huffman,
4//! symbol, and predecessor-window facts. It shares the decoder's container
5//! parsers and native Huffman primitives, but intentionally remains
6//! single-threaded: a block's history depends on every preceding block in its
7//! stream.
8//!
9//! Memory does not scale with decompressed size. The walker keeps one bounded
10//! linear history/output buffer, the bounded result collections, and the
11//! caller-configured number of detailed back-reference records.
12
13use crate::backend::resolve_cursor_format;
14use crate::crc32::Crc32;
15use crate::gzip::{
16    DetailedMemberHeader, InputCursor, SourceCursor, StreamCursor, parse_member_header_detailed,
17};
18use crate::parallel::deflate::{
19    self, DISTANCE_BASE, DISTANCE_EXTRA, DeflateBits, END_OF_BLOCK, Huffman, LENGTH_BASE,
20    LENGTH_EXTRA, dynamic_trees_with_lengths, fixed_trees,
21};
22use crate::zlib::Adler32;
23use crate::{
24    AnalysisCounter, AnalysisErrorKind, AnalysisResource, DecodeError, DeflateErrorKind, Format,
25    GzipErrorKind, ReadAt, ZlibErrorKind,
26};
27use std::io::Read;
28
29const WINDOW_SIZE: usize = 32 * 1024;
30const CHECKSUM_BUFFER_SIZE: usize = 8 * 1024;
31const DEFAULT_MAXIMUM_STREAMS: usize = 100_000;
32const DEFAULT_MAXIMUM_BLOCKS: usize = 100_000;
33const DEFAULT_MAXIMUM_HEADER_BYTES: usize = 1024 * 1024;
34
35/// Limits controlling the memory retained by structural analysis.
36///
37/// Defaults accept 100,000 streams, 100,000 blocks, and 1 MiB of optional gzip
38/// metadata across the complete input. Individual back-reference records are omitted by
39/// default; exact counts, length histograms, reach, and window coverage are
40/// always collected regardless of that retention budget.
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42#[non_exhaustive]
43pub struct AnalyzeOptions {
44    maximum_streams: usize,
45    maximum_blocks: usize,
46    maximum_header_bytes: usize,
47    maximum_retained_backreferences: usize,
48}
49
50impl Default for AnalyzeOptions {
51    fn default() -> Self {
52        Self {
53            maximum_streams: DEFAULT_MAXIMUM_STREAMS,
54            maximum_blocks: DEFAULT_MAXIMUM_BLOCKS,
55            maximum_header_bytes: DEFAULT_MAXIMUM_HEADER_BYTES,
56            maximum_retained_backreferences: 0,
57        }
58    }
59}
60
61impl AnalyzeOptions {
62    /// Sets the maximum number of gzip members or other framed streams.
63    #[must_use]
64    pub const fn maximum_streams(mut self, maximum: usize) -> Self {
65        self.maximum_streams = maximum;
66        self
67    }
68
69    /// Sets the maximum number of DEFLATE blocks across the input.
70    #[must_use]
71    pub const fn maximum_blocks(mut self, maximum: usize) -> Self {
72        self.maximum_blocks = maximum;
73        self
74    }
75
76    /// Sets the input-wide maximum for retained optional gzip metadata.
77    ///
78    /// The budget covers the extra field, original name, and comment. Fixed
79    /// header fields do not consume it. Extra fields, names, and comments from
80    /// all members share this budget.
81    #[must_use]
82    pub const fn maximum_header_bytes(mut self, maximum: usize) -> Self {
83        self.maximum_header_bytes = maximum;
84        self
85    }
86
87    /// Sets the input-wide budget for detailed predecessor-window references.
88    ///
89    /// Summaries remain exact after this many records have been retained.
90    /// Each affected block reports how many of its records were omitted.
91    #[must_use]
92    pub const fn maximum_retained_backreferences(mut self, maximum: usize) -> Self {
93        self.maximum_retained_backreferences = maximum;
94        self
95    }
96
97    /// Returns the configured stream limit.
98    #[must_use]
99    pub const fn stream_limit(self) -> usize {
100        self.maximum_streams
101    }
102
103    /// Returns the configured block limit.
104    #[must_use]
105    pub const fn block_limit(self) -> usize {
106        self.maximum_blocks
107    }
108
109    /// Returns the input-wide optional gzip-metadata limit.
110    #[must_use]
111    pub const fn header_byte_limit(self) -> usize {
112        self.maximum_header_bytes
113    }
114
115    /// Returns the input-wide detailed-reference retention budget.
116    #[must_use]
117    pub const fn retained_backreference_limit(self) -> usize {
118        self.maximum_retained_backreferences
119    }
120}
121
122/// Encoding selected by one DEFLATE block.
123#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
124#[non_exhaustive]
125pub enum BlockType {
126    /// Stored bytes with no Huffman coding.
127    #[default]
128    Uncompressed,
129    /// RFC 1951's fixed literal/length and distance alphabets.
130    FixedHuffman,
131    /// Alphabets declared in the block header.
132    DynamicHuffman,
133}
134
135/// Shape of one alphabet declared by a dynamic-Huffman block.
136#[derive(Clone, Debug, Default, Eq, PartialEq)]
137#[non_exhaustive]
138pub struct AlphabetShape {
139    /// Code length for every symbol in alphabet order, including zero lengths.
140    pub code_lengths: Vec<u8>,
141    /// Number of code lengths physically declared by the header.
142    pub declared_count: usize,
143}
144
145impl AlphabetShape {
146    /// Returns the number of symbols with non-zero code lengths.
147    #[must_use]
148    pub fn used_count(&self) -> usize {
149        self.code_lengths
150            .iter()
151            .filter(|&&length| length != 0)
152            .count()
153    }
154
155    /// Returns the shortest and longest non-zero code lengths.
156    #[must_use]
157    pub fn length_range(&self) -> Option<(u8, u8)> {
158        let mut lengths = self
159            .code_lengths
160            .iter()
161            .copied()
162            .filter(|&length| length != 0);
163        let first = lengths.next()?;
164        Some(lengths.fold((first, first), |(minimum, maximum), length| {
165            (minimum.min(length), maximum.max(length))
166        }))
167    }
168
169    /// Returns symbol counts grouped by code length, shortest first.
170    #[must_use]
171    pub fn counts_by_length(&self) -> Vec<(u8, usize)> {
172        let mut counts = [0_usize; 16];
173        for &length in &self.code_lengths {
174            counts[usize::from(length).min(15)] += 1;
175        }
176        counts
177            .into_iter()
178            .enumerate()
179            .filter(|&(_, count)| count != 0)
180            .map(|(length, count)| (length as u8, count))
181            .collect()
182    }
183}
184
185/// One retained reference into the predecessor window.
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187#[non_exhaustive]
188pub struct Backreference {
189    /// Distance before the current block's first output byte.
190    pub distance: u16,
191    /// Reference length as reported by rapidgzip, capped at the copy distance.
192    pub length: u16,
193}
194
195/// Structural facts for one DEFLATE block.
196#[derive(Clone, Debug, Default, Eq, PartialEq)]
197#[non_exhaustive]
198pub struct BlockAnalysis {
199    /// Zero-based index of the containing stream.
200    pub stream_index: u64,
201    /// Zero-based index within the containing stream.
202    pub index_in_stream: u64,
203    /// Whether this is the stream's final block.
204    pub is_final: bool,
205    /// Block encoding.
206    pub block_type: BlockType,
207    /// Absolute compressed bit offset of the three-bit block header.
208    pub compressed_offset_in_bits: u64,
209    /// Absolute compressed bit offset after stored-length or Huffman metadata.
210    pub compressed_data_offset_in_bits: u64,
211    /// Absolute decompressed byte offset of the block's first output byte.
212    pub uncompressed_offset_in_bytes: u64,
213    /// Exact compressed block size, including its header.
214    pub compressed_size_in_bits: u64,
215    /// Exact number of output bytes produced by the block.
216    pub uncompressed_size_in_bytes: u64,
217    /// Dynamic precode alphabet.
218    pub precode: Option<AlphabetShape>,
219    /// Dynamic distance alphabet.
220    pub distance: Option<AlphabetShape>,
221    /// Dynamic literal/length alphabet.
222    pub literal: Option<AlphabetShape>,
223    /// Literal symbols decoded from the block.
224    pub literal_symbols: u64,
225    /// Length/distance symbols decoded from the block.
226    pub backreference_symbols: u64,
227    /// Output bytes copied by length/distance symbols.
228    pub copied_bytes: u64,
229    /// Farthest reach before this block's first output byte.
230    pub farthest_backreference: u64,
231    /// References whose source begins before this block's output.
232    pub window_backreference_count: u64,
233    /// Deterministic interval-union count for those references.
234    pub merged_window_backreference_count: u64,
235    /// Covered predecessor-window bytes for blocks producing at least 32 KiB.
236    pub used_window_symbols: Option<u64>,
237    /// Detailed references retained within [`AnalyzeOptions`]' global budget.
238    pub retained_backreferences: Vec<Backreference>,
239    /// Detailed references omitted after the global budget was exhausted.
240    pub omitted_backreference_count: u64,
241}
242
243/// Complete gzip header metadata for one member.
244#[derive(Clone, Debug, Default, Eq, PartialEq)]
245#[non_exhaustive]
246pub struct GzipHeaderFields {
247    /// RFC 1952 flag byte.
248    pub flags: u8,
249    /// Modification time, or zero when unspecified.
250    pub modification_time: u32,
251    /// Compressor hint byte (`XFL`).
252    pub extra_flags: u8,
253    /// Originating operating-system code.
254    pub operating_system: u8,
255    /// Original file name without its terminating zero.
256    pub file_name: Option<Vec<u8>>,
257    /// Comment without its terminating zero.
258    pub comment: Option<Vec<u8>>,
259    /// Complete extra-field payload.
260    pub extra: Option<Vec<u8>>,
261    /// Stored and verified optional header CRC16.
262    pub header_crc16: Option<u16>,
263    /// BGZF `BC` block-size value when a well-formed subfield was present.
264    pub bgzf_block_size: Option<u16>,
265}
266
267/// RFC 1950 header fields for one zlib stream.
268#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
269#[non_exhaustive]
270pub struct ZlibHeaderFields {
271    /// Declared LZ77 window size in bytes.
272    pub window_size: u32,
273    /// Two-bit compressor level hint.
274    pub compression_level: u8,
275    /// Preset dictionary identifier. This decoder currently rejects FDICT, so
276    /// accepted analyses report `None`.
277    pub dictionary_id: Option<u32>,
278}
279
280/// Container header beginning one analyzed stream.
281#[derive(Clone, Debug, Eq, PartialEq)]
282#[non_exhaustive]
283pub enum StreamHeader {
284    /// gzip member header.
285    Gzip(GzipHeaderFields),
286    /// zlib header.
287    Zlib(ZlibHeaderFields),
288    /// Raw DEFLATE has no header.
289    RawDeflate,
290}
291
292/// Verified container trailer ending one analyzed stream.
293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
294#[non_exhaustive]
295pub enum StreamFooter {
296    /// gzip CRC32 and modulo-2^32 output size.
297    Gzip {
298        /// Stored and verified checksum.
299        crc32: u32,
300        /// Stored and verified modulo output size.
301        uncompressed_size: u32,
302    },
303    /// zlib Adler-32.
304    Zlib {
305        /// Stored and verified checksum.
306        adler32: u32,
307    },
308    /// Raw DEFLATE has no trailer.
309    None,
310}
311
312/// One gzip member, zlib stream, or raw-DEFLATE stream.
313#[derive(Clone, Debug, Eq, PartialEq)]
314#[non_exhaustive]
315pub struct StreamAnalysis {
316    /// Zero-based stream index.
317    pub index: u64,
318    /// Parsed container header.
319    pub header: StreamHeader,
320    /// Absolute compressed bit offset of the container header.
321    pub header_offset_in_bits: u64,
322    /// Absolute compressed bit offset of the first DEFLATE block.
323    pub deflate_offset_in_bits: u64,
324    /// Absolute compressed bit offset of the container trailer.
325    pub footer_offset_in_bits: u64,
326    /// Absolute decompressed byte offset where this stream begins.
327    pub uncompressed_offset_in_bytes: u64,
328    /// Verified container trailer.
329    pub footer: StreamFooter,
330    /// Container size including header and trailer.
331    pub compressed_size_in_bits: u64,
332    /// Output bytes produced by this stream.
333    pub uncompressed_size_in_bytes: u64,
334    /// Index of this stream's first entry in [`Analysis::blocks`].
335    pub first_block_index: usize,
336    /// Number of entries belonging to this stream.
337    pub block_count: usize,
338}
339
340/// Complete deterministic structural analysis of an input.
341#[derive(Clone, Debug, Eq, PartialEq)]
342#[non_exhaustive]
343pub struct Analysis {
344    /// Resolved container format.
345    pub format: Format,
346    /// Streams in input order.
347    pub streams: Vec<StreamAnalysis>,
348    /// Blocks in input order across every stream.
349    pub blocks: Vec<BlockAnalysis>,
350    /// Total decompressed size.
351    pub uncompressed_size_in_bytes: u64,
352    /// Total consumed compressed size.
353    pub compressed_size_in_bytes: u64,
354    /// Exact input-wide counts indexed by reference length from 0 through 258.
355    pub backreference_length_counts: [u64; 259],
356}
357
358impl Analysis {
359    /// Returns non-zero block-type counts in stable stored/fixed/dynamic order.
360    #[must_use]
361    pub fn block_type_counts(&self) -> Vec<(BlockType, u64)> {
362        let mut counts = [0_u64; 3];
363        for block in &self.blocks {
364            let index = match block.block_type {
365                BlockType::Uncompressed => 0,
366                BlockType::FixedHuffman => 1,
367                BlockType::DynamicHuffman => 2,
368            };
369            counts[index] += 1;
370        }
371        [
372            BlockType::Uncompressed,
373            BlockType::FixedHuffman,
374            BlockType::DynamicHuffman,
375        ]
376        .into_iter()
377        .zip(counts)
378        .filter(|&(_, count)| count != 0)
379        .collect()
380    }
381
382    /// Returns whether every detailed predecessor-window reference was kept.
383    #[must_use]
384    pub fn has_complete_backreference_details(&self) -> bool {
385        self.blocks
386            .iter()
387            .all(|block| block.omitted_backreference_count == 0)
388    }
389}
390
391struct AnalysisCursor<C> {
392    inner: C,
393    bits: u64,
394    buffered_bits: u8,
395    bit_position: u64,
396    scratch: [u8; 8],
397    serving_buffer: bool,
398    position_overflowed: bool,
399}
400
401impl<C: InputCursor> AnalysisCursor<C> {
402    fn new(inner: C) -> Result<Self, DecodeError> {
403        let bit_position = inner
404            .position()
405            .checked_mul(8)
406            .ok_or(DecodeError::Analysis {
407                reason: AnalysisErrorKind::CounterOverflow {
408                    counter: AnalysisCounter::CompressedBits,
409                },
410            })?;
411        Ok(Self {
412            inner,
413            bits: 0,
414            buffered_bits: 0,
415            bit_position,
416            scratch: [0; 8],
417            serving_buffer: false,
418            position_overflowed: false,
419        })
420    }
421
422    const fn bit_position(&self) -> u64 {
423        self.bit_position
424    }
425
426    #[cold]
427    #[inline(never)]
428    fn refill_bits(&mut self, wanted: u8) -> Result<(), DecodeError> {
429        self.check_position()?;
430        while self.buffered_bits < wanted {
431            let available = self.inner.available()?;
432            let capacity = usize::from((56 - self.buffered_bits) / 8);
433            let count = capacity.min(available.len());
434            if count == 0 {
435                break;
436            }
437            let word = if available.len() >= std::mem::size_of::<u64>() {
438                // SAFETY: `available.len() >= 8` proves that the unaligned
439                // eight-byte load is wholly inside the initialized slice.
440                // `read_unaligned` has no alignment requirement, and `to_le`
441                // normalizes the word before DEFLATE's least-significant-bit
442                // extraction.
443                unsafe { available.as_ptr().cast::<u64>().read_unaligned() }.to_le()
444            } else {
445                let mut tail = [0_u8; 8];
446                tail[..count].copy_from_slice(&available[..count]);
447                u64::from_le_bytes(tail)
448            };
449            let refill_bits = count * 8;
450            let mask = (1_u64 << refill_bits) - 1;
451            self.bits |= (word & mask) << self.buffered_bits;
452            self.buffered_bits += u8::try_from(count * 8).expect("at most seven bytes fit");
453            self.inner.advance(count);
454        }
455        Ok(())
456    }
457
458    #[inline(always)]
459    fn fill_to(&mut self, wanted: u8) -> Result<(), DecodeError> {
460        if self.buffered_bits >= wanted && !self.position_overflowed {
461            Ok(())
462        } else {
463            self.refill_bits(wanted)
464        }
465    }
466
467    fn check_position(&self) -> Result<(), DecodeError> {
468        if self.position_overflowed {
469            Err(DecodeError::Analysis {
470                reason: AnalysisErrorKind::CounterOverflow {
471                    counter: AnalysisCounter::CompressedBits,
472                },
473            })
474        } else {
475            Ok(())
476        }
477    }
478
479    #[inline(always)]
480    fn consume_buffered(&mut self, count: u8) -> Result<(), DecodeError> {
481        if count > self.buffered_bits {
482            return Err(self.deflate_error(deflate::Error::UnexpectedEof));
483        }
484        self.bits >>= count;
485        self.buffered_bits -= count;
486        self.bit_position =
487            self.bit_position
488                .checked_add(u64::from(count))
489                .ok_or(DecodeError::Analysis {
490                    reason: AnalysisErrorKind::CounterOverflow {
491                        counter: AnalysisCounter::CompressedBits,
492                    },
493                })?;
494        Ok(())
495    }
496
497    fn align_to_byte(&mut self) -> Result<(), DecodeError> {
498        let padding = ((8 - self.bit_position % 8) % 8) as u8;
499        if padding != 0 {
500            self.read_bits(padding)?;
501        }
502        Ok(())
503    }
504
505    fn deflate_error(&self, error: deflate::Error) -> DecodeError {
506        let reason = match error {
507            deflate::Error::UnexpectedEof => DeflateErrorKind::Truncated,
508            _ => DeflateErrorKind::InvalidData,
509        };
510        DecodeError::InvalidDeflate {
511            bit_offset: self.bit_position,
512            reason,
513        }
514    }
515}
516
517impl<C: InputCursor> DeflateBits for AnalysisCursor<C> {
518    type Error = DecodeError;
519
520    #[inline(always)]
521    fn read_bits(&mut self, count: u8) -> Result<u32, Self::Error> {
522        debug_assert!(count <= 24);
523        self.fill_to(count)?;
524        if self.buffered_bits < count {
525            return Err(self.deflate_error(deflate::Error::UnexpectedEof));
526        }
527        let mask = if count == 0 { 0 } else { (1_u64 << count) - 1 };
528        let value = (self.bits & mask) as u32;
529        self.consume_buffered(count)?;
530        Ok(value)
531    }
532
533    #[inline(always)]
534    fn peek_bits_padded(&mut self, count: u8) -> Result<(u32, u8), Self::Error> {
535        self.fill_to(count)?;
536        let available = self.buffered_bits.min(count);
537        let mask = if count == 0 { 0 } else { (1_u64 << count) - 1 };
538        Ok(((self.bits & mask) as u32, available))
539    }
540
541    #[inline(always)]
542    fn advance_bits(&mut self, count: u8) -> Result<(), Self::Error> {
543        self.consume_buffered(count)
544    }
545
546    #[inline(always)]
547    fn error(&self, error: deflate::Error) -> Self::Error {
548        self.deflate_error(error)
549    }
550}
551
552impl<C: InputCursor> InputCursor for AnalysisCursor<C> {
553    fn position(&self) -> u64 {
554        self.bit_position / 8
555    }
556
557    fn is_at_end(&mut self) -> Result<bool, DecodeError> {
558        self.check_position()?;
559        if self.buffered_bits >= 8 {
560            return Ok(false);
561        }
562        self.inner.is_at_end()
563    }
564
565    fn available(&mut self) -> Result<&[u8], DecodeError> {
566        self.check_position()?;
567        debug_assert_eq!(self.bit_position % 8, 0);
568        if self.buffered_bits >= 8 {
569            let byte_count = usize::from(self.buffered_bits / 8);
570            for (index, byte) in self.scratch[..byte_count].iter_mut().enumerate() {
571                *byte = (self.bits >> (index * 8)) as u8;
572            }
573            self.serving_buffer = true;
574            return Ok(&self.scratch[..byte_count]);
575        }
576        self.serving_buffer = false;
577        self.inner.available()
578    }
579
580    fn advance(&mut self, count: usize) {
581        if self.serving_buffer {
582            let bits = u8::try_from(count.saturating_mul(8))
583                .expect("the analysis scratch buffer contains at most eight bytes");
584            debug_assert!(bits <= self.buffered_bits);
585            self.bits >>= bits;
586            self.buffered_bits -= bits;
587        } else {
588            self.inner.advance(count);
589        }
590        let additional = u64::try_from(count)
591            .ok()
592            .and_then(|count| count.checked_mul(8));
593        match additional.and_then(|additional| self.bit_position.checked_add(additional)) {
594            Some(position) => self.bit_position = position,
595            None => self.position_overflowed = true,
596        }
597        self.serving_buffer = false;
598    }
599
600    fn verify_source_unchanged(&self) -> Result<(), DecodeError> {
601        self.check_position()?;
602        self.inner.verify_source_unchanged()
603    }
604
605    fn peek_two(&mut self) -> Result<Option<[u8; 2]>, DecodeError> {
606        self.check_position()?;
607        debug_assert_eq!(self.bit_position % 8, 0);
608        self.fill_to(16)?;
609        Ok((self.buffered_bits >= 16).then_some([self.bits as u8, (self.bits >> 8) as u8]))
610    }
611}
612
613enum StreamChecksum {
614    Gzip(Crc32),
615    Zlib(Adler32),
616    None,
617}
618
619struct OutputState {
620    // The prefix is predecessor history and the tail is new output awaiting a
621    // checksum update. Keeping both in one linear allocation lets literals and
622    // matches be written exactly once. When the tail fills, the checksum is
623    // advanced and the newest 32 KiB is compacted back to the prefix.
624    bytes: [u8; WINDOW_SIZE + CHECKSUM_BUFFER_SIZE],
625    history_length: usize,
626    write_position: usize,
627    checksum_start: usize,
628    maximum_distance: usize,
629    checksum: StreamChecksum,
630    stream_size: u64,
631}
632
633impl OutputState {
634    fn new(maximum_distance: usize, checksum: StreamChecksum) -> Self {
635        Self {
636            bytes: [0; WINDOW_SIZE + CHECKSUM_BUFFER_SIZE],
637            history_length: 0,
638            write_position: 0,
639            checksum_start: 0,
640            maximum_distance,
641            checksum,
642            stream_size: 0,
643        }
644    }
645
646    #[inline(always)]
647    fn prepare_output<const CHECK_CONFIGURED_LIMITS: bool>(
648        &mut self,
649        total_output: &mut u64,
650        additional: usize,
651        config: &crate::config::Config,
652    ) -> Result<(), DecodeError> {
653        let actual = if CHECK_CONFIGURED_LIMITS {
654            config.checked_output_total(*total_output, additional)?
655        } else {
656            total_output
657                .checked_add(additional as u64)
658                .ok_or(DecodeError::OutputLimitExceeded { limit: u64::MAX })?
659        };
660        *total_output = actual;
661        // A stream's output is a subset of total output. The checked total
662        // addition above therefore proves that this addition cannot overflow.
663        self.stream_size += additional as u64;
664        Ok(())
665    }
666
667    #[inline(always)]
668    fn ensure_space(&mut self, additional: usize) {
669        debug_assert!(additional <= 258);
670        if self.write_position + additional > self.bytes.len() {
671            self.roll_buffer();
672        }
673        debug_assert!(self.write_position + additional <= self.bytes.len());
674    }
675
676    #[inline(always)]
677    fn emit(&mut self, byte: u8) {
678        self.ensure_space(1);
679        self.bytes[self.write_position] = byte;
680        self.write_position += 1;
681        self.history_length = (self.history_length + 1).min(WINDOW_SIZE);
682    }
683
684    fn append_bytes(&mut self, bytes: &[u8]) {
685        let mut offset = 0;
686        while offset < bytes.len() {
687            if self.write_position == self.bytes.len() {
688                self.roll_buffer();
689            }
690            let count = (bytes.len() - offset).min(self.bytes.len() - self.write_position);
691            self.bytes[self.write_position..self.write_position + count]
692                .copy_from_slice(&bytes[offset..offset + count]);
693            self.write_position += count;
694            self.history_length = (self.history_length + count).min(WINDOW_SIZE);
695            offset += count;
696        }
697    }
698
699    #[inline]
700    fn copy_match(&mut self, distance: usize, length: usize) {
701        debug_assert!(distance != 0);
702        debug_assert!(distance <= self.maximum_distance);
703        debug_assert!(distance <= self.history_length);
704        self.ensure_space(length);
705        let destination = self.write_position;
706        let source = destination - distance;
707        if distance == 1 {
708            let byte = self.bytes[source];
709            self.bytes[destination..destination + length].fill(byte);
710        } else if length <= distance {
711            self.bytes.copy_within(source..source + length, destination);
712        } else {
713            self.bytes
714                .copy_within(source..source + distance, destination);
715            let mut produced = distance;
716            while produced < length {
717                let count = produced.min(length - produced);
718                self.bytes
719                    .copy_within(destination..destination + count, destination + produced);
720                produced += count;
721            }
722        }
723        self.write_position += length;
724        self.history_length = (self.history_length + length).min(WINDOW_SIZE);
725    }
726
727    fn flush_checksum(&mut self) {
728        let bytes = &self.bytes[self.checksum_start..self.write_position];
729        match &mut self.checksum {
730            StreamChecksum::Gzip(checksum) => checksum.update(bytes),
731            StreamChecksum::Zlib(checksum) => checksum.update(bytes),
732            StreamChecksum::None => {}
733        }
734        self.checksum_start = self.write_position;
735    }
736
737    #[cold]
738    fn roll_buffer(&mut self) {
739        self.flush_checksum();
740        let history_start = self.write_position - self.history_length;
741        self.bytes
742            .copy_within(history_start..self.write_position, 0);
743        self.write_position = self.history_length;
744        self.checksum_start = self.write_position;
745    }
746
747    fn finish_checksum(mut self) -> (StreamChecksum, u64) {
748        self.flush_checksum();
749        (self.checksum, self.stream_size)
750    }
751}
752
753struct AnalyzeState<'a> {
754    config: &'a crate::config::Config,
755    options: AnalyzeOptions,
756    analysis: Analysis,
757    total_output: u64,
758    remaining_backreferences: usize,
759    retained_header_bytes: usize,
760}
761
762impl AnalyzeState<'_> {
763    fn reserve_stream(&mut self) -> Result<(), DecodeError> {
764        reserve_item(
765            &mut self.analysis.streams,
766            self.options.maximum_streams,
767            AnalysisResource::Streams,
768        )
769    }
770
771    fn reserve_block(&mut self) -> Result<(), DecodeError> {
772        reserve_item(
773            &mut self.analysis.blocks,
774            self.options.maximum_blocks,
775            AnalysisResource::Blocks,
776        )
777    }
778}
779
780fn reserve_item<T>(
781    values: &mut Vec<T>,
782    limit: usize,
783    resource: AnalysisResource,
784) -> Result<(), DecodeError> {
785    if values.len() >= limit {
786        return Err(DecodeError::Analysis {
787            reason: AnalysisErrorKind::ResourceLimit { resource, limit },
788        });
789    }
790    values.try_reserve(1).map_err(|_| DecodeError::Analysis {
791        reason: AnalysisErrorKind::AllocationFailed {
792            resource,
793            additional: 1,
794        },
795    })
796}
797
798fn alphabet_shape(bytes: &[u8], declared_count: usize) -> Result<AlphabetShape, DecodeError> {
799    let mut code_lengths = Vec::new();
800    code_lengths
801        .try_reserve_exact(bytes.len())
802        .map_err(|_| DecodeError::Analysis {
803            reason: AnalysisErrorKind::AllocationFailed {
804                resource: AnalysisResource::AlphabetCodeLengths,
805                additional: bytes.len(),
806            },
807        })?;
808    code_lengths.extend_from_slice(bytes);
809    Ok(AlphabetShape {
810        code_lengths,
811        declared_count,
812    })
813}
814
815fn record_window_reference(
816    block: &mut BlockAnalysis,
817    coverage: &mut [bool; WINDOW_SIZE],
818    global_lengths: &mut [u64; 259],
819    remaining: &mut usize,
820    distance: usize,
821    length: usize,
822) -> Result<(), DecodeError> {
823    let reference = Backreference {
824        distance: u16::try_from(distance).expect("DEFLATE distances fit u16"),
825        length: u16::try_from(length).expect("DEFLATE lengths fit u16"),
826    };
827    // Every count below is bounded by the number of decoded output symbols.
828    // `prepare_output` has already proved that total output fits in `u64`.
829    block.window_backreference_count += 1;
830    block.farthest_backreference = block.farthest_backreference.max(distance as u64);
831    let length_count = global_lengths
832        .get_mut(length)
833        .expect("DEFLATE reference lengths do not exceed 258");
834    *length_count += 1;
835
836    let begin = WINDOW_SIZE - distance;
837    let end = begin.saturating_add(length).min(WINDOW_SIZE);
838    coverage[begin..end].fill(true);
839
840    if *remaining == 0 {
841        block.omitted_backreference_count += 1;
842        return Ok(());
843    }
844    block
845        .retained_backreferences
846        .try_reserve(1)
847        .map_err(|_| DecodeError::Analysis {
848            reason: AnalysisErrorKind::AllocationFailed {
849                resource: AnalysisResource::Backreferences,
850                additional: 1,
851            },
852        })?;
853    block.retained_backreferences.push(reference);
854    *remaining -= 1;
855    Ok(())
856}
857
858fn coverage_groups(coverage: &[bool; WINDOW_SIZE]) -> u64 {
859    coverage
860        .iter()
861        .copied()
862        .fold((false, 0_u64), |(inside, groups), covered| {
863            (covered, groups + u64::from(covered && !inside))
864        })
865        .1
866}
867
868fn analyze_compressed_symbols<C: InputCursor, const CHECK_CONFIGURED_LIMITS: bool>(
869    cursor: &mut AnalysisCursor<C>,
870    trees: (&Huffman, &Huffman),
871    output: &mut OutputState,
872    state: &mut AnalyzeState<'_>,
873    block: &mut BlockAnalysis,
874    block_output_start: u64,
875    coverage: &mut [bool; WINDOW_SIZE],
876) -> Result<(), DecodeError> {
877    let (literal_tree, distance_tree) = trees;
878    loop {
879        match literal_tree.decode(cursor)? {
880            symbol @ 0..=255 => {
881                output.prepare_output::<CHECK_CONFIGURED_LIMITS>(
882                    &mut state.total_output,
883                    1,
884                    state.config,
885                )?;
886                // This is bounded by total output, whose increment was checked.
887                block.literal_symbols += 1;
888                output.emit(symbol as u8);
889            }
890            END_OF_BLOCK => return Ok(()),
891            symbol @ 257..=285 => {
892                let length_index = symbol - 257;
893                let length = LENGTH_BASE[length_index]
894                    + cursor.read_bits(LENGTH_EXTRA[length_index])? as usize;
895                let distance_symbol = distance_tree.decode(cursor)?;
896                if distance_symbol >= DISTANCE_BASE.len() {
897                    return Err(cursor.deflate_error(deflate::Error::InvalidDistance));
898                }
899                let distance = DISTANCE_BASE[distance_symbol]
900                    + cursor.read_bits(DISTANCE_EXTRA[distance_symbol])? as usize;
901                if distance == 0
902                    || distance > output.maximum_distance
903                    || distance > output.history_length
904                {
905                    return Err(cursor.deflate_error(deflate::Error::InvalidDistance));
906                }
907                let position_in_block = state.total_output - block_output_start;
908                output.prepare_output::<CHECK_CONFIGURED_LIMITS>(
909                    &mut state.total_output,
910                    length,
911                    state.config,
912                )?;
913                // Both counters are bounded by checked total output.
914                block.backreference_symbols += 1;
915                block.copied_bytes += length as u64;
916                if distance as u64 > position_in_block {
917                    let preceding_distance = distance as u64 - position_in_block;
918                    let reported_length = length.min(distance);
919                    record_window_reference(
920                        block,
921                        coverage,
922                        &mut state.analysis.backreference_length_counts,
923                        &mut state.remaining_backreferences,
924                        preceding_distance as usize,
925                        reported_length,
926                    )?;
927                }
928
929                output.copy_match(distance, length);
930            }
931            _ => return Err(cursor.deflate_error(deflate::Error::InvalidSymbol)),
932        }
933    }
934}
935
936fn analyze_block<C: InputCursor, const CHECK_CONFIGURED_LIMITS: bool>(
937    cursor: &mut AnalysisCursor<C>,
938    output: &mut OutputState,
939    state: &mut AnalyzeState<'_>,
940    stream_index: u64,
941    index_in_stream: u64,
942) -> Result<BlockAnalysis, DecodeError> {
943    let block_start = cursor.bit_position();
944    let block_output_start = state.total_output;
945    let is_final = cursor.read_bits(1)? != 0;
946    let encoding = cursor.read_bits(2)?;
947    let mut block = BlockAnalysis {
948        stream_index,
949        index_in_stream,
950        is_final,
951        compressed_offset_in_bits: block_start,
952        uncompressed_offset_in_bytes: block_output_start,
953        ..BlockAnalysis::default()
954    };
955    let mut coverage = [false; WINDOW_SIZE];
956
957    match encoding {
958        0 => {
959            block.block_type = BlockType::Uncompressed;
960            cursor.align_to_byte()?;
961            let length = cursor.read_bits(16)? as u16;
962            let complement = cursor.read_bits(16)? as u16;
963            if length != !complement {
964                return Err(cursor.deflate_error(deflate::Error::InvalidStoredLength));
965            }
966            block.compressed_data_offset_in_bits = cursor.bit_position();
967            output.prepare_output::<CHECK_CONFIGURED_LIMITS>(
968                &mut state.total_output,
969                usize::from(length),
970                state.config,
971            )?;
972            let mut remaining = usize::from(length);
973            while remaining != 0 {
974                let available = cursor.available()?;
975                if available.is_empty() {
976                    return Err(cursor.deflate_error(deflate::Error::UnexpectedEof));
977                }
978                let count = remaining.min(available.len());
979                output.append_bytes(&available[..count]);
980                cursor.advance(count);
981                remaining -= count;
982            }
983        }
984        1 => {
985            block.block_type = BlockType::FixedHuffman;
986            block.compressed_data_offset_in_bits = cursor.bit_position();
987            let (literal, distance) = fixed_trees();
988            analyze_compressed_symbols::<_, CHECK_CONFIGURED_LIMITS>(
989                cursor,
990                (literal, distance),
991                output,
992                state,
993                &mut block,
994                block_output_start,
995                &mut coverage,
996            )?;
997        }
998        2 => {
999            block.block_type = BlockType::DynamicHuffman;
1000            let (literal_tree, distance_tree, declared) = dynamic_trees_with_lengths(cursor)?;
1001            block.compressed_data_offset_in_bits = cursor.bit_position();
1002            block.precode = Some(alphabet_shape(&declared.precode, declared.precode_count)?);
1003            let literal_end = declared.literal_count;
1004            let distance_end = literal_end + declared.distance_count;
1005            block.literal = Some(alphabet_shape(
1006                &declared.lengths[..literal_end],
1007                declared.literal_count,
1008            )?);
1009            block.distance = Some(alphabet_shape(
1010                &declared.lengths[literal_end..distance_end],
1011                declared.distance_count,
1012            )?);
1013            analyze_compressed_symbols::<_, CHECK_CONFIGURED_LIMITS>(
1014                cursor,
1015                (&literal_tree, &distance_tree),
1016                output,
1017                state,
1018                &mut block,
1019                block_output_start,
1020                &mut coverage,
1021            )?;
1022        }
1023        _ => return Err(cursor.deflate_error(deflate::Error::InvalidBlockType)),
1024    }
1025    cursor.check_position()?;
1026
1027    block.compressed_size_in_bits =
1028        cursor
1029            .bit_position()
1030            .checked_sub(block_start)
1031            .ok_or(DecodeError::Analysis {
1032                reason: AnalysisErrorKind::CounterOverflow {
1033                    counter: AnalysisCounter::CompressedBits,
1034                },
1035            })?;
1036    block.uncompressed_size_in_bytes =
1037        state
1038            .total_output
1039            .checked_sub(block_output_start)
1040            .ok_or(DecodeError::Analysis {
1041                reason: AnalysisErrorKind::CounterOverflow {
1042                    counter: AnalysisCounter::DecompressedBytes,
1043                },
1044            })?;
1045    block.merged_window_backreference_count = coverage_groups(&coverage);
1046    if block.uncompressed_size_in_bytes >= WINDOW_SIZE as u64 {
1047        block.used_window_symbols = Some(coverage.iter().filter(|&&used| used).count() as u64);
1048    }
1049    Ok(block)
1050}
1051
1052fn gzip_header(details: DetailedMemberHeader) -> StreamHeader {
1053    StreamHeader::Gzip(GzipHeaderFields {
1054        flags: details.flags,
1055        modification_time: details.modification_time,
1056        extra_flags: details.extra_flags,
1057        operating_system: details.operating_system,
1058        file_name: details.file_name,
1059        comment: details.comment,
1060        extra: details.extra,
1061        header_crc16: details.header_crc16,
1062        bgzf_block_size: details.member.bgzf_block_size,
1063    })
1064}
1065
1066fn read_zlib_header<C: InputCursor>(
1067    cursor: &mut AnalysisCursor<C>,
1068) -> Result<(StreamHeader, usize), DecodeError> {
1069    let offset = cursor.position();
1070    let mut bytes = [0_u8; 2];
1071    for byte in &mut bytes {
1072        let Some(&value) = cursor.available()?.first() else {
1073            return Err(DecodeError::InvalidZlib {
1074                offset,
1075                reason: ZlibErrorKind::Truncated,
1076            });
1077        };
1078        cursor.advance(1);
1079        *byte = value;
1080    }
1081    let window_bits = crate::zlib::parse_header(bytes, offset)?;
1082    Ok((
1083        StreamHeader::Zlib(ZlibHeaderFields {
1084            window_size: 1_u32 << window_bits,
1085            compression_level: bytes[1] >> 6,
1086            dictionary_id: None,
1087        }),
1088        1_usize << window_bits,
1089    ))
1090}
1091
1092fn read_zlib_footer<C: InputCursor>(
1093    cursor: &mut AnalysisCursor<C>,
1094) -> Result<[u8; 4], DecodeError> {
1095    let offset = cursor.position();
1096    let mut bytes = [0_u8; 4];
1097    for byte in &mut bytes {
1098        let Some(&value) = cursor.available()?.first() else {
1099            return Err(DecodeError::InvalidZlib {
1100                offset,
1101                reason: ZlibErrorKind::Truncated,
1102            });
1103        };
1104        cursor.advance(1);
1105        *byte = value;
1106    }
1107    Ok(bytes)
1108}
1109
1110fn analyze_one_stream<C: InputCursor, const CHECK_CONFIGURED_LIMITS: bool>(
1111    cursor: &mut AnalysisCursor<C>,
1112    state: &mut AnalyzeState<'_>,
1113    format: Format,
1114) -> Result<(), DecodeError> {
1115    state.reserve_stream()?;
1116    let stream_index = state.analysis.streams.len() as u64;
1117    let header_offset = cursor.bit_position();
1118    let stream_output_start = state.total_output;
1119    let first_block_index = state.analysis.blocks.len();
1120
1121    let (header, maximum_distance, checksum) = match format {
1122        Format::Gzip => {
1123            let details = parse_member_header_detailed(
1124                cursor,
1125                stream_index == 0,
1126                state.options.maximum_header_bytes,
1127                state.retained_header_bytes,
1128            )?;
1129            state.retained_header_bytes = details.retained_metadata_bytes;
1130            (
1131                gzip_header(details),
1132                WINDOW_SIZE,
1133                StreamChecksum::Gzip(Crc32::new()),
1134            )
1135        }
1136        Format::Zlib => {
1137            let (header, maximum_distance) = read_zlib_header(cursor)?;
1138            (
1139                header,
1140                maximum_distance,
1141                StreamChecksum::Zlib(Adler32::new()),
1142            )
1143        }
1144        Format::RawDeflate => (StreamHeader::RawDeflate, WINDOW_SIZE, StreamChecksum::None),
1145    };
1146    let deflate_offset = cursor.bit_position();
1147    let mut output = OutputState::new(maximum_distance, checksum);
1148    let mut block_index = 0_u64;
1149    loop {
1150        state.reserve_block()?;
1151        let block = analyze_block::<_, CHECK_CONFIGURED_LIMITS>(
1152            cursor,
1153            &mut output,
1154            state,
1155            stream_index,
1156            block_index,
1157        )?;
1158        let final_block = block.is_final;
1159        state.analysis.blocks.push(block);
1160        block_index = block_index.checked_add(1).ok_or(DecodeError::Analysis {
1161            reason: AnalysisErrorKind::CounterOverflow {
1162                counter: AnalysisCounter::StructuralItems,
1163            },
1164        })?;
1165        if final_block {
1166            break;
1167        }
1168    }
1169    cursor.align_to_byte()?;
1170    let footer_offset = cursor.bit_position();
1171    let (checksum, stream_size) = output.finish_checksum();
1172
1173    let footer = match (format, checksum) {
1174        (Format::Gzip, StreamChecksum::Gzip(checksum)) => {
1175            let bytes = cursor.read_exact::<8>(cursor.position())?;
1176            let expected_crc = u32::from_le_bytes(bytes[..4].try_into().expect("four bytes"));
1177            let expected_size = u32::from_le_bytes(bytes[4..].try_into().expect("four bytes"));
1178            let actual_crc = checksum.finish();
1179            if expected_crc != actual_crc {
1180                return Err(DecodeError::ChecksumMismatch {
1181                    member: stream_index,
1182                    expected: expected_crc,
1183                    actual: actual_crc,
1184                });
1185            }
1186            if expected_size != stream_size as u32 {
1187                return Err(DecodeError::SizeMismatch {
1188                    member: stream_index,
1189                    expected: expected_size,
1190                    actual_mod32: stream_size as u32,
1191                });
1192            }
1193            StreamFooter::Gzip {
1194                crc32: expected_crc,
1195                uncompressed_size: expected_size,
1196            }
1197        }
1198        (Format::Zlib, StreamChecksum::Zlib(checksum)) => {
1199            let expected = u32::from_be_bytes(read_zlib_footer(cursor)?);
1200            let actual = checksum.finish();
1201            if expected != actual {
1202                return Err(DecodeError::InvalidZlib {
1203                    offset: footer_offset / 8,
1204                    reason: ZlibErrorKind::ChecksumMismatch { expected, actual },
1205                });
1206            }
1207            StreamFooter::Zlib { adler32: expected }
1208        }
1209        (Format::RawDeflate, StreamChecksum::None) => StreamFooter::None,
1210        _ => unreachable!("the stream checksum follows the selected format"),
1211    };
1212    let stream_end = cursor.bit_position();
1213    let compressed_size_in_bits =
1214        stream_end
1215            .checked_sub(header_offset)
1216            .ok_or(DecodeError::Analysis {
1217                reason: AnalysisErrorKind::CounterOverflow {
1218                    counter: AnalysisCounter::CompressedBits,
1219                },
1220            })?;
1221    let block_count = state.analysis.blocks.len() - first_block_index;
1222    state.analysis.streams.push(StreamAnalysis {
1223        index: stream_index,
1224        header,
1225        header_offset_in_bits: header_offset,
1226        deflate_offset_in_bits: deflate_offset,
1227        footer_offset_in_bits: footer_offset,
1228        uncompressed_offset_in_bytes: stream_output_start,
1229        footer,
1230        compressed_size_in_bits,
1231        uncompressed_size_in_bytes: stream_size,
1232        first_block_index,
1233        block_count,
1234    });
1235    Ok(())
1236}
1237
1238fn analyze_cursor_mode<C: InputCursor, const CHECK_CONFIGURED_LIMITS: bool>(
1239    cursor: C,
1240    config: &crate::config::Config,
1241    options: AnalyzeOptions,
1242) -> Result<Analysis, DecodeError> {
1243    let mut cursor = AnalysisCursor::new(cursor)?;
1244    let format = resolve_cursor_format(&mut cursor, config.format)?;
1245    let mut state = AnalyzeState {
1246        config,
1247        options,
1248        analysis: Analysis {
1249            format,
1250            streams: Vec::new(),
1251            blocks: Vec::new(),
1252            uncompressed_size_in_bytes: 0,
1253            compressed_size_in_bytes: 0,
1254            backreference_length_counts: [0; 259],
1255        },
1256        total_output: 0,
1257        remaining_backreferences: options.maximum_retained_backreferences,
1258        retained_header_bytes: 0,
1259    };
1260
1261    match format {
1262        Format::Gzip => {
1263            if cursor.is_at_end()? {
1264                return Err(DecodeError::InvalidGzip {
1265                    offset: 0,
1266                    reason: GzipErrorKind::BadMagic,
1267                });
1268            }
1269            while !cursor.is_at_end()? {
1270                analyze_one_stream::<_, CHECK_CONFIGURED_LIMITS>(&mut cursor, &mut state, format)?;
1271            }
1272        }
1273        Format::Zlib | Format::RawDeflate => {
1274            analyze_one_stream::<_, CHECK_CONFIGURED_LIMITS>(&mut cursor, &mut state, format)?;
1275            if !cursor.is_at_end()? {
1276                return Err(match format {
1277                    Format::Zlib => DecodeError::InvalidZlib {
1278                        offset: cursor.position(),
1279                        reason: ZlibErrorKind::TrailingGarbage,
1280                    },
1281                    Format::RawDeflate => DecodeError::InvalidDeflate {
1282                        bit_offset: cursor.bit_position(),
1283                        reason: DeflateErrorKind::TrailingGarbage,
1284                    },
1285                    Format::Gzip => unreachable!(),
1286                });
1287            }
1288        }
1289    }
1290
1291    cursor.verify_source_unchanged()?;
1292    config.verify_expected_output(state.total_output)?;
1293    state.analysis.uncompressed_size_in_bytes = state.total_output;
1294    state.analysis.compressed_size_in_bytes = cursor.position();
1295    Ok(state.analysis)
1296}
1297
1298fn analyze_cursor<C: InputCursor>(
1299    cursor: C,
1300    config: &crate::config::Config,
1301    options: AnalyzeOptions,
1302) -> Result<Analysis, DecodeError> {
1303    // Specializing once per operation keeps the common unconstrained walk
1304    // free of two Option checks for every decoded symbol. The constrained
1305    // version retains the exact configured-limit error precedence.
1306    if config.output_limit.is_some() || config.expected_uncompressed_size.is_some() {
1307        analyze_cursor_mode::<_, true>(cursor, config, options)
1308    } else {
1309        analyze_cursor_mode::<_, false>(cursor, config, options)
1310    }
1311}
1312
1313pub(crate) fn analyze_source<R: ReadAt + ?Sized>(
1314    source: &R,
1315    config: &crate::config::Config,
1316    options: AnalyzeOptions,
1317) -> Result<Analysis, DecodeError> {
1318    let cursor = SourceCursor::new(source, config.input_page_size)?;
1319    if cursor.length() > u64::MAX / 8 {
1320        return Err(DecodeError::Analysis {
1321            reason: AnalysisErrorKind::CounterOverflow {
1322                counter: AnalysisCounter::CompressedBits,
1323            },
1324        });
1325    }
1326    analyze_cursor(cursor, config, options)
1327}
1328
1329pub(crate) fn analyze_stream<R: Read>(
1330    source: R,
1331    config: &crate::config::Config,
1332    options: AnalyzeOptions,
1333) -> Result<Analysis, DecodeError> {
1334    analyze_cursor(
1335        StreamCursor::new(source, config.input_page_size),
1336        config,
1337        options,
1338    )
1339}
1340
1341#[cfg(test)]
1342mod tests {
1343    use super::{OutputState, StreamChecksum, WINDOW_SIZE};
1344
1345    fn ordered_history(output: &OutputState) -> Vec<u8> {
1346        let start = output.write_position - output.history_length;
1347        output.bytes[start..output.write_position].to_vec()
1348    }
1349
1350    #[test]
1351    fn bulk_match_copy_matches_naive_overlap_across_buffer_rolls() {
1352        let mut expected: Vec<u8> = (0..WINDOW_SIZE + 173)
1353            .map(|index| (index.wrapping_mul(37) >> 3) as u8)
1354            .collect();
1355        let mut output = OutputState::new(WINDOW_SIZE, StreamChecksum::None);
1356        output.append_bytes(&expected);
1357
1358        for (distance, length) in [
1359            (1, 258),
1360            (3, 257),
1361            (257, 258),
1362            (258, 17),
1363            (WINDOW_SIZE - 1, 258),
1364            (WINDOW_SIZE, 258),
1365        ] {
1366            for _ in 0..length {
1367                let byte = expected[expected.len() - distance];
1368                expected.push(byte);
1369            }
1370            output.copy_match(distance, length);
1371            let suffix = &expected[expected.len() - WINDOW_SIZE..];
1372            assert_eq!(ordered_history(&output), suffix);
1373        }
1374    }
1375}